language-puppet 1.4.6.5 → 1.5.1
raw patch · 87 files changed
+5798/−5270 lines, 87 filesdep +lens-aesondep ~aesondep ~base16-bytestringdep ~bytestringsetup-changed
Dependencies added: lens-aeson
Dependency ranges changed: aeson, base16-bytestring, bytestring, formatting, hashable, hruby, http-api-data, lens, megaparsec, memory, protolude, servant, servant-client, text, vector
Files
- README.md +2/−2
- Setup.hs +0/−2
- language-puppet.cabal +20/−19
- progs/PuppetResources.hs +16/−16
- progs/pdbQuery.hs +2/−2
- progs/yera.hs +1/−1
- src/Erb.hs +7/−6
- src/Erb/Parser.hs +78/−68
- src/Erb/Ruby.hs +46/−46
- src/Facter.hs +173/−130
- src/Hiera/Server.hs +208/−192
- src/Puppet/Interpreter.hs +421/−378
- src/Puppet/Interpreter/Helpers.hs +68/−61
- src/Puppet/Interpreter/IO.hs +101/−98
- src/Puppet/Interpreter/PrettyPrinter.hs +37/−38
- src/Puppet/Interpreter/Resolve.hs +307/−281
- src/Puppet/Interpreter/Resolve/Sprintf.hs +111/−103
- src/Puppet/Interpreter/RubyRandom.hs +91/−85
- src/Puppet/Interpreter/Types.hs +230/−206
- src/Puppet/Language.hs +10/−10
- src/Puppet/Language/Core.hs +28/−30
- src/Puppet/Language/NativeTypes.hs +50/−38
- src/Puppet/Language/NativeTypes/Concat.hs +26/−25
- src/Puppet/Language/NativeTypes/Cron.hs +37/−39
- src/Puppet/Language/NativeTypes/Exec.hs +23/−23
- src/Puppet/Language/NativeTypes/File.hs +77/−70
- src/Puppet/Language/NativeTypes/Group.hs +13/−13
- src/Puppet/Language/NativeTypes/Helpers.hs +131/−121
- src/Puppet/Language/NativeTypes/Host.hs +27/−27
- src/Puppet/Language/NativeTypes/Mount.hs +14/−14
- src/Puppet/Language/NativeTypes/Notify.hs +3/−3
- src/Puppet/Language/NativeTypes/Package.hs +84/−79
- src/Puppet/Language/NativeTypes/SshSecure.hs +10/−10
- src/Puppet/Language/NativeTypes/User.hs +33/−33
- src/Puppet/Language/NativeTypes/ZoneRecord.hs +16/−17
- src/Puppet/Language/Paths.hs +12/−6
- src/Puppet/Language/Resource.hs +168/−137
- src/Puppet/Language/Value.hs +69/−63
- src/Puppet/Language/WireCatalog.hs +33/−31
- src/Puppet/Parser.hs +19/−19
- src/Puppet/Parser/Internal.hs +317/−292
- src/Puppet/Parser/Lens.hs +59/−60
- src/Puppet/Parser/PrettyPrinter.hs +103/−89
- src/Puppet/Parser/Types.hs +114/−90
- src/Puppet/Runner.hs +23/−18
- src/Puppet/Runner/Daemon.hs +125/−118
- src/Puppet/Runner/Daemon/FileParser.hs +45/−47
- src/Puppet/Runner/Daemon/OptionalTests.hs +78/−62
- src/Puppet/Runner/Erb.hs +96/−92
- src/Puppet/Runner/Erb/Evaluate.hs +34/−36
- src/Puppet/Runner/Preferences.hs +127/−119
- src/Puppet/Runner/Puppetlabs.hs +123/−116
- src/Puppet/Runner/Pure.hs +141/−122
- src/Puppet/Runner/Stats.hs +35/−31
- src/Puppet/Runner/Stdlib.hs +261/−249
- src/PuppetDB.hs +67/−64
- src/PuppetDB/Core.hs +111/−100
- src/PuppetDB/Remote.hs +37/−35
- src/PuppetDB/TestDB.hs +97/−93
- src/XPrelude.hs +7/−7
- src/XPrelude/Extra.hs +107/−97
- src/XPrelude/PP.hs +34/−25
- tests/ErbSpec.hs +77/−56
- tests/Helpers.hs +38/−39
- tests/HieraSpec.hs +46/−37
- tests/Interpreter/ClassSpec.hs +16/−18
- tests/Interpreter/CollectorSpec.hs +174/−150
- tests/Interpreter/EvalSpec.hs +36/−37
- tests/Interpreter/EvaluateStatementSpec.hs +9/−9
- tests/Interpreter/Function/AssertPrivateSpec.hs +20/−15
- tests/Interpreter/Function/DeleteAtSpec.hs +20/−20
- tests/Interpreter/Function/EachSpec.hs +58/−51
- tests/Interpreter/Function/JoinKeysToValuesSpec.hs +18/−20
- tests/Interpreter/Function/LookupSpec.hs +10/−8
- tests/Interpreter/Function/MergeSpec.hs +58/−51
- tests/Interpreter/Function/PrefixSpec.hs +25/−26
- tests/Interpreter/Function/ShellquoteSpec.hs +39/−29
- tests/Interpreter/Function/SizeSpec.hs +33/−31
- tests/Interpreter/Function/SprintfSpec.hs +25/−25
- tests/Interpreter/Function/SuffixSpec.hs +25/−26
- tests/Interpreter/Function/WithSpec.hs +18/−19
- tests/Interpreter/IfSpec.hs +24/−19
- tests/Parser/DT.hs +8/−7
- tests/Parser/ExprSpec.hs +55/−43
- tests/Parser/LexerSpec.hs +12/−15
- tests/PuppetdbSpec.hs +9/−11
- tests/Spec.hs +2/−4
README.md view
@@ -47,7 +47,7 @@ - `--node` will display all resources on screen in a nice user-friendly colored fashion. - - `--all` displays statitics and optionally shows dead code.+ - `--all` displays statistics and optionally shows dead code. - `--parse` only goes as far as parsing. No interpretation. @@ -167,7 +167,7 @@ dummy ones). It can be used to: - export data from production PuppetDB to a file (in order to debug- some issue with `puppetresources**).+ some issue with `puppetresources`). - query a Puppetdb **Command line arguments**
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
language-puppet.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: language-puppet-version: 1.4.6.5+version: 1.5.1 synopsis: Tools to parse and evaluate the Puppet DSL. description: This is a set of tools that is supposed to fill all your Puppet needs : syntax checks, catalog compilation, PuppetDB queries, simulation of complex interactions between nodes, Puppet master replacement, and more ! homepage: http://lpuppet.banquise.net/@@ -16,7 +16,7 @@ cabal-version: >=1.10 bug-reports: https://github.com/bartavelle/language-puppet/issues -Tested-With: GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.1+Tested-With: GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.7 extra-source-files: CHANGELOG@@ -102,47 +102,48 @@ default-extensions: OverloadedStrings, BangPatterns, LambdaCase, NoImplicitPrelude, FlexibleContexts, FlexibleInstances ghc-options: -Wall -funbox-strict-fields -- ghc-prof-options: -auto-all -caf-all- build-depends: aeson >= 1.0.0.0 && < 1.6+ build-depends: aeson >= 2.0 && < 2.2 , ansi-wl-pprint >= 0.6.8 , attoparsec >= 0.12 , base >= 4.9 && < 5- , base16-bytestring == 0.1.*- , bytestring < 0.11+ , base16-bytestring >= 1.0 && < 1.1+ , bytestring < 0.12 , case-insensitive == 1.2.* , containers >= 0.5 && < 0.7 , cryptonite >= 0.6 , directory >= 1.2 && < 1.4 , filecache >= 0.2.9 && < 0.5 , filepath >= 1.4- , formatting < 7- , hashable >= 1.2 && < 1.4- , http-api-data >= 0.2 && < 0.5+ , formatting < 8+ , hashable >= 1.2 && < 1.5+ , http-api-data >= 0.2 && < 0.6 , http-client >= 0.4.30 && < 0.8- , hruby >= 0.3.5 && < 0.4+ , hruby >= 0.5.1 && < 0.6 , hslogger >= 1.2 && < 1.4- , lens >= 4.12 && < 5- , megaparsec >= 7 && < 9- , memory >= 0.14 && < 0.16+ , lens >= 4.12 && < 6+ , lens-aeson >= 1.2+ , megaparsec >= 7 && < 10+ , memory >= 0.14 && < 0.19 , mtl >= 2.2.1 && < 2.3 , operational >= 0.2.3 && < 0.3 , parsec == 3.1.* , parser-combinators == 1.* , pcre-utils >= 0.1.7 && < 0.2- , protolude >= 0.2 && < 0.4+ , protolude >= 0.3 && < 0.4 , random , regex-pcre-builtin >= 0.94.4 , scientific >= 0.2 && < 0.4- , servant >= 0.9 && < 0.18- , servant-client >= 0.9 && < 0.18+ , servant >= 0.9 && < 0.20+ , servant-client >= 0.9 && < 0.20 , split == 0.2.* , stm >= 2.4 && < 2.6 , strict-base-types >= 0.3- , text >= 0.11 && < 1.3+ , text >= 0.11 && < 2.1 , time >= 1.5 && < 2 , transformers >= 0.4 && < 0.6 , unix >= 2.7 && < 2.8 , unordered-containers == 0.2.*- , vector >= 0.10 && < 0.13+ , vector >= 0.10 && < 0.14 , yaml >= 0.8.31 && < 0.12 Test-Suite spec hs-source-dirs: tests@@ -158,7 +159,7 @@ , hspec-megaparsec , megaparsec , language-puppet- , lens >= 4.12 && < 5+ , lens , mtl >= 2.2.1 && < 2.3 , scientific >= 0.2 && < 0.4 , strict-base-types@@ -199,7 +200,7 @@ hs-source-dirs: progs default-extensions: BangPatterns, OverloadedStrings default-language: Haskell2010- ghc-options: -Wall -rtsopts -funbox-strict-fields -threaded -with-rtsopts "-A2M" -eventlog+ ghc-options: -Wall -rtsopts -funbox-strict-fields -threaded -with-rtsopts "-A2M" -- ghc-prof-options: -auto-all -caf-all -fprof-auto other-modules: Paths_language_puppet build-depends: base
progs/PuppetResources.hs view
@@ -1,19 +1,18 @@ {-# LANGUAGE LambdaCase #-}-{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE StrictData #-} {-# LANGUAGE CPP #-} module Main where -import XPrelude hiding (option, (<>))+import XPrelude hiding ((<>)) import qualified Control.Concurrent.Async as Async import qualified Data.Aeson as Aeson import qualified Data.HashMap.Strict as Map import qualified Data.HashSet as HS import qualified Data.List as List-import Data.Semigroup as Sem hiding (option)+import Data.Semigroup as Sem import qualified Data.Set as Set import qualified Data.Text as Text import Data.Text.Strict.Lens (unpacked)@@ -32,6 +31,7 @@ import PuppetDB (PuppetDBAPI, dummyPuppetDB, pdbConnect, puppetDBFacts) import qualified PuppetDB+import Data.Maybe (fromJust) type QueryFunc = NodeName -> IO (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) @@ -163,15 +163,15 @@ (Just _, Just _) -> panic "You must choose between a testing PuppetDB and a remote one" (Just url, _) -> pdbConnect mgr url >>= unwrapError "Error when connecting to the remote PuppetDB" (_, Just file) -> PuppetDB.loadTestDB file >>= unwrapError "Error when initializing the PuppetDB API"- pref <- dfPreferences workingdir <&> prefPDB .~ pdbapi- <&> prefHieraPath .~ _optHieraFile- <&> prefIgnoredmodules %~ (`fromMaybe` _optIgnoredMods)- <&> (if _optStrictMode then prefStrictness .~ Strict else identity)- <&> (if _optNoExtraTests then prefExtraTests .~ False else identity)- <&> prefLogLevel .~ _optLoglevel- <&> prefRebaseFile .~ _optRebaseFile+ pref <- dfPreferences workingdir <&> (prefPDB .~ pdbapi)+ . (prefHieraPath .~ _optHieraFile)+ . (prefIgnoredmodules %~ (`fromMaybe` _optIgnoredMods))+ . (if _optStrictMode then prefStrictness .~ Strict else identity)+ . (if _optNoExtraTests then prefExtraTests .~ False else identity)+ . (prefLogLevel .~ _optLoglevel)+ . (prefRebaseFile .~ _optRebaseFile) d <- initDaemon pref- let queryfunc = \node -> fmap (unifyFacts (pref ^. prefFactsDefault) (pref ^. prefFactsOverride)) (puppetDBFacts node pdbapi) >>= getCatalog d node+ let queryfunc node = puppetDBFacts node pdbapi >>= getCatalog d node . unifyFacts (pref ^. prefFactsDefault) (pref ^. prefFactsOverride) pure (queryfunc, pdbapi, parserStats d, catalogStats d, templateStats d) where -- merge 3 sets of facts : some defaults, the original set and some override@@ -288,15 +288,15 @@ allfiles = Set.fromList $ map Text.unpack $ Map.keys pStats when _optDeadcode $ findDeadCode workingdir allres allfiles -- compute statistics- let (parsing, Just (wPName, wPMean)) = worstAndSum pStats- (cataloging, Just (wCName, wCMean)) = worstAndSum cStats- (templating, Just (wTName, wTMean)) = worstAndSum tStats+ let (parsing, (wPName, wPMean)) = worstAndSum pStats+ (cataloging, (wCName, wCMean)) = worstAndSum cStats+ (templating, (wTName, wTMean)) = worstAndSum tStats parserShare = 100 * parsing / cataloging templateShare = 100 * templating / cataloging formatDouble = Text.take 5 . show -- yeah, well ... nbnodes = length topnodes worstAndSum = (_1 %~ getSum)- . (_2 %~ fmap swap . getMaximum)+ . (_2 %~ swap . fromJust . getMaximum) . ifoldMap (\k (StatsPoint cnt total _ _) -> (Sum total, Maximum $ Just (total / fromIntegral cnt, k))) putStr ("\nTested " ++ show nbnodes ++ " nodes. ") unless (nbnodes == 0) $ do@@ -367,7 +367,7 @@ run :: Options -> IO ()-run Options {_optVersion = True, ..} = putStrLn ("language-puppet " ++ Data.Version.showVersion Meta.version)+run Options {_optVersion = True} = putStrLn ("language-puppet " ++ Data.Version.showVersion Meta.version) -- Parse mode run Options {_optParse = Just fp, ..} = do
progs/pdbQuery.hs view
@@ -92,12 +92,12 @@ showHelpText :: ParserPrefs -> ParserInfo a -> IO () showHelpText pprefs pinfo = handleParseResult . Failure $- parserFailure pprefs pinfo ShowHelpText mempty+ parserFailure pprefs pinfo (ShowHelpText Nothing) mempty run :: Options -> IO () run Options {_pdbversion = False, _pdbcmd = Nothing} = putText "Please provide one of the available command (see --help for more information) " *> exitFailure-run Options {_pdbversion = True, ..} = putStrLn ("language-puppet " ++ Meta.showVersion Meta.version)+run Options {_pdbversion = True} = putStrLn ("language-puppet " ++ Meta.showVersion Meta.version) run Options{_pdbcmd = Just pdbcmd, ..} = do mgr <- Http.newManager Http.defaultManagerSettings
progs/yera.hs view
@@ -3,7 +3,7 @@ {-# LANGUAGE StrictData #-} module Main (main) where -import XPrelude hiding (option)+import XPrelude import qualified Data.HashMap.Strict as Map import qualified Data.List as List
src/Erb.hs view
@@ -1,9 +1,10 @@ -- | Entry point to general Erb service -- This module share no dependency with Puppet modules-module Erb (- module Erb.Ruby- , module Erb.Parser-) where+module Erb+ ( module Erb.Ruby,+ module Erb.Parser,+ )+where -import Erb.Parser-import Erb.Ruby+import Erb.Parser+import Erb.Ruby
src/Erb/Parser.hs view
@@ -1,33 +1,31 @@ module Erb.Parser where -import XPrelude.Extra hiding (option, try)--import Control.Exception (catch)-import qualified Data.Text as Text-import Text.Parsec.Char-import Text.Parsec.Combinator hiding (optional)-import Text.Parsec.Error-import Text.Parsec.Expr-import Text.Parsec.Language (emptyDef)-import Text.Parsec.Pos-import Text.Parsec.Prim hiding (many, (<|>))-import Text.Parsec.String-import qualified Text.Parsec.Token as P--import Erb.Ruby+import qualified Data.Text as Text+import Erb.Ruby+import Text.Parsec.Char+import Text.Parsec.Combinator hiding (optional)+import Text.Parsec.Error+import Text.Parsec.Expr+import Text.Parsec.Language (emptyDef)+import Text.Parsec.Pos+import Text.Parsec.Prim hiding (many, (<|>))+import Text.Parsec.String+import qualified Text.Parsec.Token as P+import XPrelude.Extra hiding (try) def :: P.GenLanguageDef String u Identity-def = emptyDef- { P.commentStart = "/*"- , P.commentEnd = "*/"- , P.commentLine = "#"- , P.nestedComments = True- , P.identStart = letter- , P.identLetter = alphaNum <|> oneOf "_"- , P.reservedNames = ["if", "else", "case", "elsif"]- , P.reservedOpNames= ["=>","=","+","-","/","*","+>","->","~>","!"]- , P.caseSensitive = True- }+def =+ emptyDef+ { P.commentStart = "/*",+ P.commentEnd = "*/",+ P.commentLine = "#",+ P.nestedComments = True,+ P.identStart = letter,+ P.identLetter = alphaNum <|> oneOf "_",+ P.reservedNames = ["if", "else", "case", "elsif"],+ P.reservedOpNames = ["=>", "=", "+", "-", "/", "*", "+>", "->", "~>", "!"],+ P.caseSensitive = True+ } lexer :: P.GenTokenParser String u Identity lexer = P.makeTokenParser def@@ -60,34 +58,43 @@ rubyexpression = buildExpressionParser table term <?> "expression" table :: [[Operator String () Identity Expression]]-table = [ [ Infix ( reservedOp "+" >> return PlusOperation ) AssocLeft- , Infix ( reservedOp "-" >> return MinusOperation ) AssocLeft ]- , [ Infix ( reservedOp "/" >> return DivOperation ) AssocLeft- , Infix ( reservedOp "*" >> return MultiplyOperation ) AssocLeft ]- , [ Infix ( reservedOp "<<" >> return ShiftLeftOperation ) AssocLeft- , Infix ( reservedOp ">>" >> return ShiftRightOperation ) AssocLeft ]- , [ Infix ( reservedOp "and" >> return AndOperation ) AssocLeft- , Infix ( reservedOp "or" >> return OrOperation ) AssocLeft ]- , [ Infix ( reservedOp "==" >> return EqualOperation ) AssocLeft- , Infix ( reservedOp "!=" >> return DifferentOperation ) AssocLeft ]- , [ Infix ( reservedOp ">" >> return AboveOperation ) AssocLeft- , Infix ( reservedOp ">=" >> return AboveEqualOperation ) AssocLeft- , Infix ( reservedOp "<=" >> return UnderEqualOperation ) AssocLeft- , Infix ( reservedOp "<" >> return UnderOperation ) AssocLeft ]- , [ Infix ( reservedOp "=~" >> return RegexpOperation ) AssocLeft- , Infix ( reservedOp "!~" >> return NotRegexpOperation ) AssocLeft ]- , [ Prefix ( symbol "!" >> return NotOperation ) ]- , [ Prefix ( symbol "-" >> return NegOperation ) ]- , [ Infix ( reservedOp "?" >> return ConditionalValue ) AssocLeft ]- -- , [ Infix ( reservedOp "." >> return MethodCall ) AssocLeft ]- ]+table =+ [ [ Infix (reservedOp "+" >> return PlusOperation) AssocLeft,+ Infix (reservedOp "-" >> return MinusOperation) AssocLeft+ ],+ [ Infix (reservedOp "/" >> return DivOperation) AssocLeft,+ Infix (reservedOp "*" >> return MultiplyOperation) AssocLeft+ ],+ [ Infix (reservedOp "<<" >> return ShiftLeftOperation) AssocLeft,+ Infix (reservedOp ">>" >> return ShiftRightOperation) AssocLeft+ ],+ [ Infix (reservedOp "and" >> return AndOperation) AssocLeft,+ Infix (reservedOp "or" >> return OrOperation) AssocLeft+ ],+ [ Infix (reservedOp "==" >> return EqualOperation) AssocLeft,+ Infix (reservedOp "!=" >> return DifferentOperation) AssocLeft+ ],+ [ Infix (reservedOp ">" >> return AboveOperation) AssocLeft,+ Infix (reservedOp ">=" >> return AboveEqualOperation) AssocLeft,+ Infix (reservedOp "<=" >> return UnderEqualOperation) AssocLeft,+ Infix (reservedOp "<" >> return UnderOperation) AssocLeft+ ],+ [ Infix (reservedOp "=~" >> return RegexpOperation) AssocLeft,+ Infix (reservedOp "!~" >> return NotRegexpOperation) AssocLeft+ ],+ [Prefix (symbol "!" >> return NotOperation)],+ [Prefix (symbol "-" >> return NegOperation)],+ [Infix (reservedOp "?" >> return ConditionalValue) AssocLeft]+ -- , [ Infix ( reservedOp "." >> return MethodCall ) AssocLeft ]+ ]+ term :: Parser Expression term =- parens rubyexpression- <|> scopeLookup- <|> stringLiteral- <|> objectterm- <|> variablereference+ parens rubyexpression+ <|> scopeLookup+ <|> stringLiteral+ <|> objectterm+ <|> variablereference scopeLookup :: Parser Expression scopeLookup = do@@ -121,11 +128,11 @@ objectterm = do arobase <- optional (char '@') methodname' <- toS <$> identifier- let methodname = Value (Literal $ maybe methodname' (\a -> Text.cons a methodname') arobase)+ let methodname = Value (Literal $ maybe methodname' (`Text.cons` methodname') arobase) lookAhead anyChar >>= \case '[' -> do- hr <- many (symbol "[" *> rubyexpression <* symbol "]")- pure $! foldl LookupOperation (Object methodname) hr+ hr <- many (symbol "[" *> rubyexpression <* symbol "]")+ pure $! foldl LookupOperation (Object methodname) hr '{' -> fmap (MethodCall methodname . BlockOperation . Text.pack) (braces (many1 $ noneOf "}")) '(' -> fmap (MethodCall methodname . Value . Array) (parens (rubyexpression `sepBy` symbol ",")) _ -> return $ Object methodname@@ -136,20 +143,21 @@ rubystatement :: Parser RubyStatement rubystatement = fail "statements not supported yet" -textblockW :: Maybe Char -> Parser [RubyStatement]+textblockW :: Maybe Char -> Parser [RubyStatement] textblockW c = do s <- many (noneOf "<") let ns = case c of- Just x -> x:s+ Just x -> x : s Nothing -> s returned = Puts $ Value $ Literal $ Text.pack ns optionMaybe eof >>= \case- Just _ -> return [returned]+ Just _ -> return [returned] Nothing -> do void $ char '<'- n <- optionMaybe (char '%') >>= \case- Just _ -> rubyblock- Nothing -> textblockW (Just '<')+ n <-+ optionMaybe (char '%') >>= \case+ Just _ -> rubyblock+ Nothing -> textblockW (Just '<') pure (returned : n) textblock :: Parser [RubyStatement]@@ -158,12 +166,13 @@ rubyblock :: Parser [RubyStatement] rubyblock = do ps <- option [] (char '-' >> return [DropPrevSpace'])- parsed <- optionMaybe (char '=') >>= \case- Just _ -> spaces >> fmap (return . Puts) rubyexpression- Nothing -> spaces >> many1 rubystatement+ parsed <-+ optionMaybe (char '=') >>= \case+ Just _ -> spaces >> fmap (return . Puts) rubyexpression+ Nothing -> spaces >> many1 rubystatement spaces- let dn (x:xs) = DropNextSpace x : xs- dn x = x+ let dn (x : xs) = DropNextSpace x : xs+ dn x = x ns <- option identity (char '-' >> return dn) void $ string "%>" n <- textblock@@ -177,8 +186,9 @@ parseContent `catch` handler where parseContent = (runParser erbparser () fname . Text.unpack) `fmap` readFile fname- handler e = let msg = show (e :: SomeException)- in return $ Left $ newErrorMessage (Message msg) (initialPos fname)+ handler e =+ let msg = show (e :: SomeException)+ in return $ Left $ newErrorMessage (Message msg) (initialPos fname) parseErbString :: String -> Either ParseError [RubyStatement] parseErbString = runParser erbparser () mempty
src/Erb/Ruby.hs view
@@ -1,57 +1,57 @@ -- | Base types for the internal ruby parser ("Erb.Parser"). module Erb.Ruby where -import XPrelude+import XPrelude data Value- = Literal !Text- | Interpolable ![Expression]- | Symbol !Text- | Array ![Expression]- deriving (Show, Ord, Eq)+ = Literal !Text+ | Interpolable ![Expression]+ | Symbol !Text+ | Array ![Expression]+ deriving (Show, Ord, Eq) data Expression- = LookupOperation !Expression !Expression- | PlusOperation !Expression !Expression- | MinusOperation !Expression !Expression- | DivOperation !Expression !Expression- | MultiplyOperation !Expression !Expression- | ShiftLeftOperation !Expression !Expression- | ShiftRightOperation !Expression !Expression- | AndOperation !Expression !Expression- | OrOperation !Expression !Expression- | EqualOperation !Expression !Expression- | DifferentOperation !Expression !Expression- | AboveOperation !Expression !Expression- | AboveEqualOperation !Expression !Expression- | UnderEqualOperation !Expression !Expression- | UnderOperation !Expression !Expression- | RegexpOperation !Expression !Expression- | NotRegexpOperation !Expression !Expression- | NotOperation !Expression- | NegOperation !Expression- | ConditionalValue !Expression !Expression- | Object !Expression- | ScopeObject !Expression- | MethodCall !Expression !Expression- | BlockOperation !Text- | Value !Value- | BTrue- | BFalse- | Error !String- deriving (Show, Ord, Eq)+ = LookupOperation !Expression !Expression+ | PlusOperation !Expression !Expression+ | MinusOperation !Expression !Expression+ | DivOperation !Expression !Expression+ | MultiplyOperation !Expression !Expression+ | ShiftLeftOperation !Expression !Expression+ | ShiftRightOperation !Expression !Expression+ | AndOperation !Expression !Expression+ | OrOperation !Expression !Expression+ | EqualOperation !Expression !Expression+ | DifferentOperation !Expression !Expression+ | AboveOperation !Expression !Expression+ | AboveEqualOperation !Expression !Expression+ | UnderEqualOperation !Expression !Expression+ | UnderOperation !Expression !Expression+ | RegexpOperation !Expression !Expression+ | NotRegexpOperation !Expression !Expression+ | NotOperation !Expression+ | NegOperation !Expression+ | ConditionalValue !Expression !Expression+ | Object !Expression+ | ScopeObject !Expression+ | MethodCall !Expression !Expression+ | BlockOperation !Text+ | Value !Value+ | BTrue+ | BFalse+ | Error !String+ deriving (Show, Ord, Eq) instance Pretty Expression where- pretty (LookupOperation a b) = pretty a <> brackets (pretty b)- pretty (PlusOperation a b) = parens (pretty a <+> "+" <+> pretty b)- pretty (MinusOperation a b) = parens (pretty a <+> "-" <+> pretty b)- pretty (DivOperation a b) = parens (pretty a <+> "/" <+> pretty b)- pretty (MultiplyOperation a b) = parens (pretty a <+> "*" <+> pretty b)- pretty op = ppline (show op)+ pretty (LookupOperation a b) = pretty a <> brackets (pretty b)+ pretty (PlusOperation a b) = parens (pretty a <+> "+" <+> pretty b)+ pretty (MinusOperation a b) = parens (pretty a <+> "-" <+> pretty b)+ pretty (DivOperation a b) = parens (pretty a <+> "/" <+> pretty b)+ pretty (MultiplyOperation a b) = parens (pretty a <+> "*" <+> pretty b)+ pretty op = ppline (show op) data RubyStatement- = Puts !Expression- | DropPrevSpace !RubyStatement- | DropPrevSpace'- | DropNextSpace !RubyStatement- deriving(Show,Eq)+ = Puts !Expression+ | DropPrevSpace !RubyStatement+ | DropPrevSpace'+ | DropNextSpace !RubyStatement+ deriving (Show, Eq)
src/Facter.hs view
@@ -1,33 +1,32 @@ {-# LANGUAGE TemplateHaskell #-}-module Facter where -import Prelude+module Facter where -import Control.Lens-import Data.Aeson-import Data.Char+import Control.Lens+import Data.Aeson+import Data.Char import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as HS-import qualified Data.List as List-import qualified Data.List.Split as List-import Data.Maybe (mapMaybe)-import Data.Semigroup-import qualified Data.Text as T-import qualified System.Directory as Directory-import System.Environment-import System.Posix.Unistd (SystemID (..), getSystemID)-import System.Posix.User-import Text.Printf--import Puppet.Language+import qualified Data.HashSet as HS+import qualified Data.List as List+import qualified Data.List.Split as List+import Data.Maybe (mapMaybe)+import qualified Data.Text as T+import Puppet.Language+import qualified System.Directory as Directory+import System.Environment+import System.Posix.Unistd (SystemID (..), getSystemID)+import System.Posix.User+import Text.Printf+import Prelude type Facts = HM.HashMap T.Text PValue data FactInfo = FactInfo- { _factInfoNodename :: !NodeName- , _factInfoName :: !T.Text- , _factInfoVal :: !PValue+ { _factInfoNodename :: !NodeName,+ _factInfoName :: !T.Text,+ _factInfoVal :: !PValue }+ makeClassy ''FactInfo instance ToJSON FactInfo where@@ -35,155 +34,199 @@ instance FromJSON FactInfo where parseJSON (Object v) = FactInfo <$> v .: "certname" <*> v .: "name" <*> v .: "value"- parseJSON _ = fail "invalid fact info"+ parseJSON _ = fail "invalid fact info" storageunits :: [(String, Int)]-storageunits = [ ("", 0), ("K", 1), ("M", 2), ("G", 3), ("T", 4) ]+storageunits = [("", 0), ("K", 1), ("M", 2), ("G", 3), ("T", 4)] getPrefix :: Int -> String-getPrefix n | null fltr = error $ "Could not get unit prefix for order " <> show n- | otherwise = fst $ head fltr- where fltr = filter (\(_, x) -> x == n) storageunits+getPrefix n+ | null fltr = error $ "Could not get unit prefix for order " <> show n+ | otherwise = fst $ head fltr+ where+ fltr = filter (\(_, x) -> x == n) storageunits getOrder :: String -> Int-getOrder n | null fltr = error $ "Could not get order for unit prefix " <> show n- | otherwise = snd $ head fltr+getOrder n+ | null fltr = error $ "Could not get order for unit prefix " <> show n+ | otherwise = snd $ head fltr where nu = map toUpper n fltr = filter (\(x, _) -> x == nu) storageunits normalizeUnit :: (Double, Int) -> Double -> (Double, Int)-normalizeUnit (unit, order) base | unit > base = normalizeUnit (unit/base, order + 1) base- | otherwise = (unit, order)+normalizeUnit (unit, order) base+ | unit > base = normalizeUnit (unit / base, order + 1) base+ | otherwise = (unit, order) storagedesc :: (String, String) -> String-storagedesc (ssize, unit) = let- size = read ssize :: Double- uprefix | unit == "B" = ""- | otherwise = [head unit]- uorder = getOrder uprefix- (osize, oorder) = normalizeUnit (size, uorder) 1024- in printf "%.2f %sB" osize (getPrefix oorder)+storagedesc (ssize, unit) =+ let size = read ssize :: Double+ uprefix+ | unit == "B" = ""+ | otherwise = [head unit]+ uorder = getOrder uprefix+ (osize, oorder) = normalizeUnit (size, uorder) 1024+ in printf "%.2f %sB" osize (getPrefix oorder) factRAM :: IO [(String, String)] factRAM = do- meminfo <- fmap (map words . lines) (readFile "/proc/meminfo")- let memtotal = ginfo "MemTotal:"- memfree = ginfo "MemFree:"- swapfree = ginfo "SwapFree:"- swaptotal = ginfo "SwapTotal:"- ginfo st = sdesc $ head $ filter ((== st) . head) meminfo- sdesc [_, size, unit] = storagedesc (size, unit)- sdesc _ = storagedesc ("1","B")- return [("memorysize", memtotal), ("memoryfree", memfree), ("swapfree", swapfree), ("swapsize", swaptotal)]+ meminfo <- fmap (map words . lines) (readFile "/proc/meminfo")+ let memtotal = ginfo "MemTotal:"+ memfree = ginfo "MemFree:"+ swapfree = ginfo "SwapFree:"+ swaptotal = ginfo "SwapTotal:"+ ginfo st = sdesc $ head $ filter ((== st) . head) meminfo+ sdesc [_, size, unit] = storagedesc (size, unit)+ sdesc _ = storagedesc ("1", "B")+ return [("memorysize", memtotal), ("memoryfree", memfree), ("swapfree", swapfree), ("swapsize", swaptotal)] factNET :: IO [(String, String)] factNET = return [("ipaddress", "192.168.0.1")] factOS :: IO [(String, String)] factOS = do- islsb <- Directory.doesFileExist "/etc/lsb-release"- isdeb <- Directory.doesFileExist "/etc/debian_version"- case (islsb, isdeb) of- (True, _) -> factOSLSB- (_, True) -> factOSDebian- _ -> return []+ islsb <- Directory.doesFileExist "/etc/lsb-release"+ isdeb <- Directory.doesFileExist "/etc/debian_version"+ case (islsb, isdeb) of+ (True, _) -> factOSLSB+ (_, True) -> factOSDebian+ _ -> return [] factOSDebian :: IO [(String, String)] factOSDebian = fmap (toV . head . lines) (readFile "/etc/debian_version")- where- toV v = [ ("lsbdistid" , "Debian")- , ("operatingsystem" , "Debian")- , ("lsbdistrelease" , v)- , ("operatingsystemrelease" , v)- , ("lsbmajdistrelease" , takeWhile (/='.') v)- , ("osfamily" , "Debian")- , ("lsbdistcodename" , codename v)- , ("lsbdistdescription" , "Debian GNU/Linux " <> v <> " (" <> codename v <> ")")- ]- codename v | null v = "unknown"- | h '7' = "wheezy"- | h '6' = "squeeze"- | h '5' = "lenny"- | h '4' = "etch"- | v == "3.1" = "sarge"- | v == "3.0" = "woody"- | v == "2.2" = "potato"- | v == "2.1" = "slink"- | v == "2.0" = "hamm"- | otherwise = "unknown"- where h x = head v == x+ where+ toV v =+ [ ("lsbdistid", "Debian"),+ ("operatingsystem", "Debian"),+ ("lsbdistrelease", v),+ ("operatingsystemrelease", v),+ ("lsbmajdistrelease", takeWhile (/= '.') v),+ ("osfamily", "Debian"),+ ("lsbdistcodename", codename v),+ ("lsbdistdescription", "Debian GNU/Linux " <> v <> " (" <> codename v <> ")")+ ]+ codename v+ | null v = "unknown"+ | h '7' = "wheezy"+ | h '6' = "squeeze"+ | h '5' = "lenny"+ | h '4' = "etch"+ | v == "3.1" = "sarge"+ | v == "3.0" = "woody"+ | v == "2.2" = "potato"+ | v == "2.1" = "slink"+ | v == "2.0" = "hamm"+ | otherwise = "unknown"+ where+ h x = head v == x factOSLSB :: IO [(String, String)] factOSLSB = do- lsb <- fmap (map (break (== '=')) . lines) (readFile "/etc/lsb-release")- let getval st | null filterd = "?"- | otherwise = rvalue- where filterd = filter (\(k,_) -> k == st) lsb- value = (tail . snd . head) filterd- rvalue | head value == '"' = read value- | otherwise = value- lrelease = getval "DISTRIB_RELEASE"- distid = getval "DISTRIB_ID"- maj | lrelease == "?" = "?"- | otherwise = takeWhile (/= '.') lrelease- osfam | distid == "Ubuntu" = "Debian"- | otherwise = distid- return [ ("lsbdistid" , distid)- , ("operatingsystem" , distid)- , ("lsbdistrelease" , lrelease)- , ("operatingsystemrelease" , lrelease)- , ("operatingsystemmajrelease" , lrelease)- , ("lsbmajdistrelease" , maj)- , ("lsbminordistrelease" , "")- , ("osfamily" , osfam)- , ("lsbdistcodename" , getval "DISTRIB_CODENAME")- , ("lsbdistdescription" , getval "DISTRIB_DESCRIPTION")- ]+ lsb <- fmap (map (break (== '=')) . lines) (readFile "/etc/lsb-release")+ let getval st+ | null filterd = "?"+ | otherwise = rvalue+ where+ filterd = filter (\(k, _) -> k == st) lsb+ value = (tail . snd . head) filterd+ rvalue+ | head value == '"' = read value+ | otherwise = value+ lrelease = getval "DISTRIB_RELEASE"+ distid = getval "DISTRIB_ID"+ maj+ | lrelease == "?" = "?"+ | otherwise = takeWhile (/= '.') lrelease+ osfam+ | distid == "Ubuntu" = "Debian"+ | otherwise = distid+ return+ [ ("lsbdistid", distid),+ ("operatingsystem", distid),+ ("lsbdistrelease", lrelease),+ ("operatingsystemrelease", lrelease),+ ("operatingsystemmajrelease", lrelease),+ ("lsbmajdistrelease", maj),+ ("lsbminordistrelease", ""),+ ("osfamily", osfam),+ ("lsbdistcodename", getval "DISTRIB_CODENAME"),+ ("lsbdistdescription", getval "DISTRIB_DESCRIPTION")+ ] factMountPoints :: IO [(String, String)] factMountPoints = do- mountinfo <- fmap (map words . lines) (readFile "/proc/mounts")- let ignorefs = HS.fromList- ["NFS", "nfs", "nfs4", "nfsd", "afs", "binfmt_misc", "proc", "smbfs",- "autofs", "iso9660", "ncpfs", "coda", "devpts", "ftpfs", "devfs",- "mfs", "shfs", "sysfs", "cifs", "lustre_lite", "tmpfs", "usbfs", "udf",- "fusectl", "fuse.snapshotfs", "rpc_pipefs", "configfs", "devtmpfs",- "debugfs", "securityfs", "ecryptfs", "fuse.gvfs-fuse-daemon", "rootfs"- ]- goodlines = filter (\x -> not $ HS.member (x !! 2) ignorefs) mountinfo- goodfs = map (!! 1) goodlines- return [("mountpoints", unwords goodfs)]+ mountinfo <- fmap (map words . lines) (readFile "/proc/mounts")+ let ignorefs =+ HS.fromList+ [ "NFS",+ "nfs",+ "nfs4",+ "nfsd",+ "afs",+ "binfmt_misc",+ "proc",+ "smbfs",+ "autofs",+ "iso9660",+ "ncpfs",+ "coda",+ "devpts",+ "ftpfs",+ "devfs",+ "mfs",+ "shfs",+ "sysfs",+ "cifs",+ "lustre_lite",+ "tmpfs",+ "usbfs",+ "udf",+ "fusectl",+ "fuse.snapshotfs",+ "rpc_pipefs",+ "configfs",+ "devtmpfs",+ "debugfs",+ "securityfs",+ "ecryptfs",+ "fuse.gvfs-fuse-daemon",+ "rootfs"+ ]+ goodlines = filter (\x -> not $ HS.member (x !! 2) ignorefs) mountinfo+ goodfs = map (!! 1) goodlines+ return [("mountpoints", unwords goodfs)] fversion :: IO [(String, String)]-fversion = return [("facterversion", "0.1"),("environment","test")]+fversion = return [("facterversion", "0.1"), ("environment", "test")] factUser :: IO [(String, String)] factUser = do- username <- getEffectiveUserName- return [("id",username)]+ username <- getEffectiveUserName+ return [("id", username)] factUName :: IO [(String, String)] factUName = do- SystemID sn nn rl _ mc <- getSystemID- let vparts = List.splitOn "." (takeWhile (/='-') rl)- return [ ("kernel" , sn) -- Linux- , ("kernelmajversion" , List.intercalate "." (take 2 vparts)) -- 3.5- , ("kernelrelease" , rl) -- 3.5.0-45-generic- , ("kernelversion" , List.intercalate "." (take 3 vparts)) -- 3.5.0- , ("hardwareisa" , mc) -- x86_64- , ("hardwaremodel" , mc) -- x86_64- , ("hostname" , nn)- ]+ SystemID sn nn rl _ mc <- getSystemID+ let vparts = List.splitOn "." (takeWhile (/= '-') rl)+ return+ [ ("kernel", sn), -- Linux+ ("kernelmajversion", List.intercalate "." (take 2 vparts)), -- 3.5+ ("kernelrelease", rl), -- 3.5.0-45-generic+ ("kernelversion", List.intercalate "." (take 3 vparts)), -- 3.5.0+ ("hardwareisa", mc), -- x86_64+ ("hardwaremodel", mc), -- x86_64+ ("hostname", nn)+ ] -fenv :: IO [(String,String)]+fenv :: IO [(String, String)] fenv = do- path <- getEnv "PATH"- return [ ("path", path) ]+ path <- getEnv "PATH"+ return [("path", path)] -factProcessor :: IO [(String,String)]+factProcessor :: IO [(String, String)] factProcessor = do- cpuinfo <- readFile "/proc/cpuinfo"- let cpuinfos = zip [ "processor" <> show (n :: Int) | n <- [0..]] modelnames- modelnames = mapMaybe (fmap (dropWhile (`elem` ("\t :" :: String))) . List.stripPrefix "model name") (lines cpuinfo)- return $ ("processorcount", show (length cpuinfos)) : cpuinfos+ cpuinfo <- readFile "/proc/cpuinfo"+ let cpuinfos = zip ["processor" <> show (n :: Int) | n <- [0 ..]] modelnames+ modelnames = mapMaybe (fmap (dropWhile (`elem` ("\t :" :: String))) . List.stripPrefix "model name") (lines cpuinfo)+ return $ ("processorcount", show (length cpuinfos)) : cpuinfos
src/Hiera/Server.hs view
@@ -1,74 +1,79 @@ {-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE PatternGuards #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeApplications #-}--{- | This module runs a Hiera server that caches Hiera data. There is-a huge caveat : only the data files are watched for changes, not the main configuration file.--A minor bug is that interpolation will not work for inputs containing the % character when it isn't used for interpolation.--}-module Hiera.Server (- startHiera- , dummyHiera- , HieraQueryType (..)- , readQueryType- , HieraQueryFunc- , varSplitter- , HieraVar(..)- , mergeWith-) where+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE PatternGuards #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-} -import XPrelude hiding (space)+-- | This module runs a Hiera server that caches Hiera data. There is+-- a huge caveat : only the data files are watched for changes, not the main configuration file.+--+-- A minor bug is that interpolation will not work for inputs containing the % character when it isn't used for interpolation.+module Hiera.Server+ ( startHiera,+ dummyHiera,+ HieraQueryType (..),+ readQueryType,+ HieraQueryFunc,+ varSplitter,+ HieraVar (..),+ mergeWith,+ )+where -import Data.Aeson-import qualified Data.Aeson as Aeson-import qualified Data.Attoparsec.Text as AT-import qualified Data.ByteString.Lazy as BS+import Data.Aeson+import qualified Data.Aeson as Aeson+import Data.Aeson.Lens (_Key)+import qualified Data.Attoparsec.Text as AT+import qualified Data.ByteString.Lazy as BS import qualified Data.ByteString.Lazy.Char8 as BS8-import qualified Data.Either.Strict as S-import qualified Data.FileCache as Cache-import qualified Data.List as List-import Data.String (fromString)-import qualified Data.Text as Text-import qualified Data.Vector as Vector-import qualified Data.Yaml as Yaml-import qualified System.Directory as Directory-import qualified System.FilePath as FilePath-import System.FilePath.Lens (directory)-import Text.Megaparsec (parse, sepBy, sepBy1, Parsec, satisfy)-import Text.Megaparsec.Char (char, space)--import Puppet.Language-+import qualified Data.Either.Strict as S+import qualified Data.FileCache as Cache+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Data.Vector as Vector+import qualified Data.Yaml as Yaml+import Puppet.Language+import qualified System.Directory as Directory+import qualified System.FilePath as FilePath+import System.FilePath.Lens (directory)+import Text.Megaparsec (Parsec, parse, satisfy, sepBy, sepBy1)+import Text.Megaparsec.Char (char, space)+import XPrelude hiding (space) -- | The different kind of hiera queries. data HieraQueryType- = QFirst -- ^ The first match in the hierarchy is returned.- | QUnique -- ^ Combines array and scalar values to return a merged, flattened array with all duplicate removed.- | QHash -- ^ Combines the keys and values of any number of hashes to return a merged hash.+ = -- | The first match in the hierarchy is returned.+ QFirst+ | -- | Combines array and scalar values to return a merged, flattened array with all duplicate removed.+ QUnique+ | -- | Combines the keys and values of any number of hashes to return a merged hash. -- | Use of an Hash to specify the merge behavior- | QDeep { _knockoutPrefix :: Maybe Text- , _sortMerged :: Bool- , _mergeHashArray :: Bool- } deriving (Show)+ QHash+ | QDeep+ { _knockoutPrefix :: Maybe Text,+ _sortMerged :: Bool,+ _mergeHashArray :: Bool+ }+ deriving (Show) readQueryType :: Text -> Maybe HieraQueryType readQueryType s = case s of- "first" -> Just QFirst+ "first" -> Just QFirst "unique" -> Just QUnique- "hash" -> Just QHash- _ -> Nothing+ "hash" -> Just QHash+ _ -> Nothing -- | The type of the Hiera API function associated to given hierarchy.-type HieraQueryFunc m = Container PValue -- ^ Scope: all variables that Hiera can interpolate (the top level ones are prefixed with '::')- -> Text -- ^ The query- -> HieraQueryType- -> ExceptT PrettyError m (Maybe PValue)+type HieraQueryFunc m =+ -- | Scope: all variables that Hiera can interpolate (the top level ones are prefixed with '::')+ Container PValue ->+ -- | The query+ Text ->+ HieraQueryType ->+ ExceptT PrettyError m (Maybe PValue) data Backend = YamlBackend FilePath@@ -81,48 +86,55 @@ deriving (Show) instance Pretty HieraStringPart where- pretty (HPString t) = ppline t+ pretty (HPString t) = ppline t pretty (HPVariable v) = dullred (ppline ("%{" <> v <> "}")) prettyList = mconcat . map pretty newtype InterpolableHieraString = InterpolableHieraString { getInterpolableHieraString :: [HieraStringPart]- } deriving (Show)+ }+ deriving (Show) data HieraVar- = HieraVar (NonEmpty Text)- | HieraFunction (NonEmpty Text) [Text]- deriving (Show, Eq)+ = HieraVar (NonEmpty Text)+ | HieraFunction (NonEmpty Text) [Text]+ deriving (Show, Eq) -varSplitter :: Text -> HieraVar -- ^ returns variable parts, function arguments+varSplitter ::+ Text ->+ -- | returns variable parts, function arguments+ HieraVar varSplitter v =- case parse parser "dummy" v of- Left _ -> HieraVar (v :| [])- Right hv -> hv- where- parser :: Parsec Void Text HieraVar- parser = do- x:xs <- vpart `sepBy1` char '.'- let nms = x :| xs- margs <- optional (- char '(' *> space *>- (arg `sepBy` (space *> char ',' *> space))- <* space <* char ')'- )- pure $ case margs of- Nothing -> HieraVar nms- Just args -> HieraFunction nms args- arg = squotev <|> dquotev- vpart = squotev <|> dquotev <|> rawv- rawv = Text.pack <$> some (satisfy (\c -> c /= '.' && c /= '('))- -- TODO, escapes- squotev = Text.pack <$> (char '\'' *> some (satisfy (/= '\'')) <* char '\'')- dquotev = Text.pack <$> (char '"' *> some (satisfy (/= '"')) <* char '"')+ case parse parser "dummy" v of+ Left _ -> HieraVar (v :| [])+ Right hv -> hv+ where+ parser :: Parsec Void Text HieraVar+ parser = do+ x : xs <- vpart `sepBy1` char '.'+ let nms = x :| xs+ margs <-+ optional+ ( char '('+ *> space+ *> (arg `sepBy` (space *> char ',' *> space))+ <* space+ <* char ')'+ )+ pure $ case margs of+ Nothing -> HieraVar nms+ Just args -> HieraFunction nms args+ arg = squotev <|> dquotev+ vpart = squotev <|> dquotev <|> rawv+ rawv = Text.pack <$> some (satisfy (\c -> c /= '.' && c /= '('))+ -- TODO, escapes+ squotev = Text.pack <$> (char '\'' *> some (satisfy (/= '\'')) <* char '\'')+ dquotev = Text.pack <$> (char '"' *> some (satisfy (/= '"')) <* char '"') resolveString :: Container PValue -> InterpolableHieraString -> Maybe Text resolveString vars = fmap Text.concat . mapM resolve . getInterpolableHieraString where- resolve (HPString x) = Just x+ resolve (HPString x) = Just x resolve (HPVariable v) = vars ^? ix v . _PString instance FromJSON InterpolableHieraString where@@ -142,64 +154,60 @@ parseInterpolableString = AT.parseOnly interpolableString data HieraConfigFile = HieraConfigFile- { _version :: Int- , _backends :: [Backend]- , _hierarchy :: [InterpolableHieraString]- } deriving (Show)+ { _version :: Int,+ _backends :: [Backend],+ _hierarchy :: [InterpolableHieraString]+ }+ deriving (Show) data QRead = QRead- { _qvars :: Container PValue- , _qtype :: HieraQueryType- , _qhier :: [Value]+ { _qvars :: Container PValue,+ _qtype :: HieraQueryType,+ _qhier :: [Value] } makeLenses ''QRead instance FromJSON HieraConfigFile where parseJSON =- let- mkHiera5 :: Object -> Yaml.Parser HieraConfigFile- mkHiera5 v = do- -- we currently only read the first hierarchy entry to get the hiera path- -- TODO: change the definition of HieraConfigFile to be [(Backend, InterpolableHieraString)]- -- to allow defining a Backend per hierarchies- let paths = Object v ^.. key "hierarchy" . avalues . key "paths" . avalues- path = Object v ^.. key "hierarchy" .avalues .key "path"- hierarchy_value <- case Object v ^? key "hierarchy" . nth 0 of- Just (Object h) -> pure h- _ -> fail "Hiera config should define at least one hierarchy"- datadir <- hierarchy_value .:? "datadir" >>= \case- Just (String dir) -> pure dir- Just _ -> fail "datadir should be a string"- Nothing -> pure $ Object v ^. key "defaults" . key "datadir" . _String- HieraConfigFile- <$> pure 5- <*> pure [ YamlBackend (toS datadir) ] -- TODO: support other backends if needed- <*> mapM parseJSON (paths <> path)- mkHiera3 v =- HieraConfigFile- <$> pure 3- <*> (v .:? ":backends" .!= ["yaml"] >>= mapM mkBackend3)+ let mkHiera5 :: Object -> Yaml.Parser HieraConfigFile+ mkHiera5 v = do+ -- we currently only read the first hierarchy entry to get the hiera path+ -- TODO: change the definition of HieraConfigFile to be [(Backend, InterpolableHieraString)]+ -- to allow defining a Backend per hierarchies+ let paths = Object v ^.. key "hierarchy" . avalues . key "paths" . avalues+ path = Object v ^.. key "hierarchy" . avalues . key "path"+ hierarchy_value <- case Object v ^? key "hierarchy" . nth 0 of+ Just (Object h) -> pure h+ _ -> fail "Hiera config should define at least one hierarchy"+ datadir <-+ hierarchy_value .:? "datadir" >>= \case+ Just (String dir) -> pure dir+ Just _ -> fail "datadir should be a string"+ Nothing -> pure $ Object v ^. key "defaults" . key "datadir" . _String+ HieraConfigFile 5 [YamlBackend (toS datadir)] -- TODO: support other backends if needed+ <$> mapM parseJSON (paths <> path)+ mkHiera3 v =+ HieraConfigFile 3+ <$> (v .:? ":backends" .!= ["yaml"] >>= mapM mkBackend3) <*> (v .:? ":hierarchy" .!= [InterpolableHieraString [HPString "common"]])- where- mkBackend3 :: Text -> Yaml.Parser Backend- mkBackend3 name = do- (backendConstructor, skey) <- case name of- "yaml" -> return (YamlBackend, ":yaml")- "json" -> return (JsonBackend, ":json")- _ -> fail ("Unknown backend " <> toS name)- datadir <- case Object v ^? key skey . key ":datadir" of- Just (String dir) -> return dir- Just _ -> fail ":datadir should be a string"- Nothing -> return "/etc/puppet/hieradata"- pure (backendConstructor (toS datadir))-- in- Aeson.withObject "v3 or v5" $ \o ->- o .:? "version" >>= \case- Just (5::Int) -> mkHiera5 o- Just _ -> fail "Hiera configuration version different than 5 is not supported."- Nothing -> mkHiera3 o+ where+ mkBackend3 :: Text -> Yaml.Parser Backend+ mkBackend3 name = do+ (backendConstructor, skey) <- case name of+ "yaml" -> return (YamlBackend, ":yaml")+ "json" -> return (JsonBackend, ":json")+ _ -> fail ("Unknown backend " <> toS name)+ datadir <- case Object v ^? key skey . key ":datadir" of+ Just (String dir) -> return dir+ Just _ -> fail ":datadir should be a string"+ Nothing -> return "/etc/puppet/hieradata"+ pure (backendConstructor (toS datadir))+ in Aeson.withObject "v3 or v5" $ \o ->+ o .:? "version" >>= \case+ Just (5 :: Int) -> mkHiera5 o+ Just _ -> fail "Hiera configuration version different than 5 is not supported."+ Nothing -> mkHiera3 o type Cache = Cache.FileCacheR String Value @@ -211,16 +219,16 @@ Left (Yaml.AesonException "Error in $: Hiera configuration version different than 5 is not supported.") -> do logInfoStr ("Detect a hiera configuration format in " <> fp <> " at version 4. This format is not recognized. Using a dummy hiera.") pure dummyHiera- Left ex -> panic (show ex)- Right cfg@HieraConfigFile{..} -> do+ Left ex -> panic (show ex)+ Right cfg@HieraConfigFile {..} -> do logInfoStr ("Detect a hiera " <> layer <> " configuration format in " <> fp <> " at version " <> show _version) query cfg fp <$> Cache.newFileCache -- | A dummy hiera function that will be used when hiera is not detected.-dummyHiera :: Monad m => HieraQueryFunc m+dummyHiera :: (Monad m) => HieraQueryFunc m dummyHiera _ _ _ = pure Nothing -exceptt :: Applicative m => Except r a -> ExceptT r m a+exceptt :: (Applicative m) => Except r a -> ExceptT r m a exceptt = ExceptT . pure . runExcept query :: HieraConfigFile -> FilePath -> Cache -> HieraQueryFunc IO@@ -228,49 +236,54 @@ -- step 1, resolve hierarchies let searchin = do mhierarchy <- resolveString vars <$> _hierarchy- Just h <- [mhierarchy]- backend <- _backends+ Just h <- [mhierarchy]+ backend <- _backends let decodeInfo :: (FilePath -> IO (Either String Value), String, String) decodeInfo = case backend of- JsonBackend dir -> (fmap Aeson.eitherDecode' . BS.readFile , dir, ".json")+ JsonBackend dir -> (fmap Aeson.eitherDecode' . BS.readFile, dir, ".json") YamlBackend dir -> (fmap (_Left %~ show) . Yaml.decodeFileEither, dir, ".yaml") pure (decodeInfo, toS h) -- step 2, read all the files, returning a raw data structure mvals <- liftIO $ forM searchin $ \((decodefunction, datadir, extension), h) -> do- let extension' = if snd (FilePath.splitExtension h) == ".yaml"- then ""- else extension+ let extension' =+ if snd (FilePath.splitExtension h) == ".yaml"+ then ""+ else extension filename = basedir <> datadir <> "/" <> h <> extension' basedir = case datadir of '/' : _ -> mempty- _ -> fp ^. directory <> "/"+ _ -> fp ^. directory <> "/" querycache = do efilecontent <- Cache.query cache filename (strictifyEither <$> decodefunction filename) case efilecontent of S.Left r -> do- logWarningStr $ "Hiera: error when reading file " <> filename <> ": "<> r+ logWarningStr $ "Hiera: error when reading file " <> filename <> ": " <> r pure Nothing S.Right val -> pure (Just val)- ifM (Directory.doesFileExist filename)+ ifM+ (Directory.doesFileExist filename) querycache (pure Nothing) let vals = catMaybes mvals -- step 3, query through all the results- liftIO $ logDebugStr ("Looking up '" <> toS hquery <> "' with backends " <> List.unwords (fmap show _backends ))+ liftIO $ logDebugStr ("Looking up '" <> toS hquery <> "' with backends " <> List.unwords (fmap show _backends)) exceptt $ runReaderT (recursiveQuery hquery []) (QRead vars qt vals) type QM a = ReaderT QRead (Except PrettyError) a checkLoop :: Text -> [Text] -> QM () checkLoop x xs =- when (x `elem` xs) (throwError ("Loop in hiera: " <> fromString (Text.unpack (Text.intercalate ", " (x:xs)))))+ when (x `elem` xs) (throwError ("Loop in hiera: " <> fromString (Text.unpack (Text.intercalate ", " (x : xs))))) -- a helper function that removes prefix and suffix-textBetween :: Text -- ^ prefix- -> Text -- ^ suffix- -> Text- -> Maybe Text+textBetween ::+ -- | prefix+ Text ->+ -- | suffix+ Text ->+ Text ->+ Maybe Text textBetween pr su = Text.stripPrefix pr >=> Text.stripSuffix su recursiveQuery :: Text -> [Text] -> QM (Maybe PValue)@@ -279,11 +292,11 @@ HieraFunction _ _ -> throwError "Hiera functions not yet handled here (A)" HieraVar (varname :| allkeys) -> do checkLoop varname prevqueries- rawlookups <- mapMaybe (preview (key varname)) <$> view qhier+ rawlookups <- mapMaybe (preview (key (varname ^. _Key))) <$> view qhier let lookupKeys keys v = case keys of [] -> pure v- k:ks ->+ k : ks -> case v of Object hs -> case hs ^? ix k of@@ -291,14 +304,14 @@ Just v' -> lookupKeys ks v' _ -> Nothing rlookups <- mapM (resolveValue (varname : prevqueries)) rawlookups- let lookups = mapMaybe (lookupKeys allkeys) rlookups+ let lookups = mapMaybe (lookupKeys (allkeys ^.. traversed . _Key)) rlookups case traverse Aeson.fromJSON lookups of Aeson.Error rr -> throwError ("Something horrible happened in recursiveQuery: " <> fromString rr) Aeson.Success [] -> if null rlookups then return Nothing- else throwError ("Could not lookup " <> fromString (Text.unpack curquery) <> " in " <> PrettyError (list (map (fromString . BS8.unpack . encode) rlookups)) )- Aeson.Success (x:xs) -> do+ else throwError ("Could not lookup " <> fromString (Text.unpack curquery) <> " in " <> PrettyError (list (map (fromString . BS8.unpack . encode) rlookups)))+ Aeson.Success (x : xs) -> do qt <- view qtype Just <$> foldM (mergeWith qt) x xs @@ -306,20 +319,20 @@ resolveValue prevqueries value = case value of String t | Just alias <- Text.stripPrefix "%{alias('" t >>= Text.stripSuffix "')}" -> do- mr <- recursiveQuery alias (("alias:" <> alias) : prevqueries)- case mr of- Nothing -> throwError ("Could not alias " <> fromString (Text.unpack alias))- Just r -> pure (toJSON r)- String t -> String <$> resolveText prevqueries t+ mr <- recursiveQuery alias (("alias:" <> alias) : prevqueries)+ case mr of+ Nothing -> throwError ("Could not alias " <> fromString (Text.unpack alias))+ Just r -> pure (toJSON r)+ String t -> String <$> resolveText prevqueries t Array arr -> Array <$> mapM (resolveValue prevqueries) arr Object hh -> Object <$> mapM (resolveValue prevqueries) hh- _ -> return value+ _ -> return value resolveText :: [Text] -> Text -> QM Text resolveText prevqueries t = case parseInterpolableString t of Right qparts -> Text.concat <$> mapM (resolveStringPart prevqueries) qparts- Left _ -> return t+ Left _ -> return t resolveStringPart :: [Text] -> HieraStringPart -> QM Text resolveStringPart prevqueries sp =@@ -330,48 +343,51 @@ let rc vars val = case vars of [] -> pure val- v:vs -> case val of- PHash m -> m ^? ix v >>= rc vs- _ -> Nothing- extractFunction txt = textBetween (txt <> "('") "')" varname- <|> textBetween (txt <> "(\"") "\")" varname+ v : vs -> case val of+ PHash m -> m ^? ix v >>= rc vs+ _ -> Nothing+ extractFunction txt =+ textBetween (txt <> "('") "')" varname+ <|> textBetween (txt <> "(\"") "\")" varname r <- case extractFunction "lookup" <|> extractFunction "hiera" <|> extractFunction "alias" of Just lk -> recursiveQuery lk prevqueries Nothing -> do- vmap <- view qvars- case varSplitter varname of- HieraVar (mvar :| svars) -> pure (vmap ^? ix mvar >>= rc svars)- HieraFunction (fname :| []) args ->- case fname of- "literal" ->- case args of- [x] -> pure (Just (PString x))- _ -> throwError "The literal function expects a single argument"- _ -> throwError ("Unknown function " <> fromString (Text.unpack fname))- HieraFunction (fname :| xs) _ ->- throwError ("Malformed function name: "- <> fromString (Text.unpack (Text.intercalate "." (fname:xs))))+ vmap <- view qvars+ case varSplitter varname of+ HieraVar (mvar :| svars) -> pure (vmap ^? ix mvar >>= rc svars)+ HieraFunction (fname :| []) args ->+ case fname of+ "literal" ->+ case args of+ [x] -> pure (Just (PString x))+ _ -> throwError "The literal function expects a single argument"+ _ -> throwError ("Unknown function " <> fromString (Text.unpack fname))+ HieraFunction (fname :| xs) _ ->+ throwError+ ( "Malformed function name: "+ <> fromString (Text.unpack (Text.intercalate "." (fname : xs)))+ ) case r of Just (PString v) -> return v Just (PNumber s) -> pure (scientific2text s)- Just pvalue -> throwError (PrettyError ("Variable lookup for " <> fromString (Text.unpack varname) <> " did not return a string, but " <> pretty pvalue))- _ -> throwError ("Could not lookup variable " <> fromString (Text.unpack varname))+ Just pvalue -> throwError (PrettyError ("Variable lookup for " <> fromString (Text.unpack varname) <> " did not return a string, but " <> pretty pvalue))+ _ -> throwError ("Could not lookup variable " <> fromString (Text.unpack varname)) -mergeWith :: MonadError PrettyError m => HieraQueryType -> PValue -> PValue -> m PValue+mergeWith :: (MonadError PrettyError m) => HieraQueryType -> PValue -> PValue -> m PValue mergeWith qt cur new = case qt of QFirst -> return cur QUnique -> let getArray x = case x of- PArray array -> Vector.toList array- _ -> [x]+ PArray array -> Vector.toList array+ _ -> [x] curarray = getArray cur newarray = getArray new- in case new of- PHash _ -> throwError "Tried to merge a hash"- _ -> return (PArray (Vector.fromList (List.nub (curarray ++ newarray))))+ in case new of+ PHash _ -> throwError "Tried to merge a hash"+ _ -> return (PArray (Vector.fromList (List.nub (curarray ++ newarray)))) QHash -> case (cur, new) of (PHash curh, PHash newh) -> return (PHash (curh <> newh)) _ -> throwError (PrettyError ("Tried to merge things that are not hashes: " <> ppline (show cur) <+> ppline (show new)))- QDeep{} -> throwError "deep queries not supported"+ QDeep {} -> throwError "deep queries not supported"
src/Puppet/Interpreter.hs view
@@ -1,59 +1,62 @@-{-# LANGUAGE GADTs #-}-{-# LANGUAGE RankNTypes #-}-module Puppet.Interpreter- ( interpretCatalog- , computeCatalog- , evaluateStatement- -- * Utils- , initialState- , extractScope- , containerModName- , askFact- , module Puppet.Interpreter.Types- , module Puppet.Interpreter.Resolve- , module Puppet.Interpreter.IO- ) where--import XPrelude.Extra-import XPrelude.PP+{-# LANGUAGE GADTs #-}+{-# LANGUAGE RankNTypes #-} -import Control.Monad.Operational hiding (view)-import qualified Data.Char as Char-import qualified Data.Graph as Graph-import qualified Data.HashMap.Strict as Map-import qualified Data.HashSet as Set-import qualified Data.List as List-import qualified Data.Maybe.Strict as S-import Data.Semigroup (Max (..))-import qualified Data.Text as Text-import qualified Data.Tree as Tree-import qualified Data.Vector as V-import qualified System.Log.Logger as Log+module Puppet.Interpreter+ ( interpretCatalog,+ computeCatalog,+ evaluateStatement, -import Facter-import Hiera.Server-import Puppet.Interpreter.Helpers-import Puppet.Interpreter.IO-import Puppet.Interpreter.PrettyPrinter ()-import Puppet.Interpreter.Resolve-import Puppet.Interpreter.Types-import Puppet.Parser+ -- * Utils+ initialState,+ extractScope,+ containerModName,+ askFact,+ module Puppet.Interpreter.Types,+ module Puppet.Interpreter.Resolve,+ module Puppet.Interpreter.IO,+ )+where -{-| Call the operational 'interpretMonad' function to compute the catalog.- Returns either an error, or a tuple containing all the resources,- dependency map, exported resources, and defined resources alongside with- all messages that have been generated by the compilation process.+import Control.Monad.Operational hiding (view)+import qualified Data.Char as Char+import qualified Data.Graph as Graph+import qualified Data.HashMap.Strict as Map+import qualified Data.HashSet as Set+import qualified Data.List as List+import qualified Data.Maybe.Strict as S+import Data.Semigroup (Max (..))+import qualified Data.Text as Text+import qualified Data.Tree as Tree+import qualified Data.Vector as V+import Facter+import Hiera.Server+import Puppet.Interpreter.Helpers+import Puppet.Interpreter.IO+import Puppet.Interpreter.PrettyPrinter ()+import Puppet.Interpreter.Resolve+import Puppet.Interpreter.Types+import Puppet.Parser+import qualified System.Log.Logger as Log+import XPrelude.Extra+import XPrelude.PP - The later defined resources (eg. all class declarations) are pulled out of the- 'InterpreterState' and might not be up to date.- There are only useful for coverage testing (checking dependencies for instance).--}-interpretCatalog :: Monad m- => InterpreterReader m -- ^ The whole environment required for computing catalog.- -> NodeName- -> Facts- -> Container Text -- ^ Server settings- -> m (Pair (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) [Pair Log.Priority Doc])+-- | Call the operational 'interpretMonad' function to compute the catalog.+-- Returns either an error, or a tuple containing all the resources,+-- dependency map, exported resources, and defined resources alongside with+-- all messages that have been generated by the compilation process.+--+-- The later defined resources (eg. all class declarations) are pulled out of the+-- 'InterpreterState' and might not be up to date.+-- There are only useful for coverage testing (checking dependencies for instance).+interpretCatalog ::+ (Monad m) =>+ -- | The whole environment required for computing catalog.+ InterpreterReader m ->+ NodeName ->+ Facts ->+ -- | Server settings+ Container Text ->+ m (Pair (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) [Pair Log.Priority Doc]) interpretCatalog r node facts settings = do (output, _, warnings) <- interpretMonad r (initialState facts settings) (computeCatalog node) pure (output :!: warnings)@@ -61,49 +64,56 @@ isParent :: Text -> CurContainerDesc -> InterpreterMonad Bool isParent cur (ContClass possibleparent) = preuse (scopes . ix cur . scopeParent) >>= \case- Nothing -> throwPosError ("Internal error: could not find scope" <+> ppline cur <+> "possible parent" <+> ppline possibleparent)- Just S.Nothing -> pure False- Just (S.Just p) -> if p == possibleparent- then pure True- else isParent p (ContClass possibleparent)+ Nothing -> throwPosError ("Internal error: could not find scope" <+> ppline cur <+> "possible parent" <+> ppline possibleparent)+ Just S.Nothing -> pure False+ Just (S.Just p) ->+ if p == possibleparent+ then pure True+ else isParent p (ContClass possibleparent) isParent _ _ = pure False -- | Apply resource defaults, references overrides and expand defines finalize :: [Resource] -> InterpreterMonad [Resource] finalize rx = do- scp <- getScopeName+ scp <- getScopeName resdefaults <- use (scopes . ix scp . scopeResDefaults) let getOver = use (scopes . ix scp . scopeOverrides) -- retrieves current overrides addResDefaults r = ifoldlM (addAttribute CantReplace) r resdefval- where resdefval = resdefaults ^. ix (r ^. rid . itype) . resDefValues+ where+ resdefval = resdefaults ^. ix (r ^. rid . itype) . resDefValues addOverrides r = getOver >>= foldlM addOverrides' r . view (at (r ^. rid)) addOverrides' r (ResRefOverride _ prms p) = do- -- we used this override, so we discard it- scopes . ix scp . scopeOverrides . at (r ^. rid) .= Nothing- let forb msg = throwPosError ("Override of parameters ("- <> list (map (ppline . fst) $ itoList prms)- <> ") of the following resource is forbidden in the current context:"- </> pretty r- <+> showPPos p- </> ":"- <+> msg)- s <- getScope- overrideType <- case r ^. rscope of- [] -> forb "Could not find the current resource context" -- we could not get the current resource context- (x:_) -> if x == s- then pure CantOverride -- we are in the same context : can't replace, but add stuff- else isParent (scopeName s) x >>= \i ->- if i || (r ^. rid . itype == "class")- then pure Replace -- we can override what's defined in a parent- else forb "Can't override something that was not defined in the parent."- ifoldlM (addAttribute overrideType) r prms+ -- we used this override, so we discard it+ scopes . ix scp . scopeOverrides . at (r ^. rid) .= Nothing+ let forb msg =+ throwPosError+ ( "Override of parameters ("+ <> list (map (ppline . fst) $ itoList prms)+ <> ") of the following resource is forbidden in the current context:"+ </> pretty r+ <+> showPPos p+ </> ":"+ <+> msg+ )+ s <- getScope+ overrideType <- case r ^. rscope of+ [] -> forb "Could not find the current resource context" -- we could not get the current resource context+ (x : _) ->+ if x == s+ then pure CantOverride -- we are in the same context : can't replace, but add stuff+ else+ isParent (scopeName s) x >>= \i ->+ if i || (r ^. rid . itype == "class")+ then pure Replace -- we can override what's defined in a parent+ else forb "Can't override something that was not defined in the parent."+ ifoldlM (addAttribute overrideType) r prms -- step 1, apply resDefaults and resRefOverride withDefaults <- mapM (addOverrides >=> addResDefaults) rx -- There might be some overrides that could not be applied. The only -- valid reason is that they override something in exported resources. -- -- it probably do something unexpected on defines, but let's keep it that way for now.- let keepforlater (ResRefOverride resid resprms ropos) = resModifiers %= (appended : )+ let keepforlater (ResRefOverride resid resprms ropos) = resModifiers %= (appended :) where appended = ResourceModifier (resid ^. itype) ModifierMustMatch DontRealize (REqualitySearch "title" (PString (resid ^. iname))) overrider ropos overrider r = do@@ -127,7 +137,7 @@ expandDefine :: Resource -> InterpreterMonad [Resource] expandDefine r = let modulename = getModulename (r ^. rid)- in isIgnoredModule modulename >>= \case+ in isIgnoredModule modulename >>= \case True -> pure mempty False -> do let deftype = dropInitialColons (r ^. rid . itype)@@ -146,11 +156,11 @@ (spurious, stmt) <- interpretTopLevel TopDefine deftype DefineDecl _ defineParams stmts cp <- extractPrism "expandDefine" _DefineDecl stmt let isImported (ContImported _) = True- isImported _ = False+ isImported _ = False isImportedDefine <- isImported <$> getScope curPos .= r ^. rpos curscp <- getScope- when isImportedDefine (pushScope (ContImport (r ^. rnode) curscp ))+ when isImportedDefine (pushScope (ContImport (r ^. rnode) curscp)) pushScope curContType loadVariable "title" (PString defname) loadVariable "name" (PString defname)@@ -212,16 +222,17 @@ -- Classify sorts resources between exported and normal ones. It -- drops virtual resources, and puts in both categories resources -- that are at the same time exported and realized.- classify :: Pair (HashMap RIdentifier Resource) (HashMap RIdentifier Resource)- -> Resource- -> Pair (HashMap RIdentifier Resource) (HashMap RIdentifier Resource)+ classify ::+ Pair (HashMap RIdentifier Resource) (HashMap RIdentifier Resource) ->+ Resource ->+ Pair (HashMap RIdentifier Resource) (HashMap RIdentifier Resource) classify (curr :!: cure) r = let i curm = curm & at (r ^. rid) ?~ r- in case r ^. rvirtuality of- Normal -> i curr :!: cure- Exported -> curr :!: i cure+ in case r ^. rvirtuality of+ Normal -> i curr :!: cure+ Exported -> curr :!: i cure ExportedRealized -> i curr :!: i cure- _ -> curr :!: cure+ _ -> curr :!: cure verified <- Map.fromList . map (\r -> (r ^. rid, r)) <$> mapM validateNativeType (Map.elems real) withResourceDependentRelations <- traverse getResourceDependentRelations verified edgemap <- makeEdgeMap withResourceDependentRelations@@ -237,12 +248,12 @@ -- are defined. getResourceDependentRelations :: Resource -> InterpreterMonad Resource getResourceDependentRelations res =- extract- $ case res ^. rid . itype of+ extract $+ case res ^. rid . itype of "file" -> [depOn "user" "owner", depOn "group" "group"] "cron" -> [depOn "user" "user"] "exec" -> [depOn "user" "user", depOn "group" "group"]- _ -> []+ _ -> [] where extract actions = do newrelations <- fmap (foldl' (Map.unionWith (<>)) (res ^. rrelations)) (sequence actions)@@ -261,81 +272,81 @@ makeEdgeMap :: FinalCatalog -> InterpreterMonad EdgeMap makeEdgeMap ct = do- -- merge the loaded classes and resources- defs' <- fmap (view rpos) <$> use definedResources- clss' <- use loadedClasses- let defs = defs' <> classes' <> aliases' <> names'- names' = (view rpos) <$> ct- -- generate fake resources for all extra aliases- aliases' = ifromList $ do- r <- ct ^.. traversed :: [Resource]- extraAliases <- r ^.. ralias . folded . filtered (/= r ^. rid . iname) :: [Text]- pure (r ^. rid & iname .~ extraAliases, r ^. rpos)- classes' = ifromList $ do- (cn, _ :!: cp) <- itoList clss'- pure (RIdentifier "class" cn, cp)- -- Preparation step : all relations to a container become relations to- -- the stuff that's contained. We build a map of resources, stored by- -- container.- -- step 1 - add relations that are stored in resources- let reorderlink :: (RIdentifier, RIdentifier, LinkType) -> (RIdentifier, RIdentifier, LinkType)- reorderlink (s, d, RRequire) = (d, s, RBefore)- reorderlink (s, d, RSubscribe) = (d, s, RNotify)- reorderlink x = x- addRR curmap r = iunionWith (<>) curmap newmap- where- -- compute the explicit resources, along with the container relationship- newmap = ifromListWith (<>) resresources- resid = r ^. rid- respos = r ^. rpos- resresources :: [(RIdentifier, [LinkInformation])]- resresources = do- (rawdst, lts) <- itoList (r ^. rrelations)- lt <- toList lts- let (nsrc, ndst, nlt) = reorderlink (resid, rawdst, lt)- pure (nsrc, [LinkInformation nsrc ndst nlt respos])- step1 :: HashMap RIdentifier [LinkInformation]- step1 = foldl' addRR mempty ct- -- step 2 - add other relations (mainly stuff made from the "->"- -- operator)- let realign (LinkInformation s d t p) =- let (ns, nd, nt) = reorderlink (s, d, t)- in (ns, [LinkInformation ns nd nt p])- rels <- map realign <$> use extraRelations- let step2 = iunionWith (<>) step1 (ifromList rels)- -- check that all resources are defined, and build graph- let checkResDef :: (RIdentifier, [LinkInformation]) -> InterpreterMonad (RIdentifier, RIdentifier, [RIdentifier])- checkResDef (ri, lifs) = do- let checkExists r msg = do- let modulename = getModulename r- is_ignored <- isIgnoredModule modulename- unless (has (ix r) defs || is_ignored) (throwPosError msg)- errmsg = "Unknown resource" <+> pretty ri <+> "used in the following relationships:" <+> vcat (map pretty lifs)- checkExists ri errmsg- let genlnk :: LinkInformation -> InterpreterMonad RIdentifier- genlnk lif = do- let d = lif ^. linkdst- checkExists d ("Unknown resource" <+> pretty d <+> "used in a relation at" <+> showPPos (lif ^. linkPos))- pure d- ds <- mapM genlnk lifs- pure (ri, ri, ds)- edgeList <- mapM checkResDef (itoList step2)- let (graph, gresolver) = Graph.graphFromEdges' edgeList- -- now check for scc- let sccs = filter ((>1) . length . Tree.flatten) (Graph.scc graph)- unless (null sccs) $ do- let trees = vcat (map showtree sccs)- showtree = indent 2 . vcat . map (mkp . gresolver) . Tree.flatten- mkp (a,_,links) = resdesc <+> lnks- where- resdesc = case ct ^. at a of- Just r -> pretty r- _ -> pretty a- lnks = pretty links- throwPosError $ "Dependency error, the following resources are strongly connected!" </> trees- -- let edgePairs = concatMap (\(_,k,ls) -> [(k,l) | l <- ls]) edgeList- -- throwPosError (vcat (map (\(RIdentifier st sn, RIdentifier dt dn) -> "\"" <> pretty st <> ttext sn <> "\" -> \"" <> ttext dt <> ttext dn <> "\"") edgePairs))- pure step2+ -- merge the loaded classes and resources+ defs' <- fmap (view rpos) <$> use definedResources+ clss' <- use loadedClasses+ let defs = defs' <> classes' <> aliases' <> names'+ names' = view rpos <$> ct+ -- generate fake resources for all extra aliases+ aliases' = ifromList $ do+ r <- ct ^.. traversed :: [Resource]+ extraAliases <- r ^.. ralias . folded . filtered (/= r ^. rid . iname) :: [Text]+ pure (r ^. rid & iname .~ extraAliases, r ^. rpos)+ classes' = ifromList $ do+ (cn, _ :!: cp) <- itoList clss'+ pure (RIdentifier "class" cn, cp)+ -- Preparation step : all relations to a container become relations to+ -- the stuff that's contained. We build a map of resources, stored by+ -- container.+ -- step 1 - add relations that are stored in resources+ let reorderlink :: (RIdentifier, RIdentifier, LinkType) -> (RIdentifier, RIdentifier, LinkType)+ reorderlink (s, d, RRequire) = (d, s, RBefore)+ reorderlink (s, d, RSubscribe) = (d, s, RNotify)+ reorderlink x = x+ addRR curmap r = iunionWith (<>) curmap newmap+ where+ -- compute the explicit resources, along with the container relationship+ newmap = ifromListWith (<>) resresources+ resid = r ^. rid+ respos = r ^. rpos+ resresources :: [(RIdentifier, [LinkInformation])]+ resresources = do+ (rawdst, lts) <- itoList (r ^. rrelations)+ lt <- toList lts+ let (nsrc, ndst, nlt) = reorderlink (resid, rawdst, lt)+ pure (nsrc, [LinkInformation nsrc ndst nlt respos])+ step1 :: HashMap RIdentifier [LinkInformation]+ step1 = foldl' addRR mempty ct+ -- step 2 - add other relations (mainly stuff made from the "->"+ -- operator)+ let realign (LinkInformation s d t p) =+ let (ns, nd, nt) = reorderlink (s, d, t)+ in (ns, [LinkInformation ns nd nt p])+ rels <- map realign <$> use extraRelations+ let step2 = iunionWith (<>) step1 (ifromList rels)+ -- check that all resources are defined, and build graph+ let checkResDef :: (RIdentifier, [LinkInformation]) -> InterpreterMonad (RIdentifier, RIdentifier, [RIdentifier])+ checkResDef (ri, lifs) = do+ let checkExists r msg = do+ let modulename = getModulename r+ is_ignored <- isIgnoredModule modulename+ unless (has (ix r) defs || is_ignored) (throwPosError msg)+ errmsg = "Unknown resource" <+> pretty ri <+> "used in the following relationships:" <+> vcat (map pretty lifs)+ checkExists ri errmsg+ let genlnk :: LinkInformation -> InterpreterMonad RIdentifier+ genlnk lif = do+ let d = lif ^. linkdst+ checkExists d ("Unknown resource" <+> pretty d <+> "used in a relation at" <+> showPPos (lif ^. linkPos))+ pure d+ ds <- mapM genlnk lifs+ pure (ri, ri, ds)+ edgeList <- mapM checkResDef (itoList step2)+ let (graph, gresolver) = Graph.graphFromEdges' edgeList+ -- now check for scc+ let sccs = filter ((> 1) . length . Tree.flatten) (Graph.scc graph)+ unless (null sccs) $ do+ let trees = vcat (map showtree sccs)+ showtree = indent 2 . vcat . map (mkp . gresolver) . Tree.flatten+ mkp (a, _, links) = resdesc <+> lnks+ where+ resdesc = case ct ^. at a of+ Just r -> pretty r+ _ -> pretty a+ lnks = pretty links+ throwPosError $ "Dependency error, the following resources are strongly connected!" </> trees+ -- let edgePairs = concatMap (\(_,k,ls) -> [(k,l) | l <- ls]) edgeList+ -- throwPosError (vcat (map (\(RIdentifier st sn, RIdentifier dt dn) -> "\"" <> pretty st <> ttext sn <> "\" -> \"" <> ttext dt <> ttext dn <> "\"") edgePairs))+ pure step2 -- This functions performs all the actions triggered by calls to the -- realize function or other collectors. It returns a pair of@@ -355,18 +366,19 @@ let filtrd = curmap ^.. folded . filtered fmod -- all the resources that match the selector/realize criteria vcheck f r = f (r ^. rvirtuality) (isGoodvirtuality, alterVirtuality) = case rmod ^. rmType of- RealizeVirtual -> (vcheck (/= Exported), \r -> pure (r & rvirtuality .~ Normal))- RealizeCollected -> (vcheck (`elem` [Exported, ExportedRealized]), \r -> pure (r & rvirtuality .~ ExportedRealized))- DontRealize -> (vcheck (`elem` [Normal, ExportedRealized]), pure)+ RealizeVirtual -> (vcheck (/= Exported), \r -> pure (r & rvirtuality .~ Normal))+ RealizeCollected -> (vcheck (`elem` [Exported, ExportedRealized]), \r -> pure (r & rvirtuality .~ ExportedRealized))+ DontRealize -> (vcheck (`elem` [Normal, ExportedRealized]), pure) fmod r = (r ^. rid . itype == rmod ^. rmResType) && checkSearchExpression (rmod ^. rmSearch) r && isGoodvirtuality r mutation = alterVirtuality >=> rmod ^. rmMutation applyModification :: Pair FinalCatalog FinalCatalog -> Resource -> InterpreterMonad (Pair FinalCatalog FinalCatalog) applyModification (cma :!: cmo) r = do- nr <- mutation r- let i m = m & at (nr ^. rid) ?~ nr- pure $ if nr /= r- then i cma :!: i cmo- else cma :!: cmo+ nr <- mutation r+ let i m = m & at (nr ^. rid) ?~ nr+ pure $+ if nr /= r+ then i cma :!: i cmo+ else cma :!: cmo result <- foldM applyModification (curmap :!: modified) filtrd -- apply the modifiation to all the matching resources when (rmod ^. rmModifierType == ModifierMustMatch && null filtrd) (throwError (PrettyError ("Could not apply this resource override :" <+> pretty rmod <> ",no matching resource was found."))) pure result@@ -408,14 +420,15 @@ evaluateStatement :: Statement -> InterpreterMonad [Resource] evaluateStatement r@(ClassDeclaration (ClassDecl cname _ _ _ _)) = if "::" `Text.isInfixOf` cname- then nestedDeclarations . at (TopClass, cname) ?= r >> pure []- else do- scp <- getScopeName- let rcname = if scp == "::"- then cname- else scp <> "::" <> cname- nestedDeclarations . at (TopClass, rcname) ?= r- pure []+ then nestedDeclarations . at (TopClass, cname) ?= r >> pure []+ else do+ scp <- getScopeName+ let rcname =+ if scp == "::"+ then cname+ else scp <> "::" <> cname+ nestedDeclarations . at (TopClass, rcname) ?= r+ pure [] evaluateStatement r@(DefineDeclaration (DefineDecl dname _ _ _)) = if "::" `Text.isInfixOf` dname then nestedDeclarations . at (TopDefine, dname) ?= r >> pure []@@ -426,14 +439,15 @@ else nestedDeclarations . at (TopDefine, scp <> "::" <> dname) ?= r >> pure [] evaluateStatement r@(ResourceCollectionDeclaration (ResCollDecl ct rtype searchexp mods p)) = do curPos .= p- unless (null mods || ct == Collector)+ unless+ (null mods || ct == Collector) (throwPosError ("It doesn't seem possible to amend attributes with an exported resource collector:" </> pretty r)) when (rtype == "class") (throwPosError "Classes cannot be collected") rsearch <- resolveSearchExpression searchexp let et = case ct of- Collector -> RealizeVirtual+ Collector -> RealizeVirtual ExportedCollector -> RealizeCollected- resModifiers %= (ResourceModifier rtype ModifierCollector et rsearch (\r' -> foldM modifyCollectedAttribute r' mods) p : )+ resModifiers %= (ResourceModifier rtype ModifierCollector et rsearch (\r' -> foldM modifyCollectedAttribute r' mods) p :) -- Now collected from the PuppetDB ! if et == RealizeCollected then do@@ -441,10 +455,13 @@ fqdn <- getNodeName -- we must filter the resources that originated from this host -- here ! They are also turned into "normal" resources- res <- toListOf (folded- . filtered ( hasn't (rnode . only fqdn) )- . to (rvirtuality .~ Normal)- ) <$> singleton (PDBGetResources q)+ res <-+ toListOf+ ( folded+ . filtered (hasn't (rnode . only fqdn))+ . to (rvirtuality .~ Normal)+ )+ <$> singleton (PDBGetResources q) scpdesc <- ContImported <$> getScope void $ enterScope SENormal scpdesc "importing" p pushScope scpdesc@@ -456,7 +473,7 @@ curPos .= p rn1 <- map (fixResourceName t1) <$> resolveExpressionStrings n1 rn2 <- map (fixResourceName t2) <$> resolveExpressionStrings n2- extraRelations <>= [ LinkInformation (normalizeRIdentifier t1 an1) (normalizeRIdentifier t2 an2) lt p | an1 <- rn1, an2 <- rn2 ]+ extraRelations <>= [LinkInformation (normalizeRIdentifier t1 an1) (normalizeRIdentifier t2 an2) lt p | an1 <- rn1, an2 <- rn2] pure [] evaluateStatement (ResourceDeclaration (ResDecl t ern eargs virt p)) = do curPos .= p@@ -470,7 +487,7 @@ curPos .= p varval <- resolveExpression varexpr mapM_ (resolveDataType >=> (`checkMatch` varval)) mt- mapM_ (flip loadVariable varval) varnames+ mapM_ (`loadVariable` varval) varnames pure [] evaluateStatement (ConditionalDeclaration (ConditionalDecl conds p)) = do curPos .= p@@ -483,22 +500,23 @@ else restoreCaptureVariables sv *> checkCond xs checkCond (toList conds) evaluateStatement (ResourceDefaultDeclaration (ResDefaultDecl rtype decls p)) = do- curPos .= p- rdecls <- fromAttributeDecls decls- scp <- getScopeName- -- invariant that must be respected : the current scope must be created- -- in "scopes", or nothing gets saved- preuse (scopes . ix scp) >>= maybe (throwPosError ("INTERNAL ERROR in evaluateStatement ResourceDefaultDeclaration: scope wasn't created - " <> ppline scp)) (const (pure ()))- let newDefaults = ResDefaults rtype scp rdecls p- addDefaults x = scopes . ix scp . scopeResDefaults . at rtype ?= x- -- default merging with parent- mergedDefaults curdef = newDefaults & resDefValues .~ (rdecls <> (curdef ^. resDefValues))- preuse (scopes . ix scp . scopeResDefaults . ix rtype) >>= \case- Nothing -> addDefaults newDefaults- Just d -> if d ^. resDefSrcScope == scp- then throwPosError ("Defaults for resource" <+> ppline rtype <+> "already declared at" <+> showPPos (d ^. resDefPos))- else addDefaults (mergedDefaults d)- pure []+ curPos .= p+ rdecls <- fromAttributeDecls decls+ scp <- getScopeName+ -- invariant that must be respected : the current scope must be created+ -- in "scopes", or nothing gets saved+ preuse (scopes . ix scp) >>= maybe (throwPosError ("INTERNAL ERROR in evaluateStatement ResourceDefaultDeclaration: scope wasn't created - " <> ppline scp)) (const (pure ()))+ let newDefaults = ResDefaults rtype scp rdecls p+ addDefaults x = scopes . ix scp . scopeResDefaults . at rtype ?= x+ -- default merging with parent+ mergedDefaults curdef = newDefaults & resDefValues .~ (rdecls <> (curdef ^. resDefValues))+ preuse (scopes . ix scp . scopeResDefaults . ix rtype) >>= \case+ Nothing -> addDefaults newDefaults+ Just d ->+ if d ^. resDefSrcScope == scp+ then throwPosError ("Defaults for resource" <+> ppline rtype <+> "already declared at" <+> showPPos (d ^. resDefPos))+ else addDefaults (mergedDefaults d)+ pure [] evaluateStatement (ResourceOverrideDeclaration (ResOverrideDecl t urn eargs p)) = do curPos .= p raassignements <- fromAttributeDecls eargs@@ -510,7 +528,8 @@ withAssignements <- case curoverrides ^. at rident of Just (ResRefOverride _ prevass prevpos) -> do let cm = prevass `Map.intersection` raassignements- unless (null cm)+ unless+ (null cm) (throwPosError ("The following parameters were already overriden at" <+> showPPos prevpos <+> ":" <+> pretty cm)) pure (prevass <> raassignements) Nothing -> pure raassignements@@ -525,9 +544,9 @@ runblock assocs = do saved <- hfSetvars assocs res <- evaluateStatementsFoldable (hf ^. hoLambdaStatements)- hfRestorevars saved+ hfRestorevars saved pure res- in case hf ^. hoLambdaFunc of+ in case hf ^. hoLambdaFunc of LambdaFunc "each" -> do varassocs <- hfGenerateAssociations hf concat <$> mapM runblock varassocs@@ -535,15 +554,15 @@ -- run the inner code in this scope LambdaFunc "assert_type" -> case (hf ^.. hoLambdaExpr . folded, hf ^.. hoLambdaParams . folded) of- ( [utp, uval], [a, b] ) -> do- let typecheck_lambda (LambdaParam ltype lvar)- = case ltype of- Nothing -> pure lvar- Just udt -> do- dt <- resolveDataType udt- if dt == DTType- then pure lvar- else throwPosError ("The lambda value can only be a type in assert_type, not" <+> pretty dt)+ ([utp, uval], [a, b]) -> do+ let typecheck_lambda (LambdaParam ltype lvar) =+ case ltype of+ Nothing -> pure lvar+ Just udt -> do+ dt <- resolveDataType udt+ if dt == DTType+ then pure lvar+ else throwPosError ("The lambda value can only be a type in assert_type, not" <+> pretty dt) mtp <- resolveExpression utp val <- resolveExpression uval varexpected <- typecheck_lambda a@@ -558,7 +577,8 @@ LambdaFunc "with" -> do let expressions = hf ^. hoLambdaExpr parameters = hf ^. hoLambdaParams- unless (V.length expressions == V.length parameters)+ unless+ (V.length expressions == V.length parameters) (throwPosError ("Mismatched number of arguments and lambda parameters in" <> pretty hf)) assocs <- forM (V.zip expressions parameters) $ \(uval, LambdaParam mt name) -> do val <- resolveExpression uval@@ -575,7 +595,7 @@ -- Class evaluation ----------------------------------------------------------- -loadVariable :: Text -> PValue -> InterpreterMonad ()+loadVariable :: Text -> PValue -> InterpreterMonad () loadVariable varname varval = do curcont <- getCurContainer scp <- getScopeName@@ -584,46 +604,57 @@ variableDefined <- preuse (scopes . ix scp . scopeVariables . ix varname) case (scopeDefined, variableDefined) of (False, _) -> throwPosError ("Internal error: trying to save a variable in unknown scope" <+> ppline scp)- (_, Just (_ :!: pp :!: ctx)) -> isParent scp (curcont ^. cctype) >>= \case- True -> do- debug("The variable"- <+> pretty (UVariableReference varname)- <+> "had been overriden because of some arbitrary inheritance rule that was set up to emulate puppet behaviour. It was defined at"- <+> showPPos pp- )- scopes . ix scp . scopeVariables . at varname ?= (varval :!: p :!: curcont ^. cctype)- False -> throwPosError ("Variable" <+> pretty (UVariableReference varname) <+> "already defined at" <+> showPPos pp- </> "Context:" <+> pretty ctx- </> "Value:" <+> pretty varval- </> "Current scope:" <+> ppline scp- )- _ -> scopes . ix scp . scopeVariables . at varname ?= (varval :!: p :!: curcont ^. cctype)+ (_, Just ((_ :!: pp) :!: ctx)) ->+ isParent scp (curcont ^. cctype) >>= \case+ True -> do+ debug+ ( "The variable"+ <+> pretty (UVariableReference varname)+ <+> "had been overriden because of some arbitrary inheritance rule that was set up to emulate puppet behaviour. It was defined at"+ <+> showPPos pp+ )+ scopes . ix scp . scopeVariables . at varname ?= ((varval :!: p) :!: curcont ^. cctype)+ False ->+ throwPosError+ ( "Variable"+ <+> pretty (UVariableReference varname)+ <+> "already defined at"+ <+> showPPos pp+ </> "Context:"+ <+> pretty ctx+ </> "Value:"+ <+> pretty varval+ </> "Current scope:"+ <+> ppline scp+ )+ _ -> scopes . ix scp . scopeVariables . at varname ?= ((varval :!: p) :!: curcont ^. cctype) -- This function loads class and define parameters into scope. It checks -- that all mandatory parameters are set, that no extra parameter is declared. -- -- It is able to fill unset parameters with values from Hiera (for classes only) or default values.-loadParameters :: Container PValue -- Resource attributes (resolved)- -> Parameters -- List of parameters as declared (unresolved)- -> PPosition -- Current position- -> Maybe Text -- class name- -> InterpreterMonad ()+loadParameters ::+ Container PValue -> -- Resource attributes (resolved)+ Parameters -> -- List of parameters as declared (unresolved)+ PPosition -> -- Current position+ Maybe Text -> -- class name+ InterpreterMonad () loadParameters attrs classParams defaultPos classname = do p <- use curPos curPos .= defaultPos- let class_params = Set.fromList (classParams ^.. folded . _1 . _1)- spurious_params = ikeys attrs `Set.difference` class_params- pp_classdesc = maybe mempty (\x -> " when including class" <+> ppline x) classname+ let class_params = Set.fromList (classParams ^.. folded . _1 . _1)+ spurious_params = ikeys attrs `Set.difference` class_params+ pp_classdesc = maybe mempty (\x -> " when including class" <+> ppline x) classname -- the following functions `throwE (Max False)` when there is no value, and `throwE (Max True)` when this value in PUndef. check_undef :: S.Maybe UDataType -> Maybe PValue -> ExceptT (Max Bool) InterpreterMonad PValue check_undef (S.Just (UDTOptional _)) Nothing = throwE (Max True) check_undef _ Nothing = throwE (Max False) check_undef _ (Just PUndef) = throwE (Max True)- check_undef _ (Just v) = pure v+ check_undef _ (Just v) = pure v check_hiera :: Text -> S.Maybe UDataType -> ExceptT (Max Bool) InterpreterMonad PValue- check_hiera k dt = case classname of+ check_hiera k dt = case classname of Nothing -> throwE (Max False) Just n -> lift (runHiera (n <> "::" <> k) QFirst) >>= check_undef dt @@ -631,27 +662,27 @@ check_def k = check_undef S.Nothing (attrs ^. at k) check_default :: S.Maybe Expression -> ExceptT (Max Bool) InterpreterMonad PValue- check_default S.Nothing = throwE (Max False)+ check_default S.Nothing = throwE (Max False) check_default (S.Just expr) = lift (resolveExpression expr) - unless (null spurious_params)- $ throwPosError ("The following parameters are unknown:" <+> tupled (map (dullyellow . ppline) $ toList spurious_params) <> pp_classdesc)+ unless (null spurious_params) $+ throwPosError ("The following parameters are unknown:" <+> tupled (map (dullyellow . ppline) $ toList spurious_params) <> pp_classdesc) -- try to set a value to all parameters -- The order of evaluation is defined / hiera / default- unset_params <- fmap concat $ for classParams $ \(varname :!: vartype :!: valexpr) ->- runExceptT (check_def varname <|> check_hiera varname vartype <|> check_default valexpr) >>= \case- Right val -> do- forM_ vartype $ \utype -> do- dt <- resolveDataType utype- unless (datatypeMatch dt val)- $ throwPosError ("Expected type" <+> pretty dt <+> "for parameter" <+> ppline varname <+> "but its value was:" <+> pretty val)- loadVariable varname val >> pure []- Left (Max True) -> loadVariable varname PUndef >> pure []- Left (Max False) -> pure [varname]+ unset_params <- fmap concat $ for classParams $ \((varname :!: vartype) :!: valexpr) ->+ runExceptT (check_def varname <|> check_hiera varname vartype <|> check_default valexpr) >>= \case+ Right val -> do+ forM_ vartype $ \utype -> do+ dt <- resolveDataType utype+ unless (datatypeMatch dt val) $+ throwPosError ("Expected type" <+> pretty dt <+> "for parameter" <+> ppline varname <+> "but its value was:" <+> pretty val)+ loadVariable varname val >> pure []+ Left (Max True) -> loadVariable varname PUndef >> pure []+ Left (Max False) -> pure [varname] curPos .= p- unless (null unset_params)- $ throwPosError ("The following mandatory parameters were not set:" <+> tupled (map ppline $ toList unset_params) <> pp_classdesc)+ unless (null unset_params) $+ throwPosError ("The following mandatory parameters were not set:" <+> tupled (map ppline $ toList unset_params) <> pp_classdesc) -- Enters a new scope, checks it is not already defined, and inherits the -- defaults from the current scope@@ -659,49 +690,52 @@ -- Inheriting the defaults is necessary for non native types, because they -- will be expanded in 'finalize', so if this was not done, we would be -- expanding the defines without the defaults applied-enterScope :: ScopeEnteringContext- -> CurContainerDesc- -> Text- -> PPosition- -> InterpreterMonad Text+enterScope ::+ ScopeEnteringContext ->+ CurContainerDesc ->+ Text ->+ PPosition ->+ InterpreterMonad Text enterScope secontext cont modulename p = do let scopename = scopeName cont -- This is a special hack for inheritance, because at this time we -- have not properly stacked the scopes. curcaller <- case secontext of- SEParent l -> pure (PString $ Text.takeWhile (/=':') l)- _ -> resolveVariable "module_name"+ SEParent l -> pure (PString $ Text.takeWhile (/= ':') l)+ _ -> resolveVariable "module_name" scopeAlreadyDefined <- has (ix scopename) <$> use scopes let isImported = case cont of- ContImported _ -> True- _ -> False+ ContImported _ -> True+ _ -> False -- it is OK to reuse a scope related to imported stuff unless (scopeAlreadyDefined && isImported) $ do- when scopeAlreadyDefined (throwPosError ("Internal error: scope" <+> brackets (ppline scopename) <+> "already defined when loading scope for" <+> pretty cont))- scp <- getScopeName- -- TODO fill tags- basescope <- case secontext of- SEChild prt -> do- parentscope <- use (scopes . at prt)- when (isNothing parentscope) (throwPosError ("Internal error: could not find parent scope" <+> ppline prt))- let Just psc = parentscope- pure (psc & scopeParent .~ S.Just prt)- _ -> do- curdefs <- use (scopes . ix scp . scopeResDefaults)- pure $ ScopeInformation mempty curdefs mempty (CurContainer cont mempty) mempty S.Nothing- scopes . at scopename ?= basescope- scopes . ix scopename . scopeVariables . at "caller_module_name" ?= (curcaller :!: p :!: cont)- scopes . ix "::" . scopeVariables . at "calling_module" ?= (curcaller :!: p :!: cont)- scopes . ix scopename . scopeVariables . at "module_name" ?= (PString modulename :!: p :!: cont)+ when scopeAlreadyDefined (throwPosError ("Internal error: scope" <+> brackets (ppline scopename) <+> "already defined when loading scope for" <+> pretty cont))+ scp <- getScopeName+ -- TODO fill tags+ basescope <- case secontext of+ SEChild prt -> do+ parentscope <- use (scopes . at prt)+ psc <- case parentscope of+ Just x -> pure x+ Nothing -> throwPosError ("Internal error: could not find parent scope" <+> ppline prt)+ pure (psc & scopeParent .~ S.Just prt)+ _ -> do+ curdefs <- use (scopes . ix scp . scopeResDefaults)+ pure $ ScopeInformation mempty curdefs mempty (CurContainer cont mempty) mempty S.Nothing+ scopes . at scopename ?= basescope+ scopes . ix scopename . scopeVariables . at "caller_module_name" ?= ((curcaller :!: p) :!: cont)+ scopes . ix "::" . scopeVariables . at "calling_module" ?= ((curcaller :!: p) :!: cont)+ scopes . ix scopename . scopeVariables . at "module_name" ?= ((PString modulename :!: p) :!: cont) debug ("enterScope, scopename=" <> ppline scopename <+> "caller_module_name=" <> pretty curcaller <+> "module_name=" <> ppline modulename) pure scopename -- Instantiate/declare a class-loadClass :: Text- -> S.Maybe Text -- Set if this is an inheritance load, so that we can set calling module properly- -> Container PValue -- Resource attributes- -> ClassIncludeType- -> InterpreterMonad [Resource]+loadClass ::+ Text ->+ S.Maybe Text -> -- Set if this is an inheritance load, so that we can set calling module properly+ Container PValue -> -- Resource attributes+ ClassIncludeType ->+ InterpreterMonad [Resource] loadClass name loadedfrom attrs incltype = do let name' = dropInitialColons name nodename <- getNodeName@@ -712,45 +746,49 @@ preuse (loadedClasses . ix name' . _2) >>= \case Just pp -> case incltype of ClassIncludeLike -> pure []- _ -> throwPosError- $ "Can't include class" <+> ppline name' <+> "twice when using the resource-like syntax (first occurence at"- <+> showPPos pp <> ")"+ _ ->+ throwPosError $+ "Can't include class"+ <+> ppline name'+ <+> "twice when using the resource-like syntax (first occurence at"+ <+> showPPos pp <> ")" Nothing -> do- loadedClasses . at name' ?= (incltype :!: pos) -- set the position of the loaded class- let modulename = getModulename (RIdentifier "class" name')- is_ignored <- isIgnoredModule modulename- if is_ignored- then pure mempty- else do- -- load the actual class, note we are not changing the current position right now- (spurious, stmt) <- interpretTopLevel TopClass name'- ClassDecl _ params inh stmts curpos <- extractPrism "loadClass" _ClassDecl stmt- -- check if we need to define a resource representing the class- -- This will be the case for the first standard include- inhstmts <- case inh of- S.Nothing -> pure []- S.Just ihname -> loadClass ihname (S.Just name') mempty ClassIncludeLike- let !scopedesc = ContClass name'- secontext = case (inh, loadedfrom) of- (S.Just x,_) -> SEChild (dropInitialColons x)- (_,S.Just x) -> SEParent (dropInitialColons x)- _ -> SENormal- void $ enterScope secontext scopedesc modulename pos- classresource <- if incltype == ClassIncludeLike- then do- scp <- use curScope- fqdn <- getNodeName- pure [Resource (RIdentifier "class" name') (Set.singleton name') mempty mempty scp Normal mempty pos fqdn]- else pure []- pushScope scopedesc- loadVariable "title" (PString name')- loadVariable "name" (PString name')- loadParameters attrs params curpos (Just name')- curPos .= curpos- res <- evaluateStatementsFoldable stmts- out <- finalize (classresource <> spurious <> inhstmts <> res)- popScope- pure out+ loadedClasses . at name' ?= (incltype :!: pos) -- set the position of the loaded class+ let modulename = getModulename (RIdentifier "class" name')+ is_ignored <- isIgnoredModule modulename+ if is_ignored+ then pure mempty+ else do+ -- load the actual class, note we are not changing the current position right now+ (spurious, stmt) <- interpretTopLevel TopClass name'+ ClassDecl _ params inh stmts curpos <- extractPrism "loadClass" _ClassDecl stmt+ -- check if we need to define a resource representing the class+ -- This will be the case for the first standard include+ inhstmts <- case inh of+ S.Nothing -> pure []+ S.Just ihname -> loadClass ihname (S.Just name') mempty ClassIncludeLike+ let !scopedesc = ContClass name'+ secontext = case (inh, loadedfrom) of+ (S.Just x, _) -> SEChild (dropInitialColons x)+ (_, S.Just x) -> SEParent (dropInitialColons x)+ _ -> SENormal+ void $ enterScope secontext scopedesc modulename pos+ classresource <-+ if incltype == ClassIncludeLike+ then do+ scp <- use curScope+ fqdn <- getNodeName+ pure [Resource (RIdentifier "class" name') (Set.singleton name') mempty mempty scp Normal mempty pos fqdn]+ else pure []+ pushScope scopedesc+ loadVariable "title" (PString name')+ loadVariable "name" (PString name')+ loadParameters attrs params curpos (Just name')+ curPos .= curpos+ res <- evaluateStatementsFoldable stmts+ out <- finalize (classresource <> spurious <> inhstmts <> res)+ popScope+ pure out ----------------------------------------------------------- -- Resource stuff@@ -768,23 +806,23 @@ addTagResource r rv = r & rtags . contains rv .~ True addAttribute :: OverrideType -> Text -> Resource -> PValue -> InterpreterMonad Resource-addAttribute _ "alias" r v = (\rv -> r & ralias . contains rv .~ True) <$> resolvePValueString v-addAttribute _ "audit" r _ = use curPos >>= \p -> warn ("Metaparameter audit ignored at" <+> showPPos p) >> pure r-addAttribute _ "loglevel" r _ = use curPos >>= \p -> warn ("Metaparameter loglevel ignored at" <+> showPPos p) >> pure r-addAttribute _ "schedule" r _ = use curPos >>= \p -> warn ("Metaparameter schedule ignored at" <+> showPPos p) >> pure r-addAttribute _ "stage" r _ = use curPos >>= \p -> warn ("Metaparameter stage ignored at" <+> showPPos p) >> pure r-addAttribute _ "tag" r (PArray v) = foldM (\cr cv -> addTagResource cr <$> resolvePValueString cv) r (toList v)-addAttribute _ "tag" r v = addTagResource r <$> resolvePValueString v-addAttribute _ "before" r d = addRelationship RBefore d r-addAttribute _ "notify" r d = addRelationship RNotify d r-addAttribute _ "require" r d = addRelationship RRequire d r+addAttribute _ "alias" r v = (\rv -> r & ralias . contains rv .~ True) <$> resolvePValueString v+addAttribute _ "audit" r _ = use curPos >>= \p -> warn ("Metaparameter audit ignored at" <+> showPPos p) >> pure r+addAttribute _ "loglevel" r _ = use curPos >>= \p -> warn ("Metaparameter loglevel ignored at" <+> showPPos p) >> pure r+addAttribute _ "schedule" r _ = use curPos >>= \p -> warn ("Metaparameter schedule ignored at" <+> showPPos p) >> pure r+addAttribute _ "stage" r _ = use curPos >>= \p -> warn ("Metaparameter stage ignored at" <+> showPPos p) >> pure r+addAttribute _ "tag" r (PArray v) = foldM (\cr cv -> addTagResource cr <$> resolvePValueString cv) r (toList v)+addAttribute _ "tag" r v = addTagResource r <$> resolvePValueString v+addAttribute _ "before" r d = addRelationship RBefore d r+addAttribute _ "notify" r d = addRelationship RNotify d r+addAttribute _ "require" r d = addRelationship RRequire d r addAttribute _ "subscribe" r d = addRelationship RSubscribe d r addAttribute b t r v = go t r v where go = case b of- CantOverride -> setAttribute- Replace -> overrideAttribute- CantReplace -> defaultAttribute+ CantOverride -> setAttribute+ Replace -> overrideAttribute+ CantReplace -> defaultAttribute AppendAttribute -> appendAttribute setAttribute :: Text -> Resource -> PValue -> InterpreterMonad Resource@@ -812,18 +850,18 @@ appendAttribute :: Text -> Resource -> PValue -> InterpreterMonad Resource appendAttribute attributename res value = do nvalue <- case (res ^. rattributes . at attributename, value) of- (Nothing, _) -> pure value+ (Nothing, _) -> pure value (Just (PArray a), PArray b) -> pure (PArray (a <> b))- (Just (PArray a), b) -> pure (PArray (V.snoc a b))- (Just a, PArray b) -> pure (PArray (V.cons a b))- (Just a, b) -> pure (PArray (V.fromList [a,b]))+ (Just (PArray a), b) -> pure (PArray (V.snoc a b))+ (Just a, PArray b) -> pure (PArray (V.cons a b))+ (Just a, b) -> pure (PArray (V.fromList [a, b])) pure (res & rattributes . at attributename ?~ nvalue) defaultAttribute :: Text -> Resource -> PValue -> InterpreterMonad Resource defaultAttribute attributename res value = pure $ case res ^. rattributes . at attributename of Nothing -> res & rattributes . at attributename ?~ value- Just _ -> res+ Just _ -> res modifyCollectedAttribute :: Resource -> AttributeDecl -> InterpreterMonad Resource modifyCollectedAttribute res attrdecl =@@ -842,7 +880,7 @@ _ -> throwPosError ("A hash was expected, not" <+> pretty resolved) registerResource :: Text -> Text -> Container PValue -> Virtuality -> PPosition -> InterpreterMonad [Resource]-registerResource "class" _ _ Virtual p = curPos .= p >> throwPosError "Cannot declare a virtual class (or perhaps you can, but I do not know what this means)"+registerResource "class" _ _ Virtual p = curPos .= p >> throwPosError "Cannot declare a virtual class (or perhaps you can, but I do not know what this means)" registerResource "class" _ _ Exported p = curPos .= p >> throwPosError "Cannot declare an exported class (or perhaps you can, but I do not know what this means)" registerResource t rn arg vrt p = do curPos .= p@@ -853,11 +891,11 @@ let !defaulttags = {-# SCC "rrGetTags" #-} Set.fromList (t : classtags) <> tgs allsegs x = x : Text.splitOn "::" x (!classtags, !defaultLink) = getClassTags cnt- getClassTags (ContClass cn ) = (allsegs cn,RIdentifier "class" cn)- getClassTags (ContDefine dt dn _) = (allsegs dt,normalizeRIdentifier dt dn)- getClassTags ContRoot = ([],RIdentifier "class" "::")- getClassTags (ContImported _ ) = ([],RIdentifier "class" "::")- getClassTags (ContImport _ _ ) = ([],RIdentifier "class" "::")+ getClassTags (ContClass cn) = (allsegs cn, RIdentifier "class" cn)+ getClassTags (ContDefine dt dn _) = (allsegs dt, normalizeRIdentifier dt dn)+ getClassTags ContRoot = ([], RIdentifier "class" "::")+ getClassTags (ContImported _) = ([], RIdentifier "class" "::")+ getClassTags (ContImport _ _) = ([], RIdentifier "class" "::") defaultRelation = Map.singleton defaultLink (Set.singleton RRequire) allScope <- use curScope fqdn <- getNodeName@@ -865,32 +903,38 @@ r <- ifoldlM (addAttribute CantOverride) baseresource arg let resid = normalizeRIdentifier t rn case t of- "class" -> {-# SCC "rrClass" #-} do- definedResources . at resid ?= r- (r:) <$> loadClass rn S.Nothing (r^.rattributes) ClassResourceLike- _ -> {-# SCC "rrGeneralCase" #-}+ "class" ->+ {-# SCC "rrClass" #-}+ do+ definedResources . at resid ?= r+ (r :) <$> loadClass rn S.Nothing (r ^. rattributes) ClassResourceLike+ _ ->+ {-# SCC "rrGeneralCase" #-} use (definedResources . at resid) >>= \case- Just otheres -> throwPosError- $ "Resource" <+> pretty resid <+> "already defined:"- </> pretty r </> pretty otheres+ Just otheres ->+ throwPosError $+ "Resource"+ <+> pretty resid+ <+> "already defined:"+ </> pretty r+ </> pretty otheres Nothing -> do definedResources . at resid ?= r pure [r] - -- functions : this can't really be exported as it uses a lot of stuff from -- this module ... mainFunctionCall :: Text -> [PValue] -> InterpreterMonad [Resource] mainFunctionCall "showscope" _ = use curScope >>= warn . pretty >> pure [] -- The logging functions-mainFunctionCall "alert" a = logWithModifier Log.ALERT red a-mainFunctionCall "crit" a = logWithModifier Log.CRITICAL red a-mainFunctionCall "debug" a = logWithModifier Log.DEBUG dullwhite a-mainFunctionCall "emerg" a = logWithModifier Log.EMERGENCY red a-mainFunctionCall "err" a = logWithModifier Log.ERROR dullred a-mainFunctionCall "info" a = logWithModifier Log.INFO dullgreen a-mainFunctionCall "notice" a = logWithModifier Log.NOTICE white a-mainFunctionCall "warning" a = logWithModifier Log.WARNING dullyellow a+mainFunctionCall "alert" a = logWithModifier Log.ALERT red a+mainFunctionCall "crit" a = logWithModifier Log.CRITICAL red a+mainFunctionCall "debug" a = logWithModifier Log.DEBUG dullwhite a+mainFunctionCall "emerg" a = logWithModifier Log.EMERGENCY red a+mainFunctionCall "err" a = logWithModifier Log.ERROR dullred a+mainFunctionCall "info" a = logWithModifier Log.INFO dullgreen a+mainFunctionCall "notice" a = logWithModifier Log.NOTICE white a+mainFunctionCall "warning" a = logWithModifier Log.WARNING dullyellow a mainFunctionCall "contain" includes = concat <$> mapM doContain includes where@@ -928,7 +972,7 @@ mainFunctionCall "realize" args = do pos <- use curPos let updateMod (PResourceReference t rn) =- resModifiers %= (ResourceModifier t ModifierMustMatch RealizeVirtual (REqualitySearch "title" (PString rn)) pure pos : )+ resModifiers %= (ResourceModifier t ModifierMustMatch RealizeVirtual (REqualitySearch "title" (PString rn)) pure pos :) updateMod x = throwPosError ("realize(): all arguments must be resource references, not" <+> pretty x) mapM_ updateMod args pure []@@ -937,7 +981,7 @@ let addTag x = scopes . ix scp . scopeExtraTags . contains x .= True mapM_ (resolvePValueString >=> addTag) args pure []-mainFunctionCall "fail" [x] = ("fail:" <+>) . dullred . ppline <$> resolvePValueString x >>= throwPosError+mainFunctionCall "fail" [x] = resolvePValueString x >>= throwPosError . ("fail:" <+>) . dullred . ppline mainFunctionCall "fail" _ = throwPosError "fail(): This function takes a single argument" -- hiera_include does a unique merge lookup for the requested key, then calls the include function on the resulting array. mainFunctionCall "hiera_include" [x] = do@@ -959,7 +1003,7 @@ prntline "Variables in local scope :" scp <- getScopeName vars <- use (scopes . ix scp . scopeVariables)- forM_ (sortOn fst (itoList vars)) $ \(idx, pv :!: _ :!: _) -> prntline $ indentln $ ppline idx <> " -> " <> pretty pv+ forM_ (sortOn fst (itoList vars)) $ \(idx, (pv :!: _) :!: _) -> prntline $ indentln $ ppline idx <> " -> " <> pretty pv pure [] mainFunctionCall "assert_type" [PType dt, v] = if datatypeMatch dt v@@ -975,14 +1019,14 @@ ensurePackages :: [PValue] -> InterpreterMonad [Resource] ensurePackages [packages] = ensurePackages [packages, PHash mempty]-ensurePackages [PString p, x] = ensurePackages [ PArray (V.singleton (PString p)), x ]+ensurePackages [PString p, x] = ensurePackages [PArray (V.singleton (PString p)), x] ensurePackages [PArray packages, PHash defparams] = do checkStrict "The use of the 'ensure_packages' function is a code smell." "The 'ensure_packages' function is not allowed in strict mode." concat <$> for packages (resolvePValueString >=> ensureResource' "package" (Map.singleton "ensure" "present" <> defparams))-ensurePackages [PArray _,_] = throwPosError "ensure_packages(): the second argument must be a hash."-ensurePackages [_,_] = throwPosError "ensure_packages(): the first argument must be a string or an array of strings."+ensurePackages [PArray _, _] = throwPosError "ensure_packages(): the second argument must be a hash."+ensurePackages [_, _] = throwPosError "ensure_packages(): the first argument must be a string or an array of strings." ensurePackages _ = throwPosError "ensure_packages(): requires one or two arguments." -- Takes a resource type, title, and a hash of attributes that describe the resource.@@ -995,7 +1039,7 @@ "The 'ensure_resource' function is not allowed in strict mode." ensureResource' t params title ensureResource [t, PArray arr, params] = concat <$> mapM (\r -> ensureResource [t, r, params]) (V.toList arr)-ensureResource [t,title] = ensureResource [t,title,PHash mempty]+ensureResource [t, title] = ensureResource [t, title, PHash mempty] ensureResource [_, PString _, PHash _] = throwPosError "ensureResource(): The first argument must be a string." ensureResource [PString _, _, PHash _] = throwPosError "ensureResource(): The second argument must be a string." ensureResource [PString _, PString _, _] = throwPosError "ensureResource(): The thrid argument must be a hash."@@ -1008,12 +1052,11 @@ then pure [] else use curPos >>= registerResource t title params Normal - ----------------------------------------------------------- -- Specific utils functions that depends on this modules ----------------------------------------------------------- -evaluateStatementsFoldable :: Foldable f => f Statement -> InterpreterMonad [Resource]+evaluateStatementsFoldable :: (Foldable f) => f Statement -> InterpreterMonad [Resource] evaluateStatementsFoldable = fmap concat . mapM evaluateStatement . toList -- A helper function for the various loggers
src/Puppet/Interpreter/Helpers.hs view
@@ -1,56 +1,58 @@+{-# LANGUAGE RankNTypes #-} {-# OPTIONS_HADDOCK hide, prune, ignore-exports #-}-{-# LANGUAGE RankNTypes #-} -- | Internal helpers module. module Puppet.Interpreter.Helpers where -import XPrelude--import Control.Monad.Operational-import Control.Monad.Writer.Class-import qualified Data.HashMap.Strict as Map-import qualified Data.Vector as Vector-import qualified Data.List as List-import qualified Data.Maybe.Strict as S-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified System.Log.Logger as Log--import Facter-import Puppet.Interpreter.Types-+import Control.Monad.Operational+import Control.Monad.Writer.Class+import qualified Data.HashMap.Strict as Map+import qualified Data.List as List+import qualified Data.Maybe.Strict as S+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Vector as Vector+import Facter+import GHC.Err as Err+import Puppet.Interpreter.Types+import qualified System.Log.Logger as Log+import XPrelude hiding (key) -initialState :: Facts- -> Container Text -- ^ Server settings- -> InterpreterState+initialState ::+ Facts ->+ -- | Server settings+ Container Text ->+ InterpreterState initialState facts settings = InterpreterState baseVars initialclass mempty [ContRoot] (initialPPos mempty) mempty [] [] where- callervars = Map.fromList [("caller_module_name", PString "::" :!: (initialPPos mempty) :!: ContRoot), ("module_name", PString "::" :!: (initialPPos mempty) :!: ContRoot)]+ callervars = Map.fromList [("caller_module_name", (PString "::" :!: initialPPos mempty) :!: ContRoot), ("module_name", (PString "::" :!: initialPPos mempty) :!: ContRoot)] factvars = -- add the `facts` key: https://docs.puppet.com/puppet/4.10/lang_facts_and_builtin_vars.html#accessing-facts-from-puppet-code let facts' = Map.insert "facts" (PHash facts) facts- in fmap (\x -> x :!: initialPPos "facts" :!: ContRoot) facts'- settingvars = fmap (\x -> PString x :!: initialPPos "settings" :!: ContClass "settings") settings- baseVars = Map.fromList [ ("::", ScopeInformation (factvars `mappend` callervars) mempty mempty (CurContainer ContRoot mempty) mempty S.Nothing)- , ("settings", ScopeInformation settingvars mempty mempty (CurContainer (ContClass "settings") mempty) mempty S.Nothing)- ]- initialclass = mempty & at "::" ?~ (ClassIncludeLike :!: (initialPPos mempty))+ in fmap (\x -> (x :!: initialPPos "facts") :!: ContRoot) facts'+ settingvars = fmap (\x -> (PString x :!: initialPPos "settings") :!: ContClass "settings") settings+ baseVars =+ Map.fromList+ [ ("::", ScopeInformation (factvars `mappend` callervars) mempty mempty (CurContainer ContRoot mempty) mempty S.Nothing),+ ("settings", ScopeInformation settingvars mempty mempty (CurContainer (ContClass "settings") mempty) mempty S.Nothing)+ ]+ initialclass = mempty & at "::" ?~ (ClassIncludeLike :!: initialPPos mempty) getModulename :: RIdentifier -> Text getModulename (RIdentifier t n) = let gm x = case Text.splitOn "::" x of- [] -> x- (y:_) -> y- in case t of- "class" -> gm n- _ -> gm t+ [] -> x+ (y : _) -> y+ in case t of+ "class" -> gm n+ _ -> gm t extractPrism :: Doc -> Prism' a b -> a -> InterpreterMonad b extractPrism msg p a = case preview p a of- Just b -> return b+ Just b -> return b Nothing -> throwPosError ("Could not extract prism in" <+> msg) -- Scope@@ -64,21 +66,21 @@ getScopeName = scopeName <$> getScope scopeName :: CurContainerDesc -> Text-scopeName (ContRoot ) = "::"-scopeName (ContImported x ) = "::imported::" `Text.append` scopeName x-scopeName (ContClass x ) = x+scopeName ContRoot = "::"+scopeName (ContImported x) = "::imported::" `Text.append` scopeName x+scopeName (ContClass x) = x scopeName (ContDefine dt dn _) = "#define/" `Text.append` dt `Text.append` "/" `Text.append` dn-scopeName (ContImport _ x ) = "::import::" `Text.append` scopeName x+scopeName (ContImport _ x) = "::import::" `Text.append` scopeName x containerModName :: CurContainerDesc -> Text-containerModName (ContRoot ) = "::"-containerModName (ContImported x ) = containerModName x-containerModName (ContClass x ) = x+containerModName ContRoot = "::"+containerModName (ContImported x) = containerModName x+containerModName (ContClass x) = x containerModName (ContDefine dt _ _) = dt-containerModName (ContImport _ x ) = containerModName x+containerModName (ContImport _ x) = containerModName x getScope :: InterpreterMonad CurContainerDesc-{-# INLINABLE getScope #-}+{-# INLINEABLE getScope #-} getScope = use curScope >>= \s -> if null s@@ -86,7 +88,7 @@ else pure (List.head s) getCurContainer :: InterpreterMonad CurContainer-{-# INLINABLE getCurContainer #-}+{-# INLINEABLE getCurContainer #-} getCurContainer = do scp <- getScopeName preuse (scopes . ix scp . scopeContainer) >>= \case@@ -100,19 +102,20 @@ getPuppetPaths :: InterpreterMonad PuppetDirPaths getPuppetPaths = singleton PuppetPaths -getNodeName:: InterpreterMonad NodeName+getNodeName :: InterpreterMonad NodeName getNodeName = singleton GetNodeName -- | Give key such as "os.family" -- look an hash of facts to retrieve deepest PValue lookupFacts :: Text -> HashMap Text PValue -> Maybe PValue lookupFacts key facts =- let (k0:ks) = Text.splitOn "." key+ let (k0, ks) = case Text.splitOn "." key of+ a : b -> (a, b)+ _ -> Err.error "should not happen" f k = \case Just (PHash h) -> Map.lookup k h x -> x- in- List.foldr f (Map.lookup k0 facts) ks+ in List.foldr f (Map.lookup k0 facts) ks -- | Ask the value of a fact given a specified key -- The fact set comes from the reader used by the interpreter monad.@@ -126,9 +129,12 @@ -- | Throws an error if we are in strict mode -- A warning in permissive mode-checkStrict :: Doc -- ^ The warning message.- -> Doc -- ^ The error message.- -> InterpreterMonad ()+checkStrict ::+ -- | The warning message.+ Doc ->+ -- | The error message.+ Doc ->+ InterpreterMonad () checkStrict wrn err = do extMod <- isExternalModule let priority =@@ -152,23 +158,23 @@ isExternal = singleton . IsExternalModule . List.head . Text.splitOn "::" -- Logging ---error :: MonadWriter InterpreterWriter m => Doc -> m ()+error :: (MonadWriter InterpreterWriter m) => Doc -> m () error d = tell [Log.ERROR :!: d] -critical :: MonadWriter InterpreterWriter m => Doc -> m ()+critical :: (MonadWriter InterpreterWriter m) => Doc -> m () critical d = tell [Log.CRITICAL :!: d] -warn :: MonadWriter InterpreterWriter m => Doc -> m ()+warn :: (MonadWriter InterpreterWriter m) => Doc -> m () warn d = tell [Log.WARNING :!: d] -debug :: MonadWriter InterpreterWriter m => Doc -> m ()+debug :: (MonadWriter InterpreterWriter m) => Doc -> m () debug d = tell [Log.DEBUG :!: d] -logWriter :: MonadWriter InterpreterWriter m => Log.Priority -> Doc -> m ()+logWriter :: (MonadWriter InterpreterWriter m) => Log.Priority -> Doc -> m () logWriter prio d = tell [prio :!: d] safeDecodeUtf8 :: ByteString -> InterpreterMonad Text-{-# INLINABLE safeDecodeUtf8 #-}+{-# INLINEABLE safeDecodeUtf8 #-} safeDecodeUtf8 i = pure (Text.decodeUtf8 i) normalizeRIdentifier :: Text -> Text -> RIdentifier@@ -176,12 +182,13 @@ extractScope :: InterpreterState -> Maybe (Text, Container ScopeInformation) extractScope s =- let cscope = s ^. curScope in- if null cscope- then Nothing- else let scope_name = scopeName (List.head cscope)+ let cscope = s ^. curScope+ in if null cscope+ then Nothing+ else+ let scope_name = scopeName (List.head cscope) classes = (PArray . Vector.fromList . map PString . Map.keys) (s ^. loadedClasses) scps = s ^. scopes container_desc = fromMaybe ContRoot (scps ^? ix scope_name . scopeContainer . cctype) -- get the current containder description- cscps = scps & ix scope_name . scopeVariables . at "classes" ?~ ( classes :!: (initialPPos mempty) :!: container_desc )- in Just (scope_name, cscps)+ cscps = scps & ix scope_name . scopeVariables . at "classes" ?~ ((classes :!: initialPPos mempty) :!: container_desc)+ in Just (scope_name, cscps)
src/Puppet/Interpreter/IO.hs view
@@ -1,115 +1,118 @@ {-# LANGUAGE GADTs #-} -- | This is an internal module.-module Puppet.Interpreter.IO (- interpretMonad- ) where--import XPrelude--import Control.Monad.Operational-import Control.Monad.State.Strict-import qualified Data.Either.Strict as S-import qualified Data.Text as Text--import Hiera.Server-import Puppet.Interpreter.PrettyPrinter ()-import Puppet.Interpreter.Types-import PuppetDB+module Puppet.Interpreter.IO+ ( interpretMonad,+ )+where +import Control.Monad.Operational+import Control.Monad.State.Strict+import qualified Data.Either.Strict as S+import qualified Data.Text as Text+import Hiera.Server+import Puppet.Interpreter.PrettyPrinter ()+import Puppet.Interpreter.Types+import PuppetDB+import XPrelude -- | The operational interpreter function-interpretMonad :: Monad m- => InterpreterReader m- -> InterpreterState- -> InterpreterMonad a- -> m (Either PrettyError a, InterpreterState, InterpreterWriter)-interpretMonad r s0 instr = let (!p, !s1) = runState (viewT instr) s0- in eval r s1 p+interpretMonad ::+ (Monad m) =>+ InterpreterReader m ->+ InterpreterState ->+ InterpreterMonad a ->+ m (Either PrettyError a, InterpreterState, InterpreterWriter)+interpretMonad r s0 instr =+ let (!p, !s1) = runState (viewT instr) s0+ in eval r s1 p -- The internal (not exposed) eval function-eval :: Monad m- => InterpreterReader m- -> InterpreterState- -> ProgramViewT InterpreterInstr (State InterpreterState) a- -> m (Either PrettyError a, InterpreterState, InterpreterWriter)+eval ::+ (Monad m) =>+ InterpreterReader m ->+ InterpreterState ->+ ProgramViewT InterpreterInstr (State InterpreterState) a ->+ m (Either PrettyError a, InterpreterState, InterpreterWriter) eval _ s (Return x) = return (Right x, s, mempty) eval r s (a :>>= k) =- let runInstr = interpretMonad r s . k -- run one instruction- thpe = interpretMonad r s . throwPosError . getError- pdb = r^.readerPdbApi- strFail iof errf = iof >>= \case- Left rr -> thpe (errf (ppstring rr))- Right x -> runInstr x- canFail iof = iof >>= \case- S.Left err -> thpe err- S.Right x -> runInstr x- canFailX iof = runExceptT iof >>= \case- Left err -> thpe err- Right x -> runInstr x- logStuff x c = (_3 %~ (x <>)) <$> c- in- case a of- IsStrict -> runInstr (r ^. readerIsStrict)- ExternalFunction name args ->- -- #271: namespace is currently ignored when looking up puppetlabs functions- let (nsp, name') = Text.breakOnEnd "::" name- in- case r ^. readerExternalFunc . at name' of- Just fn -> interpretMonad r s ( fn args >>= k)- Nothing -> thpe (PrettyError ("Unknown function: (" <> ppline nsp <> ")" <> ppline name'))- GetStatement toptype topname -> canFail ((r ^. readerGetStatement) toptype topname)- ComputeTemplate src st -> canFail ((r ^. readerGetTemplate) src st r)- WriterTell t -> logStuff t (runInstr ())- WriterPass _ -> thpe "WriterPass"- WriterListen _ -> thpe "WriterListen"- PuppetPaths -> runInstr (r ^. readerPuppetPaths)- Facts -> runInstr (r ^. readerFacts)- RebaseFile -> runInstr (r ^. readerRebaseFile)- GetNativeTypes -> runInstr (r ^. readerNativeTypes)- ErrorThrow d -> return (Left d, s, mempty)- GetNodeName -> runInstr (r ^. readerNodename)- HieraQuery scps q t ->- runExceptT (queryHiera (r ^. readerHieraQuery) scps q t) >>= either thpe runInstr- PDBInformation -> pdbInformation pdb >>= runInstr- PDBReplaceCatalog w -> canFailX (replaceCatalog pdb w)- PDBReplaceFacts fcts -> canFailX (replaceFacts pdb fcts)- PDBDeactivateNode nn -> canFailX (deactivateNode pdb nn)- PDBGetFacts q -> canFailX (getPDBFacts pdb q)- PDBGetResources q -> canFailX (getResources pdb q)- PDBGetNodes q -> canFailX (getNodes pdb q)- PDBCommitDB -> canFailX (commitDB pdb)- PDBGetResourcesOfNode nn q -> canFailX (getResourcesOfNode pdb nn q)- GetCurrentCallStack -> (r ^. readerIoMethods . ioGetCurrentCallStack) >>= runInstr- ReadFile fls -> strFail ((r ^. readerIoMethods . ioReadFile) fls) (const $ PrettyError ("No file found in " <> list (map ppline fls)))- TraceEvent e -> (r ^. readerIoMethods . ioTraceEvent) e >>= runInstr- IsIgnoredModule m -> runInstr (r ^. readerIgnoredModules . contains m)- IsExternalModule m -> runInstr (r ^. readerExternalModules . contains m)- -- on error, the program state is RESET and the logged messages are dropped- ErrorCatch atry ahandle -> do- (eres, s', w) <- interpretMonad r s atry- case eres of- Left rr -> interpretMonad r s (ahandle rr >>= k)- Right x -> logStuff w (interpretMonad r s' (k x))+ let runInstr = interpretMonad r s . k -- run one instruction+ thpe = interpretMonad r s . throwPosError . getError+ pdb = r ^. readerPdbApi+ strFail iof errf =+ iof >>= \case+ Left rr -> thpe (errf (ppstring rr))+ Right x -> runInstr x+ canFail iof =+ iof >>= \case+ S.Left err -> thpe err+ S.Right x -> runInstr x+ canFailX iof =+ runExceptT iof >>= \case+ Left err -> thpe err+ Right x -> runInstr x+ logStuff x c = (_3 %~ (x <>)) <$> c+ in case a of+ IsStrict -> runInstr (r ^. readerIsStrict)+ ExternalFunction "Sensitive" [arg] -> interpretMonad r s (k (PSensitive arg))+ ExternalFunction name args ->+ -- #271: namespace is currently ignored when looking up puppetlabs functions+ let (nsp, name') = Text.breakOnEnd "::" name+ in case r ^. readerExternalFunc . at name' of+ Just fn -> interpretMonad r s (fn args >>= k)+ Nothing -> thpe (PrettyError ("Unknown function: (" <> ppline nsp <> ")" <> ppline name'))+ GetStatement toptype topname -> canFail ((r ^. readerGetStatement) toptype topname)+ ComputeTemplate src st -> canFail ((r ^. readerGetTemplate) src st r)+ WriterTell t -> logStuff t (runInstr ())+ WriterPass _ -> thpe "WriterPass"+ WriterListen _ -> thpe "WriterListen"+ PuppetPaths -> runInstr (r ^. readerPuppetPaths)+ Facts -> runInstr (r ^. readerFacts)+ RebaseFile -> runInstr (r ^. readerRebaseFile)+ GetNativeTypes -> runInstr (r ^. readerNativeTypes)+ ErrorThrow d -> return (Left d, s, mempty)+ GetNodeName -> runInstr (r ^. readerNodename)+ HieraQuery scps q t ->+ runExceptT (queryHiera (r ^. readerHieraQuery) scps q t) >>= either thpe runInstr+ PDBInformation -> pdbInformation pdb >>= runInstr+ PDBReplaceCatalog w -> canFailX (replaceCatalog pdb w)+ PDBReplaceFacts fcts -> canFailX (replaceFacts pdb fcts)+ PDBDeactivateNode nn -> canFailX (deactivateNode pdb nn)+ PDBGetFacts q -> canFailX (getPDBFacts pdb q)+ PDBGetResources q -> canFailX (getResources pdb q)+ PDBGetNodes q -> canFailX (getNodes pdb q)+ PDBCommitDB -> canFailX (commitDB pdb)+ PDBGetResourcesOfNode nn q -> canFailX (getResourcesOfNode pdb nn q)+ GetCurrentCallStack -> (r ^. readerIoMethods . ioGetCurrentCallStack) >>= runInstr+ ReadFile fls -> strFail ((r ^. readerIoMethods . ioReadFile) fls) (const $ PrettyError ("No file found in " <> list (map ppline fls)))+ TraceEvent e -> (r ^. readerIoMethods . ioTraceEvent) e >>= runInstr+ IsIgnoredModule m -> runInstr (r ^. readerIgnoredModules . contains m)+ IsExternalModule m -> runInstr (r ^. readerExternalModules . contains m)+ -- on error, the program state is RESET and the logged messages are dropped+ ErrorCatch atry ahandle -> do+ (eres, s', w) <- interpretMonad r s atry+ case eres of+ Left rr -> interpretMonad r s (ahandle rr >>= k)+ Right x -> logStuff w (interpretMonad r s' (k x)) -- query all hiera layers-queryHiera :: Monad m- => HieraQueryLayers m- -> Container PValue- -> Text- -> HieraQueryType- -> ExceptT PrettyError m (Maybe PValue)+queryHiera ::+ (Monad m) =>+ HieraQueryLayers m ->+ Container PValue ->+ Text ->+ HieraQueryType ->+ ExceptT PrettyError m (Maybe PValue) queryHiera layers scps q t = do- eglobal <- (layers^.globalLayer) scps q t- eenvironment <- (layers ^.environmentLayer) scps q t+ eglobal <- (layers ^. globalLayer) scps q t+ eenvironment <- (layers ^. environmentLayer) scps q t let modname =- case Text.splitOn "::" (Text.dropWhile (==':') q) of- [] -> Nothing- [_] -> Nothing- (m:_) -> Just m- layer = modname >>= (\n -> layers ^.moduleLayer.at n)+ case Text.splitOn "::" (Text.dropWhile (== ':') q) of+ [] -> Nothing+ [_] -> Nothing+ (m : _) -> Just m+ layer = modname >>= (\n -> layers ^. moduleLayer . at n) emodle <- maybe (pure Nothing) (\hq -> hq scps q t) layer case catMaybes [eglobal, eenvironment, emodle] of [] -> pure Nothing- x:xs -> Just <$> foldM (mergeWith t) x xs-+ x : xs -> Just <$> foldM (mergeWith t) x xs
src/Puppet/Interpreter/PrettyPrinter.hs view
@@ -1,30 +1,29 @@+{-# LANGUAGE GADTs #-} {-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE GADTs #-}-module Puppet.Interpreter.PrettyPrinter () where -import XPrelude+module Puppet.Interpreter.PrettyPrinter () where -import qualified Data.Aeson as Aeson+import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy.Char8 as BSL--import Puppet.Interpreter.Types-import PuppetDB+import Puppet.Interpreter.Types+import PuppetDB+import XPrelude instance Pretty TemplateSource where pretty (Inline s) = pretty (PString s) pretty (Filename s) = pptext s instance Pretty TopLevelType where- pretty TopNode = dullyellow "node"- pretty TopDefine = dullyellow "define"- pretty TopClass = dullyellow "class"+ pretty TopNode = dullyellow "node"+ pretty TopDefine = dullyellow "define"+ pretty TopClass = dullyellow "class" instance Pretty ResDefaults where- pretty (ResDefaults t _ v p) = capitalizeR t <+> showPPos p <> line <> containerComma v+ pretty (ResDefaults t _ v p) = capitalizeR t <+> showPPos p <> line <> containerComma v instance Pretty ResourceModifier where- pretty (ResourceModifier rt ModifierMustMatch RealizeVirtual (REqualitySearch "title" (PString x)) _ p) = "realize" <> parens (pretty (PResourceReference rt x)) <+> showPPos p- pretty _ = "TODO pretty ResourceModifier"+ pretty (ResourceModifier rt ModifierMustMatch RealizeVirtual (REqualitySearch "title" (PString x)) _ p) = "realize" <> parens (pretty (PResourceReference rt x)) <+> showPPos p+ pretty _ = "TODO pretty ResourceModifier" instance Pretty RSearchExpression where pretty (REqualitySearch a v) = ppline a <+> "==" <+> pretty v@@ -36,7 +35,7 @@ pf :: Doc -> [Doc] -> Doc pf fn args = bold (red fn) <> tupled (map pretty args) -showQuery :: ToJSON a => Query a -> Doc+showQuery :: (ToJSON a) => Query a -> Doc showQuery = ppstring . BSL.unpack . Aeson.encode instance Pretty (InterpreterInstr a) where@@ -44,31 +43,31 @@ pretty RebaseFile = pf "RebaseFile" [] pretty IsStrict = pf "IsStrict" [] pretty GetNativeTypes = pf "GetNativeTypes" []- pretty (GetStatement tlt nm) = pf "GetStatement" [pretty tlt,ppline nm]+ pretty (GetStatement tlt nm) = pf "GetStatement" [pretty tlt, ppline nm] pretty (ComputeTemplate src _) = pf "ComputeTemplate" [pretty src]- pretty (ExternalFunction fn args) = pf (ppline fn) (map pretty args)- pretty GetNodeName = pf "GetNodeName" []- pretty (HieraQuery _ q _) = pf "HieraQuery" [ppline q]- pretty GetCurrentCallStack = pf "GetCurrentCallStack" []- pretty (ErrorThrow rr) = pf "ErrorThrow" [getError rr]- pretty (ErrorCatch _ _) = pf "ErrorCatch" []- pretty (WriterTell t) = pf "WriterTell" (map (pretty . view _2) t)- pretty (WriterPass _) = pf "WriterPass" []- pretty (WriterListen _) = pf "WriterListen" []- pretty PDBInformation = pf "PDBInformation" []- pretty (PDBReplaceCatalog _) = pf "PDBReplaceCatalog" ["..."]- pretty (PDBReplaceFacts _) = pf "PDBReplaceFacts" ["..."]- pretty (PDBDeactivateNode n) = pf "PDBDeactivateNode" [ppline n]- pretty (PDBGetFacts q) = pf "PDBGetFacts" [showQuery q]- pretty (PDBGetResources q) = pf "PDBGetResources" [showQuery q]- pretty (PDBGetNodes q) = pf "PDBGetNodes" [showQuery q]- pretty PDBCommitDB = pf "PDBCommitDB" []+ pretty (ExternalFunction fn args) = pf (ppline fn) (map pretty args)+ pretty GetNodeName = pf "GetNodeName" []+ pretty (HieraQuery _ q _) = pf "HieraQuery" [ppline q]+ pretty GetCurrentCallStack = pf "GetCurrentCallStack" []+ pretty (ErrorThrow rr) = pf "ErrorThrow" [getError rr]+ pretty (ErrorCatch _ _) = pf "ErrorCatch" []+ pretty (WriterTell t) = pf "WriterTell" (map (pretty . view _2) t)+ pretty (WriterPass _) = pf "WriterPass" []+ pretty (WriterListen _) = pf "WriterListen" []+ pretty PDBInformation = pf "PDBInformation" []+ pretty (PDBReplaceCatalog _) = pf "PDBReplaceCatalog" ["..."]+ pretty (PDBReplaceFacts _) = pf "PDBReplaceFacts" ["..."]+ pretty (PDBDeactivateNode n) = pf "PDBDeactivateNode" [ppline n]+ pretty (PDBGetFacts q) = pf "PDBGetFacts" [showQuery q]+ pretty (PDBGetResources q) = pf "PDBGetResources" [showQuery q]+ pretty (PDBGetNodes q) = pf "PDBGetNodes" [showQuery q]+ pretty PDBCommitDB = pf "PDBCommitDB" [] pretty (PDBGetResourcesOfNode n q) = pf "PDBGetResourcesOfNode" [ppline n, showQuery q]- pretty (ReadFile f) = pf "ReadFile" (map ppline f)- pretty (TraceEvent e) = pf "TraceEvent" [ppstring e]- pretty (IsIgnoredModule m) = pf "IsIgnoredModule" [ppline m]- pretty (IsExternalModule m) = pf "IsExternalModule" [ppline m]- pretty Facts = pf "Facts" []+ pretty (ReadFile f) = pf "ReadFile" (map ppline f)+ pretty (TraceEvent e) = pf "TraceEvent" [ppstring e]+ pretty (IsIgnoredModule m) = pf "IsIgnoredModule" [ppline m]+ pretty (IsExternalModule m) = pf "IsExternalModule" [ppline m]+ pretty Facts = pf "Facts" [] instance Pretty LinkInformation where- pretty (LinkInformation lsrc ldst ltype lpos) = pretty lsrc <+> pretty ltype <+> pretty ldst <+> showPPos lpos+ pretty (LinkInformation lsrc ldst ltype lpos) = pretty lsrc <+> pretty ltype <+> pretty ldst <+> showPPos lpos
src/Puppet/Interpreter/Resolve.hs view
@@ -1,75 +1,76 @@-{-# LANGUAGE PackageImports #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TupleSections #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE RankNTypes #-} -- | This module is all about converting and resolving foreign data into -- the fully exploitable corresponding data type. -- -- The main use case is the conversion of 'Expression' to 'PValue'. module Puppet.Interpreter.Resolve- ( -- * Pure resolution functions- getVariable,- pValue2Bool,- -- * Monadic resolution functions- resolveVariable,- resolveExpression,- resolveValue,- resolvePValueString,- resolvePValueNumber,- resolveExpressionString,- resolveExpressionStrings,- resolveFunction',- resolveDataType,- runHiera,- isNativeType,- -- * Search expression management- resolveSearchExpression,- checkSearchExpression,- searchExpressionToPuppetDB,- -- * Higher order puppet functions handling- hfGenerateAssociations,- hfSetvars,- hfRestorevars,- fixResourceName,- datatypeMatch,- checkMatch,- typeOf- ) where+ ( -- * Pure resolution functions+ getVariable,+ pValue2Bool, -import XPrelude.Extra-import XPrelude.PP+ -- * Monadic resolution functions+ resolveVariable,+ resolveExpression,+ resolveValue,+ resolvePValueString,+ resolvePValueNumber,+ resolveExpressionString,+ resolveExpressionStrings,+ resolveFunction',+ resolveDataType,+ runHiera,+ isNativeType, -import qualified Control.Monad.Operational as Operational-import "cryptonite" Crypto.Hash-import qualified Data.Aeson as Aeson-import qualified Data.ByteArray as ByteArray-import qualified Data.ByteString as BS-import qualified Data.ByteString.Base16 as B16-import qualified Data.CaseInsensitive as CaseInsensitive-import qualified Data.Char as Char-import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as HS-import qualified Data.List as List-import qualified Data.List.NonEmpty as NE-import qualified Data.Maybe.Strict as S-import qualified Data.Scientific as Scientific-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Data.Tuple.Strict as Tuple-import qualified Data.Vector as V-import Data.Version (Version (..), parseVersion)-import Text.ParserCombinators.ReadP (readP_to_S)-import qualified Text.Regex.PCRE.ByteString.Utils as Regex+ -- * Search expression management+ resolveSearchExpression,+ checkSearchExpression,+ searchExpressionToPuppetDB, -import Hiera.Server-import Puppet.Interpreter.Helpers-import Puppet.Interpreter.PrettyPrinter ()-import Puppet.Interpreter.Resolve.Sprintf (sprintf)-import Puppet.Interpreter.RubyRandom-import Puppet.Interpreter.Types-import Puppet.Parser-import PuppetDB+ -- * Higher order puppet functions handling+ hfGenerateAssociations,+ hfSetvars,+ hfRestorevars,+ fixResourceName,+ datatypeMatch,+ checkMatch,+ typeOf,+ )+where +import qualified Control.Monad.Operational as Operational+import qualified Data.Aeson as Aeson+import qualified Data.ByteArray as ByteArray+import qualified Data.ByteString as BS+import qualified Data.ByteString.Base16 as B16+import qualified Data.CaseInsensitive as CaseInsensitive+import qualified Data.Char as Char+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import qualified Data.List as List+import qualified Data.List.NonEmpty as NE+import qualified Data.Maybe.Strict as S+import qualified Data.Scientific as Scientific+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Tuple.Strict as Tuple+import qualified Data.Vector as V+import Data.Version (Version (..), parseVersion)+import Hiera.Server+import Puppet.Interpreter.Helpers+import Puppet.Interpreter.PrettyPrinter ()+import Puppet.Interpreter.Resolve.Sprintf (sprintf)+import Puppet.Interpreter.RubyRandom+import Puppet.Interpreter.Types+import Puppet.Parser+import PuppetDB+import Text.ParserCombinators.ReadP (readP_to_S)+import qualified Text.Regex.PCRE.ByteString.Utils as Regex+import XPrelude.Extra+import XPrelude.PP+import "cryptonite" Crypto.Hash+ sha1 :: ByteString -> ByteString sha1 = ByteArray.convert . (hash :: ByteString -> Digest SHA1) @@ -80,11 +81,14 @@ type NumberPair = Pair Scientific Scientific -- | Converts class resource names to lowercase (fix for the jenkins plugin).-fixResourceName :: Text -- ^ Resource type- -> Text -- ^ Resource name- -> Text+fixResourceName ::+ -- | Resource type+ Text ->+ -- | Resource name+ Text ->+ Text fixResourceName "class" x = Text.toLower $ fromMaybe x $ Text.stripPrefix "::" x-fixResourceName _ x = x+fixResourceName _ x = x -- | A hiera helper function, that will throw all Hiera errors and log -- messages to the main monad.@@ -111,11 +115,11 @@ hieraCall qt q df dt _ = do qs <- resolvePValueString q runHiera qs qt >>= \case- Just p -> case dt of+ Just p -> case dt of Just dt' | not (datatypeMatch dt' p) -> throwPosError ("Datatype mismatched :" <+> pretty dt' <+> "/" <+> pretty p)- _ -> pure p+ _ -> pure p Nothing -> case df of- Just d -> pure d+ Just d -> pure d Nothing -> throwPosError ("Lookup for " <> ppline qs <> " failed") -- | Tries to convert a pair of 'PValue's into a 'NumberPair', as defined in@@ -133,10 +137,14 @@ -- | This tries to run a numerical binary operation on two puppet -- expressions. It will try to resolve them, then convert them to numbers -- (using 'toNumbers'), and will finally apply the correct operation.-binaryOperation :: Expression -- ^ left operand- -> Expression -- ^ right operand- -> (Scientific -> Scientific -> Scientific) -- ^ operation- -> InterpreterMonad PValue+binaryOperation ::+ -- | left operand+ Expression ->+ -- | right operand+ Expression ->+ -- | operation+ (Scientific -> Scientific -> Scientific) ->+ InterpreterMonad PValue binaryOperation a b opr = ((PNumber .) . opr) `fmap` resolveExpressionNumber a <*> resolveExpressionNumber b -- Just like 'binaryOperation', but for operations that only work on integers.@@ -162,19 +170,24 @@ isNativeType t = has (ix t) `fmap` Operational.singleton GetNativeTypes -- | A pure function for resolving variables.-getVariable :: Container ScopeInformation -- ^ The whole scope data.- -> Text -- ^ Current scope name.- -> Text -- ^ Full variable name.- -> Either Doc PValue+getVariable ::+ -- | The whole scope data.+ Container ScopeInformation ->+ -- | Current scope name.+ Text ->+ -- | Full variable name.+ Text ->+ Either Doc PValue getVariable scps scp fullvar = do (varscope, varname) <- case Text.splitOn "::" fullvar of [] -> Left "This doesn't make any sense in resolveVariable" [vn] -> pure (scp, vn) -- Non qualified variables rst -> pure (Text.intercalate "::" (filter (not . Text.null) (List.init rst)), List.last rst) -- qualified variables- let extractVariable (varval :!: _ :!: _) = pure varval+ let extractVariable ((varval :!: _) :!: _) = pure varval case scps ^? ix varscope . scopeVariables . ix varname of Just pp -> extractVariable pp- Nothing -> -- check top level scope+ Nothing ->+ -- check top level scope case scps ^? ix "::" . scopeVariables . ix varname of Just pp -> extractVariable pp Nothing -> Left ("Could not resolve variable" <+> pretty (UVariableReference fullvar) <+> "in context" <+> ppline varscope <+> "or root")@@ -189,15 +202,15 @@ case toNumbers ra rb of (S.Just (na :!: nb)) -> na == nb _ -> case (ra, rb) of- (PUndef , PBoolean x) -> not x- (PString "true", PBoolean x) -> x+ (PUndef, PBoolean x) -> not x+ (PString "true", PBoolean x) -> x (PString "false", PBoolean x) -> not x- (PBoolean x, PString "true") -> x+ (PBoolean x, PString "true") -> x (PBoolean x, PString "false") -> not x- (PString sa, PString sb) -> CaseInsensitive.mk sa == CaseInsensitive.mk sb+ (PString sa, PString sb) -> CaseInsensitive.mk sa == CaseInsensitive.mk sb -- TODO, check if array / hash equality should be recursed -- for case insensitive matching- _ -> ra == rb+ _ -> ra == rb -- Match a left expression with a regex -- Return a contextual True/False or Error after executing against the regex@@ -205,16 +218,16 @@ matchExpression a (rv, rexpr) = do ra <- Text.encodeUtf8 <$> resolveExpressionString a case Regex.execute' rv ra of- Left (_,rr) -> throwPosError ("Error when executing regex" <+> pretty rexpr <+> ":" <+> pretty rr)- Right Nothing -> pure $ PBoolean False+ Left (_, rr) -> throwPosError ("Error when executing regex" <+> pretty rexpr <+> ":" <+> pretty rr)+ Right Nothing -> pure $ PBoolean False Right (Just matches) -> do -- A bit of logic to save the capture variables. -- Note that this will pollute the namespace, as it should only -- happen in conditional expressions ... p <- use curPos ctype <- view cctype <$> getCurContainer- let captures = zip (map (Text.pack . show) [(0 :: Int)..]) (map mkMatch (toList matches))- mkMatch (offset, len) = PString (Text.decodeUtf8 (BS.take len (BS.drop offset ra))) :!: p :!: ctype+ let captures = zip (map (Text.pack . show) [(0 :: Int) ..]) (map mkMatch (toList matches))+ mkMatch (offset, len) = (PString (Text.decodeUtf8 (BS.take len (BS.drop offset ra))) :!: p) :!: ctype scp <- getScopeName scopes . ix scp . scopeVariables %= HM.union (HM.fromList captures) pure $ PBoolean True@@ -259,8 +272,8 @@ PHash h -> do ridx <- resolveExpressionString idx case h ^. at ridx of- Just _ -> pure (PBoolean True)- Nothing -> pure (PBoolean False)+ Just _ -> pure (PBoolean True)+ Nothing -> pure (PBoolean False) PArray ar -> do ridx <- resolveExpression idx pure (PBoolean (ridx `V.elem` ar))@@ -296,9 +309,9 @@ checkCond ((SelectorValue v@(URegexp (CompRegex _ rg)) :!: ce) : xs) = do rs <- fmap Text.encodeUtf8 (resolvePValueString rese) case Regex.execute' rg rs of- Left (_,rr) -> throwPosError ("Could not match" <+> pretty v <+> ":" <+> ppstring rr)- Right Nothing -> checkCond xs- Right (Just _) -> resolveExpression ce+ Left (_, rr) -> throwPosError ("Could not match" <+> pretty v <+> ":" <+> ppstring rr)+ Right Nothing -> checkCond xs+ Right (Just _) -> resolveExpression ce checkCond ((SelectorType udt :!: ce) : xs) = do dt <- resolveDataType udt if datatypeMatch dt rese@@ -314,29 +327,29 @@ ra <- resolveExpression a rb <- resolveExpression b case (ra, rb) of- (PHash ha, PHash hb) -> pure (PHash (ha <> hb))+ (PHash ha, PHash hb) -> pure (PHash (ha <> hb)) (PArray ha, PArray hb) -> pure (PArray (ha <> hb))- _ -> binaryOperation a b (+)-resolveExpression (Substraction a b) = binaryOperation a b (-)-resolveExpression (Division a b) = do+ _ -> binaryOperation a b (+)+resolveExpression (Substraction a b) = binaryOperation a b (-)+resolveExpression (Division a b) = do ra <- resolveExpressionNumber a rb <- resolveExpressionNumber b case rb of- 0 -> throwPosError "Division by 0"- _ -> case (,) `fmap` preview _ScientificInteger ra <*> preview _ScientificInteger rb of- Just (ia, ib) -> pure $ PNumber $ fromIntegral (ia `div` ib)- _ -> pure $ PNumber $ ra / rb+ 0 -> throwPosError "Division by 0"+ _ -> case (,) `fmap` preview _ScientificInteger ra <*> preview _ScientificInteger rb of+ Just (ia, ib) -> pure $ PNumber $ fromIntegral (ia `div` ib)+ _ -> pure $ PNumber $ ra / rb resolveExpression (Multiplication a b) = binaryOperation a b (*)-resolveExpression (Modulo a b) = integerOperation a b mod-resolveExpression (RightShift a b) = integerOperation a b (\x -> shiftR x . fromIntegral)+resolveExpression (Modulo a b) = integerOperation a b mod+resolveExpression (RightShift a b) = integerOperation a b (\x -> shiftR x . fromIntegral) resolveExpression (LeftShift a b) = do ra <- resolveExpression a rb <- resolveExpression b case (ra, rb) of- (PArray ha, v) -> pure (PArray (V.snoc ha v))- _ -> integerOperation a b (\x -> shiftL x . fromIntegral)+ (PArray ha, v) -> pure (PArray (V.snoc ha v))+ _ -> integerOperation a b (\x -> shiftL x . fromIntegral) resolveExpression (FunctionApplication e (Terminal (UHOLambdaCall hol))) =- resolveValue (UHOLambdaCall (hol & hoLambdaExpr <>~ V.singleton e))+ resolveValue (UHOLambdaCall (hol & hoLambdaExpr <>~ V.singleton e)) resolveExpression (FunctionApplication _ x) = throwPosError ("Expected function application here, not" <+> pretty x) resolveExpression (Negate x) = PNumber . negate <$> resolveExpressionNumber x @@ -353,7 +366,7 @@ r <- resolveExpressionStrings e case r of [s] -> pure (PResourceReference t (fixResourceName t s))- _ -> pure (PArray (V.fromList (map (PResourceReference t . fixResourceName t) r)))+ _ -> pure (PArray (V.fromList (map (PResourceReference t . fixResourceName t) r))) resolveValue (UArray a) = fmap PArray (V.mapM resolveExpression a) resolveValue (UHash a) = fmap (PHash . HM.fromList) (mapM resPair (V.toList a))@@ -370,18 +383,19 @@ resolvePValueString (PBoolean True) = pure "true" resolvePValueString (PBoolean False) = pure "false" resolvePValueString (PNumber x) = pure (scientific2text x)+resolvePValueString (PRegexp (CompRegex e _)) = pure e resolvePValueString PUndef = do checkStrict "Resolving the keyword `undef` to the string \"undef\"" "Strict mode won't convert the keyword `undef` to the string \"undef\"" pure "undef"-resolvePValueString x = throwPosError ("Don't know how to convert this to a string:" <> line <> pretty x)+resolvePValueString x = throwPosError ("Don't know how to convert this to a string:" <> line <> pretty x) -- | Turns everything it can into a number, or throws an error resolvePValueNumber :: PValue -> InterpreterMonad Scientific resolvePValueNumber x = case x ^? _PValueNumber of- Just n -> pure n+ Just n -> pure n Nothing -> throwPosError ("Don't know how to convert this to a number:" <> line <> pretty x) -- | > resolveExpressionString = resolveExpression >=> resolvePValueString@@ -401,10 +415,10 @@ -- | Turns a 'PValue' into a 'Bool' as explained in the reference documentation. pValue2Bool :: PValue -> Bool-pValue2Bool PUndef = False+pValue2Bool PUndef = False pValue2Bool (PString "") = True pValue2Bool (PBoolean x) = x-pValue2Bool _ = True+pValue2Bool _ = True -- | This resolve function calls at the expression level. resolveFunction :: Text -> V.Vector Expression -> InterpreterMonad PValue@@ -412,62 +426,66 @@ let nbargs = V.length args when (nbargs < 1 || nbargs > 2) (throwPosError "fqdn_rand(): Expects one or two arguments") fqdn <- resolveVariable "::fqdn" >>= resolvePValueString- (mx:targs) <- mapM resolveExpressionString (V.toList args)+ (mx : targs) <- mapM resolveExpressionString (V.toList args) curmax <- case PString mx ^? _PValueInteger of Just x -> pure x _ -> throwPosError ("fqdn_rand(): the first argument must be an integer, not" <+> ppline mx)- let rargs = if null targs- then [fqdn, ""]- else fqdn : targs+ let rargs =+ if null targs+ then [fqdn, ""]+ else fqdn : targs val = fromIntegral (fst (limitedRand (randInit myhash) (fromIntegral curmax))) myhash = toint (md5 (Text.encodeUtf8 fullstring)) :: Integer- toint = BS.foldl' (\c nx -> c*256 + fromIntegral nx) 0+ toint = BS.foldl' (\c nx -> c * 256 + fromIntegral nx) 0 fullstring = Text.intercalate ":" rargs pure (_PValueInteger # val) resolveFunction fname args = mapM resolveExpression (V.toList args) >>= resolveFunction' fname . map undefEmptyString where undefEmptyString PUndef = PString ""- undefEmptyString x = x+ undefEmptyString x = x resolveFunction' :: Text -> [PValue] -> InterpreterMonad PValue resolveFunction' "defined" [PResourceReference "class" cn] = do- checkStrict "The use of the 'defined' function is a code smell"- "The 'defined' function is not allowed in strict mode."+ checkStrict+ "The use of the 'defined' function is a code smell"+ "The 'defined' function is not allowed in strict mode." fmap (PBoolean . has (ix cn)) (use loadedClasses) resolveFunction' "defined" [PResourceReference rt rn] = do- checkStrict "The use of the 'defined' function is a code smell"- "The 'defined' function is not allowed in strict mode."+ checkStrict+ "The use of the 'defined' function is a code smell"+ "The 'defined' function is not allowed in strict mode." fmap (PBoolean . has (ix (RIdentifier rt rn))) (use definedResources) resolveFunction' "defined" [ut] = do- checkStrict "The use of the 'defined' function is a code smell."- "The 'defined' function is not allowed in strict mode."+ checkStrict+ "The use of the 'defined' function is a code smell."+ "The 'defined' function is not allowed in strict mode." t <- resolvePValueString ut if not (Text.null t) && Text.head t == '$' -- variable test then do scps <- use scopes scp <- getScopeName pure $ PBoolean $ case getVariable scps scp (Text.tail t) of- Left _ -> False+ Left _ -> False Right _ -> True- else do -- resource test+ else do+ -- resource test -- case 1, nested thingie nestedStuff <- use nestedDeclarations if has (ix (TopDefine, t)) nestedStuff || has (ix (TopClass, t)) nestedStuff then pure (PBoolean True)- else do -- case 2, loaded class+ else do+ -- case 2, loaded class lc <- use loadedClasses if has (ix t) lc then pure (PBoolean True) else fmap PBoolean (isNativeType t)- resolveFunction' "defined" x = throwPosError ("defined(): expects a single resource reference, type or class name, and not" <+> pretty x) resolveFunction' "fail" x = throwPosError ("fail:" <+> pretty x) resolveFunction' "inline_template" [] = throwPosError "inline_template(): Expects at least one argument" resolveFunction' "inline_template" templates = let compute = fmap Inline . resolvePValueString >=> calcTemplate- in- PString . mconcat <$> traverse compute templates+ in PString . mconcat <$> traverse compute templates resolveFunction' "md5" [pstr] = fmap (PString . Text.decodeUtf8 . B16.encode . md5 . Text.encodeUtf8) (resolvePValueString pstr) resolveFunction' "md5" _ = throwPosError "md5(): Expects a single argument" resolveFunction' "regsubst" [ptarget, pregexp, preplacement] = resolveFunction' "regsubst" [ptarget, pregexp, preplacement, PString "G"]@@ -475,7 +493,7 @@ -- TODO handle all the flags -- http://docs.puppetlabs.com/references/latest/function.html#regsubst when (pflags /= "G") (use curPos >>= \p -> warn ("regsubst(): Currently only supports a single flag (G) " <> showPos (Tuple.fst p)))- regexp <- fmap Text.encodeUtf8 (resolvePValueString pregexp)+ regexp <- fmap Text.encodeUtf8 (resolvePValueString pregexp) replacement <- fmap Text.encodeUtf8 (resolvePValueString preplacement) let sub t = do t' <- fmap Text.encodeUtf8 (resolvePValueString t)@@ -484,45 +502,46 @@ Right x -> fmap PString (safeDecodeUtf8 x) case ptarget of PArray a -> fmap PArray (traverse sub a)- s -> sub s+ s -> sub s resolveFunction' "regsubst" _ = throwPosError "regsubst(): Expects 3 or 4 arguments" resolveFunction' "split" [psrc, psplt] = do- src <- fmap Text.encodeUtf8 (resolvePValueString psrc)+ src <- fmap Text.encodeUtf8 (resolvePValueString psrc) splt <- fmap Text.encodeUtf8 (resolvePValueString psplt) case Regex.splitCompile' splt src of Left err -> throwPosError ("splitCompile():" <+> ppstring err)- Right x -> fmap (PArray . V.fromList) (mapM (fmap PString . safeDecodeUtf8) x)+ Right x -> fmap (PArray . V.fromList) (mapM (fmap PString . safeDecodeUtf8) x) resolveFunction' "sha1" [pstr] = fmap (PString . Text.decodeUtf8 . B16.encode . sha1 . Text.encodeUtf8) (resolvePValueString pstr) resolveFunction' "sha1" _ = throwPosError "sha1(): Expects a single argument" resolveFunction' "shellquote" args = do sargs <- for args $ \arg -> case arg of PArray vals -> mapM resolvePValueString vals- _ -> V.singleton <$> resolvePValueString arg- let escape str | Text.all isSafe str = str- | not (Text.any isDangerous str) = between "\"" str- | Text.any (== '\'') str = between "\"" (Text.concatMap escapeDangerous str)- | otherwise = between "'" str+ _ -> V.singleton <$> resolvePValueString arg+ let escape str+ | Text.all isSafe str = str+ | not (Text.any isDangerous str) = between "\"" str+ | Text.any (== '\'') str = between "\"" (Text.concatMap escapeDangerous str)+ | otherwise = between "'" str isSafe x = Char.isAlphaNum x || x `elem` ("@%_+=:,./-" :: String) isDangerous x = x `elem` ("!\"`$\\" :: String)- escapeDangerous x | isDangerous x = Text.snoc "\\" x- | otherwise = Text.singleton x+ escapeDangerous x+ | isDangerous x = Text.snoc "\\" x+ | otherwise = Text.singleton x between c s = c <> s <> c pure $ PString $ Text.unwords $ V.toList (escape <$> mconcat sargs)- resolveFunction' "mysql_password" [pstr] = fmap (PString . Text.decodeUtf8 . B16.encode . sha1 . sha1 . Text.encodeUtf8) (resolvePValueString pstr) resolveFunction' "mysql_password" _ = throwPosError "mysql_password(): Expects a single argument" resolveFunction' "file" args = do rebasefile <- fmap Text.pack <$> Operational.singleton RebaseFile- let fixFilePath s | Text.null s = let rr = "Empty file path passed to the 'file' function" in checkStrict rr rr >> pure s- | Text.head s == '/' = pure (maybe s (<> s) rebasefile)- | otherwise = case Text.splitOn "/" s of- (md:x:rst) -> do- moduledir <- view modulesPath <$> getPuppetPaths- pure (Text.intercalate "/" (Text.pack moduledir : md : "files" : x : rst))- _ -> throwPosError ("file() argument invalid: " <> ppline s)+ let fixFilePath s+ | Text.null s = let rr = "Empty file path passed to the 'file' function" in checkStrict rr rr >> pure s+ | Text.head s == '/' = pure (maybe s (<> s) rebasefile)+ | otherwise = case Text.splitOn "/" s of+ (md : x : rst) -> do+ moduledir <- view modulesPath <$> getPuppetPaths+ pure (Text.intercalate "/" (Text.pack moduledir : md : "files" : x : rst))+ _ -> throwPosError ("file() argument invalid: " <> ppline s) mapM (resolvePValueString >=> fixFilePath) args >>= fmap PString . Operational.singleton . ReadFile- resolveFunction' "tagged" ptags = do tags <- fmap HS.fromList (mapM resolvePValueString ptags) scp <- getScopeName@@ -534,15 +553,14 @@ resolveFunction' "template" [] = throwPosError "template(): Expects at least one argument" resolveFunction' "template" templates = let compute = fmap (Filename . Text.unpack) . resolvePValueString >=> calcTemplate- in- PString . mconcat <$> traverse compute templates-resolveFunction' "versioncmp" [pa,pb] = do+ in PString . mconcat <$> traverse compute templates+resolveFunction' "versioncmp" [pa, pb] = do a <- resolvePValueString pa b <- resolvePValueString pb let parser x = case filter (null . snd) (readP_to_S parseVersion (Text.unpack x)) of- ( (v, _) : _ ) -> v- _ -> Version [] [] -- fallback :(+ ((v, _) : _) -> v+ _ -> Version [] [] -- fallback :( va = parser a vb = parser b pure $ PString $ case compare va vb of@@ -550,30 +568,29 @@ LT -> "-1" GT -> "1" resolveFunction' "versioncmp" _ = throwPosError "versioncmp(): Expects two arguments"--- | Simplified implementation of sprintf-resolveFunction' "sprintf" (PString str:args) = sprintf str args+-- \| Simplified implementation of sprintf+resolveFunction' "sprintf" (PString str : args) = sprintf str args resolveFunction' "sprintf" _ = throwPosError "sprintf(): Expects a string as its first argument" -- some custom functions-resolveFunction' "pdbresourcequery" [q] = pdbresourcequery q Nothing-resolveFunction' "pdbresourcequery" [q,k] = fmap Just (resolvePValueString k) >>= pdbresourcequery q-resolveFunction' "pdbresourcequery" _ = throwPosError "pdbresourcequery(): Expects one or two arguments"-resolveFunction' "hiera" [q] = hieraCall QFirst q Nothing Nothing Nothing-resolveFunction' "hiera" [q,d] = hieraCall QFirst q (Just d) Nothing Nothing-resolveFunction' "hiera" [q,d,o] = hieraCall QFirst q (Just d) Nothing (Just o)-resolveFunction' "hiera_array" [q] = hieraCall QUnique q Nothing Nothing Nothing-resolveFunction' "hiera_array" [q,d] = hieraCall QUnique q (Just d) Nothing Nothing-resolveFunction' "hiera_array" [q,d,o] = hieraCall QUnique q (Just d) Nothing (Just o)-resolveFunction' "hiera_hash" [q] = hieraCall QHash q Nothing Nothing Nothing-resolveFunction' "hiera_hash" [q,d] = hieraCall QHash q (Just d) Nothing Nothing-resolveFunction' "hiera_hash" [q,d,o] = hieraCall QHash q (Just d) Nothing (Just o)-resolveFunction' "lookup" [q] = hieraCall QFirst q Nothing Nothing Nothing-resolveFunction' "lookup" [q, PType dt] = hieraCall QFirst q Nothing (Just dt) Nothing-resolveFunction' "lookup" [q, PType dt, PString qt, def] =+resolveFunction' "pdbresourcequery" [q] = pdbresourcequery q Nothing+resolveFunction' "pdbresourcequery" [q, k] = resolvePValueString k >>= pdbresourcequery q . Just+resolveFunction' "pdbresourcequery" _ = throwPosError "pdbresourcequery(): Expects one or two arguments"+resolveFunction' "hiera" [q] = hieraCall QFirst q Nothing Nothing Nothing+resolveFunction' "hiera" [q, d] = hieraCall QFirst q (Just d) Nothing Nothing+resolveFunction' "hiera" [q, d, o] = hieraCall QFirst q (Just d) Nothing (Just o)+resolveFunction' "hiera_array" [q] = hieraCall QUnique q Nothing Nothing Nothing+resolveFunction' "hiera_array" [q, d] = hieraCall QUnique q (Just d) Nothing Nothing+resolveFunction' "hiera_array" [q, d, o] = hieraCall QUnique q (Just d) Nothing (Just o)+resolveFunction' "hiera_hash" [q] = hieraCall QHash q Nothing Nothing Nothing+resolveFunction' "hiera_hash" [q, d] = hieraCall QHash q (Just d) Nothing Nothing+resolveFunction' "hiera_hash" [q, d, o] = hieraCall QHash q (Just d) Nothing (Just o)+resolveFunction' "lookup" [q] = hieraCall QFirst q Nothing Nothing Nothing+resolveFunction' "lookup" [q, PType dt] = hieraCall QFirst q Nothing (Just dt) Nothing+resolveFunction' "lookup" [q, PType dt, PString qt, def] = case readQueryType qt of Nothing -> throwPosError ("Unknown merge strategy " <> ppline qt) Just qt' -> hieraCall qt' q (Just def) (Just dt) Nothing-resolveFunction' "lookup" _ = throwPosError "lookup(): Wrong set of arguments"-+resolveFunction' "lookup" _ = throwPosError "lookup(): Wrong set of arguments" -- user functions resolveFunction' fname args = Operational.singleton (ExternalFunction fname args) @@ -581,7 +598,7 @@ pdbresourcequery q mkey = do rrv <- case fromJSON (toJSON q) of Aeson.Success rq -> Operational.singleton (PDBGetResources rq)- Aeson.Error rr -> throwPosError ("Invalid resource query:" <+> ppstring rr)+ Aeson.Error rr -> throwPosError ("Invalid resource query:" <+> ppstring rr) rv <- case fromJSON (toJSON rrv) of Aeson.Success x -> pure x Aeson.Error rr -> throwPosError ("For some reason we could not convert a resource list to Puppet internal values!!" <+> ppstring rr <+> pretty rrv)@@ -592,10 +609,9 @@ Nothing -> throwPosError ("pdbresourcequery strange error, could not find key" <+> ppline k <+> "in" <+> pretty (PHash h)) extractSubHash _ x = throwPosError ("pdbresourcequery strange error, expected a hash, had" <+> pretty x) case mkey of- Nothing -> pure (PArray rv)+ Nothing -> pure (PArray rv) (Just k) -> fmap PArray (V.mapM (extractSubHash k) rv) - calcTemplate :: TemplateSource -> InterpreterMonad Text calcTemplate tplsrc = do interp_state <- use identity@@ -612,8 +628,8 @@ -- resolved 'RSearchExpression'. resolveSearchExpression :: SearchExpression -> InterpreterMonad RSearchExpression resolveSearchExpression AlwaysTrue = pure RAlwaysTrue-resolveSearchExpression (EqualitySearch a e) = REqualitySearch `fmap` pure a <*> resolveExpressionSE e-resolveSearchExpression (NonEqualitySearch a e) = RNonEqualitySearch `fmap` pure a <*> resolveExpressionSE e+resolveSearchExpression (EqualitySearch a e) = REqualitySearch a <$> resolveExpressionSE e+resolveSearchExpression (NonEqualitySearch a e) = RNonEqualitySearch a <$> resolveExpressionSE e resolveSearchExpression (AndSearch e1 e2) = RAndSearch `fmap` resolveSearchExpression e1 <*> resolveSearchExpression e2 resolveSearchExpression (OrSearch e1 e2) = ROrSearch `fmap` resolveSearchExpression e1 <*> resolveSearchExpression e2 @@ -621,16 +637,16 @@ -- be used in a PuppetDB query. searchExpressionToPuppetDB :: Text -> RSearchExpression -> Query ResourceField searchExpressionToPuppetDB rtype res =- QAnd ( QEqual RType (capitalizeRT rtype) : mkSE res )+ QAnd (QEqual RType (capitalizeRT rtype) : mkSE res) where- mkSE (RAndSearch a b) = [QAnd (mkSE a ++ mkSE b)]- mkSE (ROrSearch a b) = [QOr (mkSE a ++ mkSE b)]- mkSE (RNonEqualitySearch a b) = fmap QNot (mkSE (REqualitySearch a b))+ mkSE (RAndSearch a b) = [QAnd (mkSE a ++ mkSE b)]+ mkSE (ROrSearch a b) = [QOr (mkSE a ++ mkSE b)]+ mkSE (RNonEqualitySearch a b) = fmap QNot (mkSE (REqualitySearch a b)) mkSE (REqualitySearch a (PString b)) = [QEqual (mkFld a) b]- mkSE _ = []- mkFld "tag" = RTag+ mkSE _ = []+ mkFld "tag" = RTag mkFld "title" = RTitle- mkFld z = RParameter z+ mkFld z = RParameter z -- | Checks whether a given 'Resource' matches a 'RSearchExpression'. -- Note that the expression doesn't check for type, so you must filter the@@ -645,52 +661,54 @@ let nameequal = puppetEquality v (PString (r ^. rid . iname)) aliasequal = case r ^. rattributes . at "alias" of- Just a -> puppetEquality v a+ Just a -> puppetEquality v a Nothing -> False- in nameequal || aliasequal+ in nameequal || aliasequal checkSearchExpression (REqualitySearch attributename v) r = case r ^. rattributes . at attributename of- Nothing -> False+ Nothing -> False Just (PArray x) -> any (`puppetEquality` v) x- Just x -> puppetEquality x v+ Just x -> puppetEquality x v checkSearchExpression (RNonEqualitySearch attributename v) r- | attributename == "tag" = True- | attributename == "title" = not (checkSearchExpression (REqualitySearch attributename v) r)+ | attributename == "tag" = True+ | attributename == "title" = not (checkSearchExpression (REqualitySearch attributename v) r) | otherwise = case r ^. rattributes . at attributename of- Nothing -> True+ Nothing -> True Just (PArray x) -> not (all (`puppetEquality` v) x)- Just x -> not (puppetEquality x v)+ Just x -> not (puppetEquality x v) resolveDataType :: UDataType -> InterpreterMonad DataType-resolveDataType ud- = case ud of- UDTType -> pure DTType- UDTString a b -> pure (DTString a b)- UDTInteger a b -> pure (DTInteger a b)- UDTFloat a b -> pure (DTFloat a b)- UDTBoolean -> pure DTBoolean- UDTArray dt a b -> DTArray <$> resolveDataType dt <*> pure a <*> pure b- UDTHash dt1 dt2 a b -> DTHash <$> resolveDataType dt1 <*> resolveDataType dt2 <*> pure a <*> pure b- UDTUndef -> pure DTUndef- UDTScalar -> pure DTScalar- UDTData -> pure DTData- UDTOptional dt -> DTOptional <$> resolveDataType dt- UNotUndef -> pure NotUndef- UDTVariant vrs -> DTVariant <$> traverse resolveDataType vrs- UDTPattern a -> pure (DTPattern a)- -- will not crash as ens is nonempty- UDTEnum ens -> DTEnum . NE.fromList . sconcat <$> traverse resolveExpressionStrings ens- UDTAny -> pure DTAny- UDTCollection -> pure DTCollection- UDTRegexp mr -> pure (DTRegexp mr)+resolveDataType ud =+ case ud of+ UDTType -> pure DTType+ UDTString a b -> pure (DTString a b)+ UDTInteger a b -> pure (DTInteger a b)+ UDTFloat a b -> pure (DTFloat a b)+ UDTBoolean -> pure DTBoolean+ UDTArray dt a b -> DTArray <$> resolveDataType dt <*> pure a <*> pure b+ UDTHash dt1 dt2 a b -> DTHash <$> resolveDataType dt1 <*> resolveDataType dt2 <*> pure a <*> pure b+ UDTUndef -> pure DTUndef+ UDTScalar -> pure DTScalar+ UDTData -> pure DTData+ UDTOptional dt -> DTOptional <$> resolveDataType dt+ UNotUndef -> pure NotUndef+ UDTVariant vrs -> DTVariant <$> traverse resolveDataType vrs+ UDTPattern a -> pure (DTPattern a)+ -- will not crash as ens is nonempty+ UDTEnum ens -> DTEnum . NE.fromList . sconcat <$> traverse resolveExpressionStrings ens+ UDTAny -> pure DTAny+ UDTCollection -> pure DTCollection+ UDTRegexp mr -> pure (DTRegexp mr)+ UDTDeferred -> pure DTDeferred+ UDTSensitive dt -> DTOptional <$> resolveDataType dt -- | Generates variable associations for evaluation of blocks. -- Each item corresponds to an iteration in the calling block. hfGenerateAssociations :: HOLambdaCall -> InterpreterMonad [[(Text, PValue)]] hfGenerateAssociations hol = do sourceexpression <- case hol ^.. hoLambdaExpr . folded of- [x] -> pure x+ [x] -> pure x [] -> throwPosError ("No expression to run the function on" <+> pretty hol) _ -> throwPosError ("Too many expressions to run the function on" <+> pretty hol) sourcevalue <- resolveExpression sourceexpression@@ -699,20 +717,20 @@ dtype <- resolveDataType udtype mapM_ (\v -> unless (datatypeMatch dtype v) (throwPosError (pretty v <+> "isn't of type" <+> pretty dtype))) tocheck case (sourcevalue, V.toList (hol ^. hoLambdaParams)) of- (PArray pr, [LambdaParam mvtype varname]) -> do- check mvtype pr- pure (map (\x -> [(varname, x)]) (V.toList pr))- (PArray pr, [LambdaParam _ idx, LambdaParam mvtype var] ) -> do- check mvtype pr- pure [ [(idx,PString (Text.pack (show i))),(var,v)] | (i,v) <- zip ([0..] :: [Int]) (V.toList pr) ]- (PHash hh, [LambdaParam mvtype varname]) -> do- check mvtype hh- pure [ [(varname, PArray (V.fromList [PString k,v]))] | (k,v) <- HM.toList hh]- (PHash hh, [LambdaParam midxtype idx, LambdaParam mvtype var]) -> do- check mvtype hh- check midxtype (PString <$> HM.keys hh)- pure [ [(idx,PString k),(var,v)] | (k,v) <- HM.toList hh]- (invalid, _) -> throwPosError ("Can't iterate on this data type:" <+> pretty invalid)+ (PArray pr, [LambdaParam mvtype varname]) -> do+ check mvtype pr+ pure (map (\x -> [(varname, x)]) (V.toList pr))+ (PArray pr, [LambdaParam _ idx, LambdaParam mvtype var]) -> do+ check mvtype pr+ pure [[(idx, PString (Text.pack (show i))), (var, v)] | (i, v) <- zip ([0 ..] :: [Int]) (V.toList pr)]+ (PHash hh, [LambdaParam mvtype varname]) -> do+ check mvtype hh+ pure [[(varname, PArray (V.fromList [PString k, v]))] | (k, v) <- HM.toList hh]+ (PHash hh, [LambdaParam midxtype idx, LambdaParam mvtype var]) -> do+ check mvtype hh+ check midxtype (PString <$> HM.keys hh)+ pure [[(idx, PString k), (var, v)] | (k, v) <- HM.toList hh]+ (invalid, _) -> throwPosError ("Can't iterate on this data type:" <+> pretty invalid) -- | Sets the proper variables, and returns the scope variables the way -- they were before being modified. This is a hack that ensures that@@ -726,7 +744,7 @@ p <- use curPos container <- getCurContainer save <- use (scopes . ix scp . scopeVariables)- let hfSetvar (varname, varval) = scopes . ix scp . scopeVariables . at varname ?= (varval :!: p :!: (container ^. cctype))+ let hfSetvar (varname, varval) = scopes . ix scp . scopeVariables . at varname ?= ((varval :!: p) :!: (container ^. cctype)) mapM_ hfSetvar vals pure save @@ -753,10 +771,13 @@ then throwPosError ("The statement block must not be empty" <+> pretty hol) else case V.last statements of (MainFunctionDeclaration (MainFuncDecl fn args _)) ->- let expr = Terminal (UFunctionCall fn args)- in pure (hol & hoLambdaStatements %~ V.init- & hoLambdaLastExpr .~ S.Just expr- , expr)+ let expr = Terminal (UFunctionCall fn args)+ in pure+ ( hol+ & hoLambdaStatements %~ V.init+ & hoLambdaLastExpr .~ S.Just expr,+ expr+ ) _ -> throwPosError ("The statement block must end with an expression" <+> pretty hol) -- | All the "higher order function" stuff, for "value" mode. In this case@@ -766,11 +787,11 @@ (hol, finalexpression) <- transformPureHf hol' let runblock :: [(Text, PValue)] -> InterpreterMonad PValue runblock assocs = do- saved <- hfSetvars assocs- V.mapM_ evalPureStatement (hol ^. hoLambdaStatements)- r <- resolveExpression finalexpression- hfRestorevars saved- pure r+ saved <- hfSetvars assocs+ V.mapM_ evalPureStatement (hol ^. hoLambdaStatements)+ r <- resolveExpression finalexpression+ hfRestorevars saved+ pure r case hol ^. hoLambdaFunc of LambdaFunc "each" -> throwPosError "The 'each' function can't be used at the value level in language-puppet. Please use map." LambdaFunc "map" -> do@@ -779,7 +800,8 @@ LambdaFunc "with" -> do let expressions = hol ^. hoLambdaExpr parameters = hol ^. hoLambdaParams- unless (V.length expressions == V.length parameters)+ unless+ (V.length expressions == V.length parameters) (throwPosError ("Mismatched number of arguments and lambda parameters in" <> pretty hol)) assocs <- forM (V.zip expressions parameters) $ \(uval, LambdaParam mt name) -> do val <- resolveExpression uval@@ -794,19 +816,19 @@ res <- mapM (fmap pValue2Bool . runblock) varassocs sourcevalue <- case hol ^.. hoLambdaExpr . folded of [x] -> resolveExpression x- _ -> throwPosError "Internal error evaluateHFCPure 1"+ _ -> throwPosError "Internal error evaluateHFCPure 1" case sourcevalue of PArray ar -> pure $ PArray $ V.map fst $ V.filter snd $ V.zip ar (V.fromList res)- PHash hh -> pure $ PHash $ HM.fromList $ map fst $ filter snd $ zip (HM.toList hh) res- x -> throwPosError ("Can't iterate on this data type:" <+> pretty x)+ PHash hh -> pure $ PHash $ HM.fromList $ map fst $ filter snd $ zip (HM.toList hh) res+ x -> throwPosError ("Can't iterate on this data type:" <+> pretty x) LambdaFunc "reduce" -> case hol ^.. hoLambdaExpr . folded of [zero', foldable'] -> do zero'' <- resolveExpression zero' foldable'' <- resolveExpression foldable' (accname, curname) <- case hol ^.. hoLambdaParams . folded of- [an, cn] -> pure (an, cn)- _ -> throwPosError ("Reduce requires two parameters in its block expression, in" <+> pretty hol)+ [an, cn] -> pure (an, cn)+ _ -> throwPosError ("Reduce requires two parameters in its block expression, in" <+> pretty hol) let runStep acc cur = do accName <- mCheckType acc accname curName <- mCheckType cur curname@@ -819,7 +841,7 @@ case foldable'' of PArray ar -> foldM runStep zero'' ar PHash hs -> foldM runStep zero'' $ do- (k,v) <- HM.toList hs+ (k, v) <- HM.toList hs return (PArray (V.fromList [PString k, v])) x -> throwPosError ("Can't iterate on this data type:" <+> pretty x) _ -> throwPosError ("Reduce requires two arguments and a lambda block, in" <+> pretty hol)@@ -829,37 +851,39 @@ datatypeMatch :: DataType -> PValue -> Bool datatypeMatch dt v = case dt of- DTType -> has _PType v- DTUndef -> v == PUndef- NotUndef -> v /= PUndef- DTString mmin mmax -> boundedBy _PString Text.length mmin mmax- DTInteger mmin mmax -> boundedBy (_PNumber . to Scientific.toBoundedInteger . _Just) identity mmin mmax- DTFloat mmin mmax -> boundedBy _PNumber Scientific.toRealFloat mmin mmax- DTBoolean -> has _PBoolean v- DTArray sdt mi mmx -> container (_PArray . to V.toList) (datatypeMatch sdt) mi mmx- DTHash kt sdt mi mmx -> container (_PHash . to itoList) (\(k,a) -> datatypeMatch kt (PString k) && datatypeMatch sdt a) mi mmx- DTScalar -> datatypeMatch (DTVariant (DTInteger Nothing Nothing :| [DTString Nothing Nothing, DTBoolean])) v- DTData -> datatypeMatch (DTVariant (DTScalar :| [DTArray DTData 0 Nothing, DTHash DTScalar DTData 0 Nothing])) v- DTOptional sdt -> datatypeMatch (DTVariant (DTUndef :| [sdt])) v- DTVariant sdts -> any (`datatypeMatch` v) sdts- DTEnum lst -> maybe False (`elem` lst) (v ^? _PString)- DTAny -> True- DTCollection -> datatypeMatch (DTVariant (DTArray DTData 0 Nothing :| [DTHash DTScalar DTData 0 Nothing])) v- DTPattern patterns -> maybe False (\str -> any (checkPattern (Text.encodeUtf8 str)) patterns) (v ^? _PString)- DTRegexp mr -> case v ^? _PRegexp of- Nothing -> False- Just cr -> maybe True (== cr) mr+ DTType -> has _PType v+ DTUndef -> v == PUndef+ NotUndef -> v /= PUndef+ DTString mmin mmax -> boundedBy _PString Text.length mmin mmax+ DTInteger mmin mmax -> boundedBy (_PNumber . to Scientific.toBoundedInteger . _Just) identity mmin mmax+ DTFloat mmin mmax -> boundedBy _PNumber Scientific.toRealFloat mmin mmax+ DTBoolean -> has _PBoolean v+ DTArray sdt mi mmx -> container (_PArray . to V.toList) (datatypeMatch sdt) mi mmx+ DTHash kt sdt mi mmx -> container (_PHash . to itoList) (\(k, a) -> datatypeMatch kt (PString k) && datatypeMatch sdt a) mi mmx+ DTScalar -> datatypeMatch (DTVariant (DTInteger Nothing Nothing :| [DTString Nothing Nothing, DTBoolean])) v+ DTData -> datatypeMatch (DTVariant (DTScalar :| [DTArray DTData 0 Nothing, DTHash DTScalar DTData 0 Nothing])) v+ DTOptional sdt -> datatypeMatch (DTVariant (DTUndef :| [sdt])) v+ DTVariant sdts -> any (`datatypeMatch` v) sdts+ DTEnum lst -> maybe False (`elem` lst) (v ^? _PString)+ DTAny -> True+ DTCollection -> datatypeMatch (DTVariant (DTArray DTData 0 Nothing :| [DTHash DTScalar DTData 0 Nothing])) v+ DTPattern patterns -> maybe False (\str -> any (checkPattern (Text.encodeUtf8 str)) patterns) (v ^? _PString)+ DTRegexp mr -> case v ^? _PRegexp of+ Nothing -> False+ Just cr -> maybe True (== cr) mr+ DTDeferred -> True+ DTSensitive sdt -> datatypeMatch (DTVariant (DTUndef :| [sdt])) v where checkPattern str (CompRegex _ ptrn) = case Regex.execute' ptrn str of Right (Just _) -> True- _ -> False+ _ -> False container :: Fold PValue [a] -> (a -> Bool) -> Int -> Maybe Int -> Bool container f c mi mmx = let lst = v ^. f ln = length lst- in ln >= mi && (fmap (ln <=) mmx /= Just False) && all c lst- boundedBy :: Ord b => Fold PValue a -> (a -> b) -> Maybe b -> Maybe b -> Bool+ in ln >= mi && (fmap (ln <=) mmx /= Just False) && all c lst+ boundedBy :: (Ord b) => Fold PValue a -> (a -> b) -> Maybe b -> Maybe b -> Bool boundedBy prm f mmin mmax = fromMaybe False $ do vr <- f <$> v ^? prm@@ -870,15 +894,17 @@ typeOf :: PValue -> DataType typeOf pv =- case pv of- PBoolean _ -> DTBoolean- PUndef -> DTUndef- PString _ -> DTString Nothing Nothing- PResourceReference _ _ -> DTType -- ???- PArray _ -> DTArray DTAny 0 Nothing- PHash _ -> DTHash DTAny DTAny 0 Nothing- PType _ -> DTType- PRegexp _ -> DTRegexp Nothing- PNumber n -> if Scientific.isInteger n- then DTInteger Nothing Nothing- else DTFloat Nothing Nothing+ case pv of+ PSensitive x -> typeOf x+ PBoolean _ -> DTBoolean+ PUndef -> DTUndef+ PString _ -> DTString Nothing Nothing+ PResourceReference _ _ -> DTType -- ???+ PArray _ -> DTArray DTAny 0 Nothing+ PHash _ -> DTHash DTAny DTAny 0 Nothing+ PType _ -> DTType+ PRegexp _ -> DTRegexp Nothing+ PNumber n ->+ if Scientific.isInteger n+ then DTInteger Nothing Nothing+ else DTFloat Nothing Nothing
src/Puppet/Interpreter/Resolve/Sprintf.hs view
@@ -1,37 +1,39 @@-module Puppet.Interpreter.Resolve.Sprintf (- sprintf-) where-+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} -import XPrelude+{-# HLINT ignore "Use <$>" #-}+module Puppet.Interpreter.Resolve.Sprintf+ ( sprintf,+ )+where -import Data.Attoparsec.Text-import qualified Data.Text as Text-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TB-import qualified Data.Text.Lazy.Builder.Int as TB+import Data.Attoparsec.Text+import qualified Data.Text as Text+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TB+import qualified Data.Text.Lazy.Builder.Int as TB import qualified Data.Text.Lazy.Builder.Scientific as TB--import Puppet.Interpreter.Helpers-import Puppet.Interpreter.PrettyPrinter ()-import Puppet.Interpreter.Types+import Puppet.Interpreter.Helpers+import Puppet.Interpreter.PrettyPrinter ()+import Puppet.Interpreter.Types+import XPrelude data Flag = Minus | Plus | Space | Zero | Hash- deriving (Show, Eq)+ deriving (Show, Eq) data FLen = Lhh | Lh | Ll | Lll | LL | Lz | Lj | Lt- deriving (Show, Eq)+ deriving (Show, Eq) data FType = TPct | Td | Tu | Tf | TF | Te | TE | Tg | TG | Tx | TX | To | Ts | Tc | Tp | Ta | TA- deriving (Show, Eq)+ deriving (Show, Eq) data PrintfFormat = PrintfFormat- { _pfFlags :: [Flag]- , _pfWidth :: Maybe Int- , _pfPrec :: Maybe Int- , _pfLen :: Maybe FLen- , _pfType :: FType- } deriving (Show, Eq)+ { _pfFlags :: [Flag],+ _pfWidth :: Maybe Int,+ _pfPrec :: Maybe Int,+ _pfLen :: Maybe FLen,+ _pfType :: FType+ }+ deriving (Show, Eq) data FormatStringPart = Raw Text@@ -39,67 +41,71 @@ deriving (Show, Eq) parseFormat :: Text -> [FormatStringPart]-parseFormat t | Text.null t = []- | Text.null nxt = [Raw raw]- | otherwise = Raw raw : rformat+parseFormat t+ | Text.null t = []+ | Text.null nxt = [Raw raw]+ | otherwise = Raw raw : rformat where (raw, nxt) = Text.break (== '%') t tryNext = case parseFormat (Text.tail nxt) of- (Raw nt : nxt') -> Raw (Text.cons '%' nt) : nxt'- nxt' -> Raw (Text.singleton '%') : nxt'+ (Raw nt : nxt') -> Raw (Text.cons '%' nt) : nxt'+ nxt' -> Raw (Text.singleton '%') : nxt' rformat = case parse format nxt of- Fail _ _ _ -> tryNext- Partial _ -> tryNext- Done remaining f -> Format f : parseFormat remaining+ Fail {} -> tryNext+ Partial _ -> tryNext+ Done remaining f -> Format f : parseFormat remaining flag :: Parser Flag-flag = (Minus <$ char '-')- <|> (Plus <$ char '+')- <|> (Space <$ char ' ')- <|> (Zero <$ char '0')- <|> (Hash <$ char '#')+flag =+ (Minus <$ char '-')+ <|> (Plus <$ char '+')+ <|> (Space <$ char ' ')+ <|> (Zero <$ char '0')+ <|> (Hash <$ char '#') lenModifier :: Parser FLen-lenModifier = (Lhh <$ string "hh")- <|> (Lh <$ char 'h')- <|> (Lll <$ string "ll")- <|> (Ll <$ char 'l')- <|> (LL <$ char 'L')- <|> (Lz <$ char 'z')- <|> (Lj <$ char 'j')- <|> (Lt <$ char 't')+lenModifier =+ (Lhh <$ string "hh")+ <|> (Lh <$ char 'h')+ <|> (Lll <$ string "ll")+ <|> (Ll <$ char 'l')+ <|> (LL <$ char 'L')+ <|> (Lz <$ char 'z')+ <|> (Lj <$ char 'j')+ <|> (Lt <$ char 't') ftype :: Parser FType-ftype = (TPct <$ char '%')- <|> (Td <$ char 'd')- <|> (Td <$ char 'i')- <|> (Tu <$ char 'u')- <|> (Tf <$ char 'f')- <|> (TF <$ char 'F')- <|> (Te <$ char 'e')- <|> (TE <$ char 'E')- <|> (Tg <$ char 'g')- <|> (TG <$ char 'G')- <|> (Tx <$ char 'x')- <|> (TX <$ char 'X')- <|> (To <$ char 'o')- <|> (Ts <$ char 's')- <|> (Tc <$ char 'c')- <|> (Ta <$ char 'a')- <|> (Tp <$ char 'p')- <|> (TA <$ char 'A')+ftype =+ (TPct <$ char '%')+ <|> (Td <$ char 'd')+ <|> (Td <$ char 'i')+ <|> (Tu <$ char 'u')+ <|> (Tf <$ char 'f')+ <|> (TF <$ char 'F')+ <|> (Te <$ char 'e')+ <|> (TE <$ char 'E')+ <|> (Tg <$ char 'g')+ <|> (TG <$ char 'G')+ <|> (Tx <$ char 'x')+ <|> (TX <$ char 'X')+ <|> (To <$ char 'o')+ <|> (Ts <$ char 's')+ <|> (Tc <$ char 'c')+ <|> (Ta <$ char 'a')+ <|> (Tp <$ char 'p')+ <|> (TA <$ char 'A') format :: Parser PrintfFormat format = do- void $ char '%'- flags <- many flag- width <- optional decimal- prec <- optional $ do- void $ char '.'- decimal- len <- optional lenModifier- ft <- ftype- return (PrintfFormat flags width prec len ft)+ void $ char '%'+ flags <- many flag+ width <- optional decimal+ prec <- optional $ do+ void $ char '.'+ decimal+ len <- optional lenModifier+ ft <- ftype+ return (PrintfFormat flags width prec len ft) sprintf :: Text -> [PValue] -> InterpreterMonad PValue sprintf str oargs = PString . TL.toStrict . TB.toLazyText . mconcat <$> go (parseFormat str) oargs@@ -107,38 +113,40 @@ go (Raw x : xs) args = (TB.fromText x :) <$> go xs args go (Format f : _) _ | Hash `elem` _pfFlags f = throwPosError "sprintf: the # modifier is not supported" go (Format f : xs) (arg : args) = do- let numeric = case arg of- PNumber n -> pure n- PString s -> maybe (throwError "sprintf: Don't know how to convert this to a number") return (text2Scientific s)- _ -> throwError "sprintf: Don't know how to convert this to a number"- flags = _pfFlags f- sh mkBuilder n | has_ Minus = TL.justifyLeft padlen ' ' (sprefix <> content)- | has_ Plus && has_ Zero = sprefix <> TL.justifyRight mpadlen '0' content- | has_ Plus = TL.justifyRight padlen ' ' (sprefix <> content)- | has_ Zero = TL.justifyRight padlen '0' content- | otherwise = TL.justifyRight padlen ' ' content- where- (mpadlen, sprefix) | Plus `elem` flags && n >= 0 = (padlen - 1, "+")- | Space `elem` flags && n >= 0 = (padlen - 1, " ")- | otherwise = (padlen, mempty)- padlen = maybe 0 fromIntegral (_pfWidth f)- has_ flg = flg `elem` flags- content = TB.toLazyText (mkBuilder n)- baseString <- case _pfType f of- Td -> sh (TB.formatScientificBuilder TB.Fixed (Just 0)) <$> numeric- Tf -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric- TF -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric- Tg -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric- TG -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric- Te -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric- TE -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric- Tx -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric- TX -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric- Ts -> return $ case arg of- PString s -> TL.fromStrict s- _ -> TL.pack (show (pretty arg))- _ -> throwPosError "sprintf: not yet supported"- (TB.fromLazyText baseString :) <$> go xs args+ let numeric = case arg of+ PNumber n -> pure n+ PString s -> maybe (throwError "sprintf: Don't know how to convert this to a number") return (text2Scientific s)+ _ -> throwError "sprintf: Don't know how to convert this to a number"+ flags = _pfFlags f+ sh mkBuilder n+ | has_ Minus = TL.justifyLeft padlen ' ' (sprefix <> content)+ | has_ Plus && has_ Zero = sprefix <> TL.justifyRight mpadlen '0' content+ | has_ Plus = TL.justifyRight padlen ' ' (sprefix <> content)+ | has_ Zero = TL.justifyRight padlen '0' content+ | otherwise = TL.justifyRight padlen ' ' content+ where+ (mpadlen, sprefix)+ | Plus `elem` flags && n >= 0 = (padlen - 1, "+")+ | Space `elem` flags && n >= 0 = (padlen - 1, " ")+ | otherwise = (padlen, mempty)+ padlen = maybe 0 fromIntegral (_pfWidth f)+ has_ flg = flg `elem` flags+ content = TB.toLazyText (mkBuilder n)+ baseString <- case _pfType f of+ Td -> sh (TB.formatScientificBuilder TB.Fixed (Just 0)) <$> numeric+ Tf -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric+ TF -> sh (TB.formatScientificBuilder TB.Fixed (_pfPrec f)) <$> numeric+ Tg -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric+ TG -> sh (TB.formatScientificBuilder TB.Generic (_pfPrec f)) <$> numeric+ Te -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric+ TE -> sh (TB.formatScientificBuilder TB.Exponent (_pfPrec f)) <$> numeric+ Tx -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric+ TX -> sh (TB.hexadecimal . (truncate :: Scientific -> Integer)) <$> numeric+ Ts -> return $ case arg of+ PString s -> TL.fromStrict s+ _ -> TL.pack (show (pretty arg))+ _ -> throwPosError "sprintf: not yet supported"+ (TB.fromLazyText baseString :) <$> go xs args go [] [] = return [] go _ [] = throwPosError "sprintf: not enough arguments" go [] _ = [] <$ let msg = "sprintf: too many arguments" in checkStrict msg msg
src/Puppet/Interpreter/RubyRandom.hs view
@@ -1,20 +1,21 @@ module Puppet.Interpreter.RubyRandom- ( randInit- , limitedRand- ) where--import XPrelude+ ( randInit,+ limitedRand,+ )+where -import qualified Data.List as List-import qualified Data.Vector.Unboxed as V+import qualified Data.List as List+import qualified Data.Vector.Unboxed as V import qualified Data.Vector.Unboxed.Mutable as VM+import XPrelude data RandState = RandState- { _array :: V.Vector Int- , _left :: Int- , _initf :: Int- , _next :: Int- } deriving (Show)+ { _array :: V.Vector Int,+ _left :: Int,+ _initf :: Int,+ _next :: Int+ }+ deriving (Show) mixbits :: Int -> Int -> Int mixbits u v = (u .&. 0x80000000) .|. (v .&. 0x7fffffff)@@ -29,6 +30,7 @@ valN :: Int valN = 624+ valM :: Int valM = 397 @@ -41,84 +43,88 @@ nextState :: RandState -> RandState nextState (RandState array _ initf _) = RandState narray valN 1 0- where- rarray = if initf == 0- then _array (initGenrand 5489)- else array- narray = V.modify (\v -> twist1 v >> twist2 v >> final v) rarray- twist1 v = mapM_ (twist' valM v) [0..(valN - valM - 1)]- twist2 v = mapM_ (twist' (valM - valN) v) [(valN - valM) .. (valN - 2)]- final v = do- a <- VM.read v (valN - 1)- b <- VM.read v 0- pm <- VM.read v (valM - 1)- let res = pm `xor` twist a b- VM.write v (valN - 1) res- twist' idx v n = do- a <- VM.read v n- b <- VM.read v (n+1)- pm <- VM.read v (idx + n)- let res = pm `xor` twist a b- VM.write v n res+ where+ rarray =+ if initf == 0+ then _array (initGenrand 5489)+ else array+ narray = V.modify (\v -> twist1 v >> twist2 v >> final v) rarray+ twist1 v = mapM_ (twist' valM v) [0 .. (valN - valM - 1)]+ twist2 v = mapM_ (twist' (valM - valN) v) [(valN - valM) .. (valN - 2)]+ final v = do+ a <- VM.read v (valN - 1)+ b <- VM.read v 0+ pm <- VM.read v (valM - 1)+ let res = pm `xor` twist a b+ VM.write v (valN - 1) res+ twist' idx v n = do+ a <- VM.read v n+ b <- VM.read v (n + 1)+ pm <- VM.read v (idx + n)+ let res = pm `xor` twist a b+ VM.write v n res -- needs refactoring, too tedious for me initGenrandBigint :: Integer -> RandState initGenrandBigint seed =- let intarray = unfoldr reduceint seed- reduceint :: Integer -> Maybe (Integer, Integer)- reduceint 0 = Nothing- reduceint x = Just (x .&. 0xffffffff, x `shiftR` 32)- initstate = _array (initGenrand 19650218)- keylist = concat (repeat intarray)- jlist = concat (repeat [0..(length intarray - 1)])- kmax = max (length intarray) valN- state1 = foldl' apply1 initstate (List.zip3 keylist jlist [1..kmax])- apply1 :: V.Vector Int -> (Integer, Int, Int) -> V.Vector Int- apply1 ra (initKey, j, ri) =- let (a, i, sti, stim) = rollover ra ri- nsti = ((sti `xor` ((stim `xor` (stim `shiftR` 30)) * 1664525)) + fromIntegral initKey + j) .&. 0xffffffff- in a V.// [(i,nsti)]- state2 = foldl' apply2 state1 [2..valN]- rollover :: V.Vector Int -> Int -> (V.Vector Int, Int, Int, Int)- rollover ra ri =- let (a,i) = if ri >= valN- then (ra V.// [(0, ra V.! (valN-1))],1)- else (ra,ri)- in (a,i,a V.! i, a V.! (i-1))- apply2 :: V.Vector Int -> Int -> V.Vector Int- apply2 ra ri =- let (a, i, sti, stim) = rollover ra ri- nsti = ((sti `xor` ((stim `xor` (stim `shiftR` 30)) * 1566083941)) - i) .&. 0xffffffff- in a V.// [(i,nsti)]- in RandState (state2 V.// [(0,0x80000000)]) 1 1 0-+ let intarray = unfoldr reduceint seed+ reduceint :: Integer -> Maybe (Integer, Integer)+ reduceint 0 = Nothing+ reduceint x = Just (x .&. 0xffffffff, x `shiftR` 32)+ initstate = _array (initGenrand 19650218)+ keylist = concat (repeat intarray)+ jlist = concat (repeat [0 .. (length intarray - 1)])+ kmax = max (length intarray) valN+ state1 = foldl' apply1 initstate (List.zip3 keylist jlist [1 .. kmax])+ apply1 :: V.Vector Int -> (Integer, Int, Int) -> V.Vector Int+ apply1 ra (initKey, j, ri) =+ let (a, i, sti, stim) = rollover ra ri+ nsti = ((sti `xor` ((stim `xor` (stim `shiftR` 30)) * 1664525)) + fromIntegral initKey + j) .&. 0xffffffff+ in a V.// [(i, nsti)]+ state2 = foldl' apply2 state1 [2 .. valN]+ rollover :: V.Vector Int -> Int -> (V.Vector Int, Int, Int, Int)+ rollover ra ri =+ let (a, i) =+ if ri >= valN+ then (ra V.// [(0, ra V.! (valN - 1))], 1)+ else (ra, ri)+ in (a, i, a V.! i, a V.! (i - 1))+ apply2 :: V.Vector Int -> Int -> V.Vector Int+ apply2 ra ri =+ let (a, i, sti, stim) = rollover ra ri+ nsti = ((sti `xor` ((stim `xor` (stim `shiftR` 30)) * 1566083941)) - i) .&. 0xffffffff+ in a V.// [(i, nsti)]+ in RandState (state2 V.// [(0, 0x80000000)]) 1 1 0 randInit :: Integer -> RandState-randInit x = if x <= 0xffffffff- then initGenrand x- else initGenrandBigint x+randInit x =+ if x <= 0xffffffff+ then initGenrand x+ else initGenrandBigint x limitedRand :: RandState -> Int -> (Int, RandState)-limitedRand s n | n <= 0 = (0, s)- | otherwise = limitedRand' s- where- masked = foldl' (\x pow -> x .|. (x `shiftR` pow)) (n - 1) [1,2,4,8,16,32]- limitedRand' s' =- let (rval, ns) = rbGenrandInt32 s'- val = rval .&. masked- in if n <= val- then limitedRand' ns- else (val, ns)- rbGenrandInt32 :: RandState -> (Int, RandState)- rbGenrandInt32 st =- let rst = if _left st == 1- then nextState st- else st { _left = _left st - 1 }- next = _next rst- cv = _array rst V.! next- nst = rst { _next = next + 1 }- y1 = cv `xor` (cv `shiftR` 11)- y2 = y1 `xor` ((y1 `shiftL` 7) .&. 0x9d2c5680)- y3 = y2 `xor` ((y2 `shiftL` 15) .&. 0xefc60000)- y4 = y3 `xor` (y3 `shiftR` 18)- in (y4,nst)+limitedRand s n+ | n <= 0 = (0, s)+ | otherwise = limitedRand' s+ where+ masked = foldl' (\x pow -> x .|. (x `shiftR` pow)) (n - 1) [1, 2, 4, 8, 16, 32]+ limitedRand' s' =+ let (rval, ns) = rbGenrandInt32 s'+ val = rval .&. masked+ in if n <= val+ then limitedRand' ns+ else (val, ns)+ rbGenrandInt32 :: RandState -> (Int, RandState)+ rbGenrandInt32 st =+ let rst =+ if _left st == 1+ then nextState st+ else st {_left = _left st - 1}+ next = _next rst+ cv = _array rst V.! next+ nst = rst {_next = next + 1}+ y1 = cv `xor` (cv `shiftR` 11)+ y2 = y1 `xor` ((y1 `shiftL` 7) .&. 0x9d2c5680)+ y3 = y2 `xor` ((y2 `shiftL` 15) .&. 0xefc60000)+ y4 = y3 `xor` (y3 `shiftR` 18)+ in (y4, nst)
src/Puppet/Interpreter/Types.hs view
@@ -1,115 +1,120 @@-{-# LANGUAGE AutoDeriveTypeable #-}-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-} -module Puppet.Interpreter.Types (- -- * Operational state- InterpreterState(InterpreterState)- , scopes- , definedResources- , nestedDeclarations- , resModifiers- , extraRelations- , curScope- , curPos- , loadedClasses- -- * Operational reader- , InterpreterReader(InterpreterReader)- , readerNativeTypes- , readerGetStatement- , readerGetTemplate- , readerPdbApi- , readerExternalFunc- , readerNodename- , readerHieraQuery- , readerIoMethods- , readerIgnoredModules- , readerExternalModules- , readerIsStrict- , readerPuppetPaths- , readerFacts- , readerRebaseFile- -- * Interpreter monad- , InterpreterMonad- , InterpreterWriter- , InterpreterInstr(..)- , Strictness(..)- -- * Io methods- , IoMethods(IoMethods)- , ioGetCurrentCallStack- , ioReadFile- , ioTraceEvent- , MonadThrowPos(..)- -- * Resource modifier- , ResourceModifier(ResourceModifier)- , rmResType- , rmDeclaration- , rmSearch- , rmType- , rmMutation- , rmModifierType- , ModifierType(..)- , OverrideType(..)- , ResourceCollectorType(..)- , ClassIncludeType(..)- , RSearchExpression(..)- -- * Scope information- , ScopeInformation(ScopeInformation)- , scopeResDefaults- , scopeVariables- , scopeParent- , scopeOverrides- , scopeContainer- , scopeExtraTags- , CurContainer(CurContainer)- , cctype- , cctags- -- * Resource default- , ResDefaults(ResDefaults)- , resDefValues- , resDefSrcScope- , resDefPos- , resDefType- , ResRefOverride(..)- , ScopeEnteringContext(..)- , TopLevelType(..)- -- * Hiera- , HieraQueryLayers(..)- , globalLayer- , environmentLayer- , moduleLayer- -- * Template- , TemplateSource(..)- -- * Re-export- , module Puppet.Language-) where+module Puppet.Interpreter.Types+ ( -- * Operational state+ InterpreterState (InterpreterState),+ scopes,+ definedResources,+ nestedDeclarations,+ resModifiers,+ extraRelations,+ curScope,+ curPos,+ loadedClasses, -import XPrelude.Extra-import XPrelude.PP+ -- * Operational reader+ InterpreterReader (InterpreterReader),+ readerNativeTypes,+ readerGetStatement,+ readerGetTemplate,+ readerPdbApi,+ readerExternalFunc,+ readerNodename,+ readerHieraQuery,+ readerIoMethods,+ readerIgnoredModules,+ readerExternalModules,+ readerIsStrict,+ readerPuppetPaths,+ readerFacts,+ readerRebaseFile, + -- * Interpreter monad+ InterpreterMonad,+ InterpreterWriter,+ InterpreterInstr (..),+ Strictness (..),++ -- * Io methods+ IoMethods (IoMethods),+ ioGetCurrentCallStack,+ ioReadFile,+ ioTraceEvent,+ MonadThrowPos (..),++ -- * Resource modifier+ ResourceModifier (ResourceModifier),+ rmResType,+ rmDeclaration,+ rmSearch,+ rmType,+ rmMutation,+ rmModifierType,+ ModifierType (..),+ OverrideType (..),+ ResourceCollectorType (..),+ ClassIncludeType (..),+ RSearchExpression (..),++ -- * Scope information+ ScopeInformation (ScopeInformation),+ scopeResDefaults,+ scopeVariables,+ scopeParent,+ scopeOverrides,+ scopeContainer,+ scopeExtraTags,+ CurContainer (CurContainer),+ cctype,+ cctags,++ -- * Resource default+ ResDefaults (ResDefaults),+ resDefValues,+ resDefSrcScope,+ resDefPos,+ resDefType,+ ResRefOverride (..),+ ScopeEnteringContext (..),+ TopLevelType (..),++ -- * Hiera+ HieraQueryLayers (..),+ globalLayer,+ environmentLayer,+ moduleLayer,++ -- * Template+ TemplateSource (..),++ -- * Re-export+ module Puppet.Language,+ )+where+ import qualified Control.Monad.Fail as Fail-import Control.Monad.Operational-import Control.Monad.State.Strict-import Control.Monad.Writer.Class-import Data.Aeson as A-import qualified Data.Either.Strict as S-import qualified Data.HashMap.Strict as HM-import qualified Data.List as List-import qualified Data.Maybe.Strict as S+import Control.Monad.Operational+import Control.Monad.State.Strict+import Control.Monad.Writer.Class+import Data.Aeson as A+import qualified Data.Either.Strict as S+import qualified Data.HashMap.Strict as HM+import qualified Data.List as List+import qualified Data.Maybe.Strict as S+import Facter import qualified GHC.Show import qualified GHC.Stack-import qualified System.Log.Logger as Log--import Facter-import Hiera.Server-import Puppet.Language-import Puppet.Parser.Types-import PuppetDB-+import Hiera.Server+import Puppet.Language+import Puppet.Parser.Types+import PuppetDB+import qualified System.Log.Logger as Log+import XPrelude.Extra+import XPrelude.PP -- | The intepreter can run in two modes : a strict mode (recommended), and -- a permissive mode.@@ -119,9 +124,9 @@ deriving (Show, Eq) instance FromJSON Strictness where- parseJSON (Bool True) = pure Strict+ parseJSON (Bool True) = pure Strict parseJSON (Bool False) = pure Permissive- parseJSON _ = mzero+ parseJSON _ = mzero data RSearchExpression = REqualitySearch !Text !PValue@@ -135,69 +140,78 @@ -- -- See <https://docs.puppetlabs.com/puppet/latest/reference/lang_classes.html#include-like-vs-resource-like puppet reference>. data ClassIncludeType- = ClassIncludeLike -- ^ Using the include or contain function- | ClassResourceLike -- ^ Resource like declaration+ = -- | Using the include or contain function+ ClassIncludeLike+ | -- | Resource like declaration+ ClassResourceLike deriving (Eq) -- | Differentiate the distinct top level types such as node, define or class. data TopLevelType- = TopNode -- ^ For node entries- | TopDefine -- ^ For defines- | TopClass -- ^ For classes+ = -- | For node entries+ TopNode+ | -- | For defines+ TopDefine+ | -- | For classes+ TopClass deriving (Generic, Eq) instance Hashable TopLevelType -- | From the evaluation of Resource Default Declaration. data ResDefaults = ResDefaults- { _resDefType :: !Text- , _resDefSrcScope :: !Text- , _resDefValues :: !(Container PValue)- , _resDefPos :: !PPosition+ { _resDefType :: !Text,+ _resDefSrcScope :: !Text,+ _resDefValues :: !(Container PValue),+ _resDefPos :: !PPosition } -- | From the evaluation of Resource Override Declaration. data ResRefOverride = ResRefOverride- { _rrid :: !RIdentifier- , _rrparams :: !(Container PValue)- , _rrpos :: !PPosition- } deriving (Eq)+ { _rrid :: !RIdentifier,+ _rrparams :: !(Container PValue),+ _rrpos :: !PPosition+ }+ deriving (Eq) data ScopeEnteringContext = SENormal- | SEChild !Text -- ^ We enter the scope as the child of another class- | SEParent !Text -- ^ We enter the scope as the parent of another class+ | -- | We enter the scope as the child of another class+ SEChild !Text+ | -- | We enter the scope as the parent of another class+ SEParent !Text -- | The type of the container together with its tags. data CurContainer = CurContainer- { _cctype :: !CurContainerDesc- , _cctags :: !(HashSet Text)- } deriving (Eq)+ { _cctype :: !CurContainerDesc,+ _cctags :: !(HashSet Text)+ }+ deriving (Eq) data ScopeInformation = ScopeInformation- { _scopeVariables :: !(Container (Pair (Pair PValue PPosition) CurContainerDesc))- , _scopeResDefaults :: !(Container ResDefaults)- , _scopeExtraTags :: !(HashSet Text)- , _scopeContainer :: !CurContainer- , _scopeOverrides :: !(HashMap RIdentifier ResRefOverride)- , _scopeParent :: !(S.Maybe Text)+ { _scopeVariables :: !(Container (Pair (Pair PValue PPosition) CurContainerDesc)),+ _scopeResDefaults :: !(Container ResDefaults),+ _scopeExtraTags :: !(HashSet Text),+ _scopeContainer :: !CurContainer,+ _scopeOverrides :: !(HashMap RIdentifier ResRefOverride),+ _scopeParent :: !(S.Maybe Text) } data InterpreterState = InterpreterState- { _scopes :: !(Container ScopeInformation)- , _loadedClasses :: !(Container (Pair ClassIncludeType PPosition))- , _definedResources :: !(HM.HashMap RIdentifier Resource)- , _curScope :: ![CurContainerDesc]- , _curPos :: !PPosition- , _nestedDeclarations :: !(HashMap (TopLevelType, Text) Statement)- , _extraRelations :: ![LinkInformation]- , _resModifiers :: ![ResourceModifier]+ { _scopes :: !(Container ScopeInformation),+ _loadedClasses :: !(Container (Pair ClassIncludeType PPosition)),+ _definedResources :: !(HM.HashMap RIdentifier Resource),+ _curScope :: ![CurContainerDesc],+ _curPos :: !PPosition,+ _nestedDeclarations :: !(HashMap (TopLevelType, Text) Statement),+ _extraRelations :: ![LinkInformation],+ _resModifiers :: ![ResourceModifier] } data IoMethods m = IoMethods- { _ioGetCurrentCallStack :: m [String]- , _ioReadFile :: [Text] -> m (Either String Text)- , _ioTraceEvent :: String -> m ()+ { _ioGetCurrentCallStack :: m [String],+ _ioReadFile :: [Text] -> m (Either String Text),+ _ioTraceEvent :: String -> m () } -- | All available queries including the global and module layer@@ -205,73 +219,76 @@ -- -- The datatype belongs to the "Puppet.Interpreter" module because it serves to implement how Hiera is used within Puppet. data HieraQueryLayers m = HieraQueryLayers- { _globalLayer :: HieraQueryFunc m- , _environmentLayer :: HieraQueryFunc m- , _moduleLayer :: Container (HieraQueryFunc m)+ { _globalLayer :: HieraQueryFunc m,+ _environmentLayer :: HieraQueryFunc m,+ _moduleLayer :: Container (HieraQueryFunc m) } -- | Whether the template source is specified 'inline' or loaded from a file.-data TemplateSource= Inline Text | Filename FilePath+data TemplateSource = Inline Text | Filename FilePath data InterpreterReader m = InterpreterReader- { _readerNativeTypes :: !(Container NativeTypeMethods)- , _readerGetStatement :: TopLevelType -> Text -> m (S.Either PrettyError Statement) -- ^ Access to parsed statements- , _readerGetTemplate :: TemplateSource -> InterpreterState -> InterpreterReader m -> m (S.Either PrettyError Text)- , _readerPdbApi :: PuppetDBAPI m- , _readerExternalFunc :: Container ([PValue] -> InterpreterMonad PValue) -- ^ External func such as stdlib or puppetlabs- , _readerNodename :: Text- , _readerHieraQuery :: HieraQueryLayers m- , _readerIoMethods :: IoMethods m- , _readerIgnoredModules :: HashSet Text- , _readerExternalModules :: HashSet Text- , _readerIsStrict :: Bool- , _readerPuppetPaths :: PuppetDirPaths- , _readerRebaseFile :: Maybe FilePath- , _readerFacts :: Container PValue -- ^ Access to the list of facts that were given to the 'Preferences' module+ { _readerNativeTypes :: !(Container NativeTypeMethods),+ -- | Access to parsed statements+ _readerGetStatement :: TopLevelType -> Text -> m (S.Either PrettyError Statement),+ _readerGetTemplate :: TemplateSource -> InterpreterState -> InterpreterReader m -> m (S.Either PrettyError Text),+ _readerPdbApi :: PuppetDBAPI m,+ -- | External func such as stdlib or puppetlabs+ _readerExternalFunc :: Container ([PValue] -> InterpreterMonad PValue),+ _readerNodename :: Text,+ _readerHieraQuery :: HieraQueryLayers m,+ _readerIoMethods :: IoMethods m,+ _readerIgnoredModules :: HashSet Text,+ _readerExternalModules :: HashSet Text,+ _readerIsStrict :: Bool,+ _readerPuppetPaths :: PuppetDirPaths,+ _readerRebaseFile :: Maybe FilePath,+ -- | Access to the list of facts that were given to the 'Preferences' module+ _readerFacts :: Container PValue } data InterpreterInstr a where -- Utility for using what's in 'InterpreterReader'- GetNativeTypes :: InterpreterInstr (Container NativeTypeMethods)- GetStatement :: TopLevelType -> Text -> InterpreterInstr Statement- ComputeTemplate :: TemplateSource-> InterpreterState -> InterpreterInstr Text- ExternalFunction :: Text -> [PValue] -> InterpreterInstr PValue- Facts :: InterpreterInstr (Container PValue)- GetNodeName :: InterpreterInstr Text- HieraQuery :: Container PValue -> Text -> HieraQueryType -> InterpreterInstr (Maybe PValue)- GetCurrentCallStack :: InterpreterInstr [String]- IsIgnoredModule :: Text -> InterpreterInstr Bool- IsExternalModule :: Text -> InterpreterInstr Bool- IsStrict :: InterpreterInstr Bool- PuppetPaths :: InterpreterInstr PuppetDirPaths- RebaseFile :: InterpreterInstr (Maybe FilePath)+ GetNativeTypes :: InterpreterInstr (Container NativeTypeMethods)+ GetStatement :: TopLevelType -> Text -> InterpreterInstr Statement+ ComputeTemplate :: TemplateSource -> InterpreterState -> InterpreterInstr Text+ ExternalFunction :: Text -> [PValue] -> InterpreterInstr PValue+ Facts :: InterpreterInstr (Container PValue)+ GetNodeName :: InterpreterInstr Text+ HieraQuery :: Container PValue -> Text -> HieraQueryType -> InterpreterInstr (Maybe PValue)+ GetCurrentCallStack :: InterpreterInstr [String]+ IsIgnoredModule :: Text -> InterpreterInstr Bool+ IsExternalModule :: Text -> InterpreterInstr Bool+ IsStrict :: InterpreterInstr Bool+ PuppetPaths :: InterpreterInstr PuppetDirPaths+ RebaseFile :: InterpreterInstr (Maybe FilePath) -- error- ErrorThrow :: PrettyError -> InterpreterInstr a- ErrorCatch :: InterpreterMonad a -> (PrettyError -> InterpreterMonad a) -> InterpreterInstr a+ ErrorThrow :: PrettyError -> InterpreterInstr a+ ErrorCatch :: InterpreterMonad a -> (PrettyError -> InterpreterMonad a) -> InterpreterInstr a -- writer- WriterTell :: InterpreterWriter -> InterpreterInstr ()- WriterPass :: InterpreterMonad (a, InterpreterWriter -> InterpreterWriter) -> InterpreterInstr a- WriterListen :: InterpreterMonad a -> InterpreterInstr (a, InterpreterWriter)+ WriterTell :: InterpreterWriter -> InterpreterInstr ()+ WriterPass :: InterpreterMonad (a, InterpreterWriter -> InterpreterWriter) -> InterpreterInstr a+ WriterListen :: InterpreterMonad a -> InterpreterInstr (a, InterpreterWriter) -- puppetdb wrappers , see 'PuppetDBAPI' for details- PDBInformation :: InterpreterInstr Doc- PDBReplaceCatalog :: WireCatalog -> InterpreterInstr ()- PDBReplaceFacts :: [(NodeName, Facts)] -> InterpreterInstr ()- PDBDeactivateNode :: NodeName -> InterpreterInstr ()- PDBGetFacts :: Query FactField -> InterpreterInstr [FactInfo]- PDBGetResources :: Query ResourceField -> InterpreterInstr [Resource]- PDBGetNodes :: Query NodeField -> InterpreterInstr [NodeInfo]- PDBCommitDB :: InterpreterInstr ()+ PDBInformation :: InterpreterInstr Doc+ PDBReplaceCatalog :: WireCatalog -> InterpreterInstr ()+ PDBReplaceFacts :: [(NodeName, Facts)] -> InterpreterInstr ()+ PDBDeactivateNode :: NodeName -> InterpreterInstr ()+ PDBGetFacts :: Query FactField -> InterpreterInstr [FactInfo]+ PDBGetResources :: Query ResourceField -> InterpreterInstr [Resource]+ PDBGetNodes :: Query NodeField -> InterpreterInstr [NodeInfo]+ PDBCommitDB :: InterpreterInstr () PDBGetResourcesOfNode :: NodeName -> Query ResourceField -> InterpreterInstr [Resource] -- Reading the first file that can be read in a list- ReadFile :: [Text] -> InterpreterInstr Text+ ReadFile :: [Text] -> InterpreterInstr Text -- Tracing events- TraceEvent :: String -> InterpreterInstr ()+ TraceEvent :: String -> InterpreterInstr () -- | The main monad type InterpreterMonad = ProgramT InterpreterInstr (State InterpreterState) instance Fail.MonadFail InterpreterMonad where- fail = throwError . PrettyError . ppstring+ fail = throwError . PrettyError . ppstring instance MonadError PrettyError InterpreterMonad where throwError = singleton . ErrorThrow@@ -279,33 +296,40 @@ -- | Log type InterpreterWriter = [Pair Log.Priority Doc]+ instance MonadWriter InterpreterWriter InterpreterMonad where tell = singleton . WriterTell pass = singleton . WriterPass listen = singleton . WriterListen data ResourceModifier = ResourceModifier- { _rmResType :: !Text- , _rmModifierType :: !ModifierType- , _rmType :: !ResourceCollectorType- , _rmSearch :: !RSearchExpression- , _rmMutation :: !(Resource -> InterpreterMonad Resource)- , _rmDeclaration :: !PPosition+ { _rmResType :: !Text,+ _rmModifierType :: !ModifierType,+ _rmType :: !ResourceCollectorType,+ _rmSearch :: !RSearchExpression,+ _rmMutation :: !(Resource -> InterpreterMonad Resource),+ _rmDeclaration :: !PPosition } instance Show ResourceModifier where show (ResourceModifier rt mt ct se _ p) = List.unwords ["ResourceModifier", show rt, show mt, show ct, "(" ++ show se ++ ")", "???", show p] data ModifierType- = ModifierCollector -- ^ For collectors, optional resources- | ModifierMustMatch -- ^ For stuff like realize+ = -- | For collectors, optional resources+ ModifierCollector+ | -- | For stuff like realize+ ModifierMustMatch deriving (Show, Eq) data OverrideType- = CantOverride -- ^ Overriding forbidden, will throw an error- | Replace -- ^ Can silently replace- | CantReplace -- ^ Silently ignore errors- | AppendAttribute -- ^ Can append values+ = -- | Overriding forbidden, will throw an error+ CantOverride+ | -- | Can silently replace+ Replace+ | -- | Silently ignore errors+ CantReplace+ | -- | Can append values+ AppendAttribute deriving (Show, Eq) data ResourceCollectorType@@ -323,8 +347,7 @@ makeLenses ''ScopeInformation makeLenses ''InterpreterState --class Monad m => MonadThrowPos m where+class (Monad m) => MonadThrowPos m where throwPosError :: Doc -> m a -- Useful for mocking for instance in a REPL@@ -341,7 +364,8 @@ throwPosError s = do p <- use (curPos . _1) stack <- getCurrentCallStack- let dstack = if null stack- then line- else mempty </> ppstring (GHC.Stack.renderStack stack)+ let dstack =+ if null stack+ then line+ else mempty </> ppstring (GHC.Stack.renderStack stack) throwError (PrettyError (s <+> "at" <+> showPos p <> dstack))
src/Puppet/Language.hs view
@@ -3,19 +3,19 @@ -- This module doesn't depend on any other project modules (except for "XPrelude"). -- It serves as a common bridge that can be used in "PuppetDB" or "Facter" as well as in -- "Puppet.Interpreter" or "Puppet.Parser".-module Puppet.Language(- module Puppet.Language.Core- , module Puppet.Language.NativeTypes- , module Puppet.Language.Paths- , module Puppet.Language.Resource- , module Puppet.Language.Value- , module Puppet.Language.WireCatalog-)+module Puppet.Language+ ( module Puppet.Language.Core,+ module Puppet.Language.NativeTypes,+ module Puppet.Language.Paths,+ module Puppet.Language.Resource,+ module Puppet.Language.Value,+ module Puppet.Language.WireCatalog,+ ) where import Puppet.Language.Core+import Puppet.Language.NativeTypes+import Puppet.Language.Paths import Puppet.Language.Resource import Puppet.Language.Value import Puppet.Language.WireCatalog-import Puppet.Language.NativeTypes-import Puppet.Language.Paths
src/Puppet/Language/Core.hs view
@@ -1,16 +1,13 @@-module Puppet.Language.Core--where--import XPrelude+module Puppet.Language.Core where -import Data.Aeson-import qualified Data.Char as Char+import Data.Aeson+import qualified Data.Char as Char import qualified Data.HashMap.Strict as Map-import qualified Data.Text as Text-import qualified Data.Tuple.Strict as Tuple-import qualified GHC.Show as Show (Show (..))-import Text.Megaparsec.Pos+import qualified Data.Text as Text+import qualified Data.Tuple.Strict as Tuple+import qualified GHC.Show as Show (Show (..))+import Text.Megaparsec.Pos+import XPrelude showPos :: Position -> Doc showPos = blue . pptext . sourcePosPretty@@ -26,8 +23,8 @@ -- | Generates an initial position interval based on a filename. initialPPos :: FilePath -> PPosition initialPPos x =- let i = initialPos x- in (i :!: i)+ let i = initialPos x+ in (i :!: i) -- | A pair containing the start and end of a given token. type PPosition = Pair Position Position@@ -36,6 +33,7 @@ type Position = SourcePos type NodeName = Text+ type Scope = Text data CompRegex = CompRegex !Text !Regex@@ -44,7 +42,7 @@ show (CompRegex t _) = show t instance Eq CompRegex where- (CompRegex a _) == (CompRegex b _) = a == b+ (CompRegex a _) == (CompRegex b _) = a == b instance FromJSON CompRegex where parseJSON = panic "Can't deserialize a regular expression"@@ -75,37 +73,37 @@ capitalizeRT = Text.intercalate "::" . map capitalize' . Text.splitOn "::" where capitalize' :: Text -> Text- capitalize' t | Text.null t = Text.empty- | otherwise = Text.cons (Char.toUpper (Text.head t)) (Text.tail t)+ capitalize' t+ | Text.null t = Text.empty+ | otherwise = Text.cons (Char.toUpper (Text.head t)) (Text.tail t) -containerComma'' :: Pretty a => [(Doc, a)] -> Doc+containerComma'' :: (Pretty a) => [(Doc, a)] -> Doc containerComma'' x = indent 4 ins where- ins = mconcat $ intersperse (comma <> line <> mempty) (fmap showC x)- showC (a,b) = a <+> "=>" <+> pretty b+ ins = mconcat $ intersperse (comma <> line) (fmap showC x)+ showC (a, b) = a <+> "=>" <+> pretty b -containerComma' :: Pretty a => [(Doc, a)] -> Doc+containerComma' :: (Pretty a) => [(Doc, a)] -> Doc containerComma' = braces . containerComma'' -containerComma :: Pretty a => Container a -> Doc-containerComma hm = containerComma' (fmap (\(a,b) -> (fill maxalign (ppline a), b)) hml)+containerComma :: (Pretty a) => Container a -> Doc+containerComma hm = containerComma' (fmap (\(a, b) -> (fill maxalign (ppline a), b)) hml) where- hml = Map.toList hm- maxalign = maximum (fmap (Text.length . fst) hml)-+ hml = Map.toList hm+ maxalign = maximum (fmap (Text.length . fst) hml) -- Lens _sourceName :: Lens' Position String-_sourceName = lens sourceName (\s n -> s { sourceName = n })+_sourceName = lens sourceName (\s n -> s {sourceName = n}) _sourceLine :: Lens' Position Pos-_sourceLine = lens sourceLine (\s l -> s { sourceLine = l })+_sourceLine = lens sourceLine (\s l -> s {sourceLine = l}) _sourceColumn :: Lens' Position Pos-_sourceColumn = lens sourceColumn (\s c -> s { sourceColumn = c })+_sourceColumn = lens sourceColumn (\s c -> s {sourceColumn = c}) -- | Generates a 'PPosition' based on a filename and line number. toPPos :: Text -> Int -> PPosition toPPos fl ln =- let p = (initialPos (toS fl)) { sourceLine = mkPos $ fromIntegral (max 1 ln) }- in (p :!: p)+ let p = (initialPos (toS fl)) {sourceLine = mkPos $ fromIntegral (max 1 ln)}+ in (p :!: p)
src/Puppet/Language/NativeTypes.hs view
@@ -1,48 +1,60 @@ -- | This exposed module holds the /native/ Puppet resource types.-module Puppet.Language.NativeTypes (- baseNativeTypes- , defaulttype- , NativeTypeMethods- , NativeTypeName- , HasNativeTypeMethods(..)-) where--import qualified Data.HashMap.Strict as HM+module Puppet.Language.NativeTypes+ ( baseNativeTypes,+ defaulttype,+ NativeTypeMethods,+ NativeTypeName,+ HasNativeTypeMethods (..),+ )+where -import Puppet.Language.NativeTypes.Concat-import Puppet.Language.NativeTypes.Cron-import Puppet.Language.NativeTypes.Exec-import Puppet.Language.NativeTypes.File-import Puppet.Language.NativeTypes.Group-import Puppet.Language.NativeTypes.Helpers-import Puppet.Language.NativeTypes.Host-import Puppet.Language.NativeTypes.Mount-import Puppet.Language.NativeTypes.Notify-import Puppet.Language.NativeTypes.Package-import Puppet.Language.NativeTypes.SshSecure-import Puppet.Language.NativeTypes.User-import Puppet.Language.NativeTypes.ZoneRecord+import qualified Data.HashMap.Strict as HM+import Puppet.Language.NativeTypes.Concat (nativeConcat, nativeConcatFragment)+import Puppet.Language.NativeTypes.Cron (nativeCron)+import Puppet.Language.NativeTypes.Exec (nativeExec)+import Puppet.Language.NativeTypes.File (nativeFile)+import Puppet.Language.NativeTypes.Group (nativeGroup)+import Puppet.Language.NativeTypes.Helpers+ ( Container,+ HasNativeTypeMethods (..),+ NativeTypeMethods,+ NativeTypeName,+ defaulttype,+ faketype,+ map,+ (++),+ )+import Puppet.Language.NativeTypes.Host (nativeHost)+import Puppet.Language.NativeTypes.Mount (nativeMount)+import Puppet.Language.NativeTypes.Notify (nativeNotify)+import Puppet.Language.NativeTypes.Package (nativePackage)+import Puppet.Language.NativeTypes.SshSecure (nativeSshSecure)+import Puppet.Language.NativeTypes.User (nativeUser)+import Puppet.Language.NativeTypes.ZoneRecord (nativeZoneRecord) fakeTypes :: [(NativeTypeName, NativeTypeMethods)]-fakeTypes = map faketype ["class"]+fakeTypes = [faketype "class"] defaultTypes :: [(NativeTypeName, NativeTypeMethods)]-defaultTypes = map defaulttype ["augeas","computer","filebucket","interface","k5login","macauthorization","mailalias","maillist","mcx","nagios_command","nagios_contact","nagios_contactgroup","nagios_host","nagios_hostdependency","nagios_hostescalation","nagios_hostextinfo","nagios_hostgroup","nagios_service","nagios_servicedependency","nagios_serviceescalation","nagios_serviceextinfo","nagios_servicegroup","nagios_timeperiod","resources","router","schedule","scheduledtask","selboolean","selmodule","service","ssh_authorized_key","sshkey","stage","tidy","vlan","yumrepo","zfs","zone","zpool"]+defaultTypes = map defaulttype ["augeas", "computer", "filebucket", "interface", "k5login", "macauthorization", "mailalias", "maillist", "mcx", "nagios_command", "nagios_contact", "nagios_contactgroup", "nagios_host", "nagios_hostdependency", "nagios_hostescalation", "nagios_hostextinfo", "nagios_hostgroup", "nagios_service", "nagios_servicedependency", "nagios_serviceescalation", "nagios_serviceextinfo", "nagios_servicegroup", "nagios_timeperiod", "resources", "router", "schedule", "scheduledtask", "selboolean", "selmodule", "service", "ssh_authorized_key", "sshkey", "stage", "tidy", "vlan", "yumrepo", "zfs", "zone", "zpool"] -- | The map of native types. baseNativeTypes :: Container NativeTypeMethods-baseNativeTypes = HM.fromList+baseNativeTypes =+ HM.fromList ( nativeConcat- : nativeConcatFragment- : nativeCron- : nativeExec- : nativeFile- : nativeGroup- : nativeHost- : nativeMount- : nativeNotify- : nativePackage- : nativeSshSecure- : nativeUser- : nativeZoneRecord- : fakeTypes ++ defaultTypes)+ : nativeConcatFragment+ : nativeCron+ : nativeExec+ : nativeFile+ : nativeGroup+ : nativeHost+ : nativeMount+ : nativeNotify+ : nativePackage+ : nativeSshSecure+ : nativeUser+ : nativeZoneRecord+ : fakeTypes+ ++ defaultTypes+ )
src/Puppet/Language/NativeTypes/Concat.hs view
@@ -1,7 +1,8 @@-module Puppet.Language.NativeTypes.Concat (- nativeConcat- , nativeConcatFragment-) where+module Puppet.Language.NativeTypes.Concat+ ( nativeConcat,+ nativeConcatFragment,+ )+where import Puppet.Language.NativeTypes.Helpers @@ -13,29 +14,29 @@ concatparamfunctions :: [(Text, [Text -> NativeTypeValidate])] concatparamfunctions =- [("name" , [nameval])- ,("ensure" , [defaultvalue "present", string, values ["present","absent"]])- ,("path" , [string])- ,("owner" , [string])- ,("group" , [string])- ,("validate_cmd" , [string])- ,("mode" , [defaultvalue "0644", string])- ,("warn" , [defaultvalue "false", string, values ["false", "true"]])- ,("force" , [defaultvalue "false", string, values ["false", "true"]])- ,("backup" , [defaultvalue "puppet", string])- ,("replace" , [defaultvalue "true", string, values ["false", "true"]])- ,("order" , [defaultvalue "alpha", string, values ["alpha","numeric"]])- ,("ensure_newline" , [defaultvalue "false", string, values ["false", "true"]])+ [ ("name", [nameval]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent"]]),+ ("path", [string]),+ ("owner", [string]),+ ("group", [string]),+ ("validate_cmd", [string]),+ ("mode", [defaultvalue "0644", string]),+ ("warn", [defaultvalue "false", string, values ["false", "true"]]),+ ("force", [defaultvalue "false", string, values ["false", "true"]]),+ ("backup", [defaultvalue "puppet", string]),+ ("replace", [defaultvalue "true", string, values ["false", "true"]]),+ ("order", [defaultvalue "alpha", string, values ["alpha", "numeric"]]),+ ("ensure_newline", [defaultvalue "false", string, values ["false", "true"]]) -- deprecated -- ,("gnu" , [string])- ]+ ] fragmentparamfunctions :: [(Text, [Text -> NativeTypeValidate])] fragmentparamfunctions =- [("name" , [nameval])- ,("target" , [string, mandatory])- ,("content" , [string])- ,("source" , [string])- -- order should be an int or a string- ,("order" , [defaultvalue "10", string])- ]+ [ ("name", [nameval]),+ ("target", [string, mandatory]),+ ("content", [string]),+ ("source", [string]),+ -- order should be an int or a string+ ("order", [defaultvalue "10", string])+ ]
src/Puppet/Language/NativeTypes/Cron.hs view
@@ -1,61 +1,59 @@-module Puppet.Language.NativeTypes.Cron- (nativeCron)-where--import qualified Data.Text as Text+module Puppet.Language.NativeTypes.Cron (nativeCron) where +import qualified Data.Text as Text import Puppet.Language.NativeTypes.Helpers nativeCron :: (NativeTypeName, NativeTypeMethods)-nativeCron = ("cron", nativetypemethods parameterfunctions return )+nativeCron = ("cron", nativetypemethods parameterfunctions return) -- Autorequires: If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("ensure" , [defaultvalue "present", string, values ["present","absent"]])- ,("command" , [string, mandatoryIfNotAbsent])- ,("environment" , [])- ,("hour" , [vrange 0 23 [] ])- ,("minute" , [vrange 0 59 [] ])- ,("month" , [vrange 1 12 ["January","February","March","April","May","June","July","August","September","October","November","December"] ])- ,("monthday" , [vrange 1 31 [] ])- ,("name" , [nameval])- ,("provider" , [defaultvalue "crontab", string, values ["crontab"]])- ,("special" , [string])- ,("target" , [string])- ,("user" , [defaultvalue "root", string])- ,("weekday" , [vrange 0 7 ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]])- ]-+ [ ("ensure", [defaultvalue "present", string, values ["present", "absent"]]),+ ("command", [string, mandatoryIfNotAbsent]),+ ("environment", []),+ ("hour", [vrange 0 23 []]),+ ("minute", [vrange 0 59 []]),+ ("month", [vrange 1 12 ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]]),+ ("monthday", [vrange 1 31 []]),+ ("name", [nameval]),+ ("provider", [defaultvalue "crontab", string, values ["crontab"]]),+ ("special", [string]),+ ("target", [string]),+ ("user", [defaultvalue "root", string]),+ ("weekday", [vrange 0 7 ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]])+ ] vrange :: Integer -> Integer -> [Text] -> Text -> NativeTypeValidate vrange mi ma valuelist param res = case res ^. rattributes . at param of- Just (PArray xs) -> foldM (vrange' mi ma valuelist param) res xs- Just x -> vrange' mi ma valuelist param res x- Nothing -> defaultvalue "*" param res+ Just (PArray xs) -> foldM (vrange' mi ma valuelist param) res xs+ Just x -> vrange' mi ma valuelist param res x+ Nothing -> defaultvalue "*" param res vrange' :: Integer -> Integer -> [Text] -> Text -> Resource -> PValue -> Either PrettyError Resource vrange' mi ma valuelist param res y = case y of- PString "*" -> Right res- PString "absent" -> Right res- PNumber n -> checkint' n mi ma param res- PString x -> if x `elem` valuelist- then Right res- else parseval x mi ma param res- x -> perror $ "Parameter" <+> paramname param <+> "value should be a valid cron declaration and not" <+> pretty x+ PString "*" -> Right res+ PString "absent" -> Right res+ PNumber n -> checkint' n mi ma param res+ PString x ->+ if x `elem` valuelist+ then Right res+ else parseval x mi ma param res+ x -> perror $ "Parameter" <+> paramname param <+> "value should be a valid cron declaration and not" <+> pretty x parseval :: Text -> Integer -> Integer -> Text -> NativeTypeValidate-parseval resval mi ma pname res | "*/" `Text.isPrefixOf` resval = checkint (Text.drop 2 resval) 1 ma pname res- | otherwise = checkint resval mi ma pname res+parseval resval mi ma pname res+ | "*/" `Text.isPrefixOf` resval = checkint (Text.drop 2 resval) 1 ma pname res+ | otherwise = checkint resval mi ma pname res checkint :: Text -> Integer -> Integer -> Text -> NativeTypeValidate checkint st mi ma pname res =- case text2Scientific st of- Just n -> checkint' n mi ma pname res- Nothing -> perror $ "Invalid value type for parameter" <+> paramname pname <+> ": " <+> red (ppline st)+ case text2Scientific st of+ Just n -> checkint' n mi ma pname res+ Nothing -> perror $ "Invalid value type for parameter" <+> paramname pname <+> ": " <+> red (ppline st) checkint' :: Scientific -> Integer -> Integer -> Text -> NativeTypeValidate checkint' i mi ma param res =- if (i >= fromIntegral mi) && (i <= fromIntegral ma)- then Right res- else perror $ "Parameter" <+> paramname param <+> "value is out of bound, should satisfy" <+> pretty mi <+> "<=" <+> pplines (show i) <+> "<=" <+> pretty ma+ if (i >= fromIntegral mi) && (i <= fromIntegral ma)+ then Right res+ else perror $ "Parameter" <+> paramname param <+> "value is out of bound, should satisfy" <+> pretty mi <+> "<=" <+> pplines (show i) <+> "<=" <+> pretty ma
src/Puppet/Language/NativeTypes/Exec.hs view
@@ -1,7 +1,6 @@ module Puppet.Language.NativeTypes.Exec (nativeExec) where -import qualified Data.Text as Text-+import qualified Data.Text as Text import Puppet.Language.NativeTypes.Helpers nativeExec :: (NativeTypeName, NativeTypeMethods)@@ -10,29 +9,30 @@ -- Autorequires: If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("command" , [nameval])- ,("creates" , [rarray, strings, fullyQualifieds])- ,("cwd" , [string, fullyQualified])- ,("environment" , [rarray, strings])- ,("group" , [string])- ,("logoutput" , [defaultvalue "false", string, values ["true","false","on_failure"]])- ,("onlyif" , [string])- ,("path" , [rarray, strings, fullyQualifieds])- ,("provider" , [string, values ["posix","shell","windows"]])- ,("refresh" , [string])- ,("refreshonly" , [defaultvalue "false", string, values ["true","false"]])- ,("returns" , [defaultvalue "0", rarray, integers])- ,("timeout" , [defaultvalue "300", integer])- ,("tries" , [defaultvalue "1", integer])- ,("try_sleep" , [defaultvalue "0", integer])- ,("unless" , [string])- ,("user" , [string])- ]+ [ ("command", [nameval]),+ ("creates", [rarray, strings, fullyQualifieds]),+ ("cwd", [string, fullyQualified]),+ ("environment", [rarray, strings]),+ ("group", [string]),+ ("logoutput", [defaultvalue "false", string, values ["true", "false", "on_failure"]]),+ ("onlyif", [string]),+ ("path", [rarray, strings, fullyQualifieds]),+ ("provider", [string, values ["posix", "shell", "windows"]]),+ ("refresh", [string]),+ ("refreshonly", [defaultvalue "false", string, values ["true", "false"]]),+ ("returns", [defaultvalue "0", rarray, integers]),+ ("timeout", [defaultvalue "300", integer]),+ ("tries", [defaultvalue "1", integer]),+ ("try_sleep", [defaultvalue "0", integer]),+ ("unless", [string]),+ ("user", [string])+ ] fullyQualifiedOrPath :: NativeTypeValidate fullyQualifiedOrPath res = case (res ^. rattributes . at "path", res ^. rattributes . at "command") of- (Nothing, Just (PString x)) -> if Text.head x == '/'- then Right res- else Left "Command must be fully qualified if path is not defined"+ (Nothing, Just (PString x)) ->+ if Text.head x == '/'+ then Right res+ else Left "Command must be fully qualified if path is not defined" _ -> Right res
src/Puppet/Language/NativeTypes/File.hs view
@@ -1,51 +1,50 @@-module Puppet.Language.NativeTypes.File (nativeFile) where--import qualified Data.Attoparsec.Text as AT-import qualified Data.Char as Char-import qualified Data.Map.Strict as Map-import qualified Data.Set as Set-import qualified Data.Text as Text+{-# LANGUAGE TupleSections #-} -import Puppet.Language.NativeTypes.Helpers+module Puppet.Language.NativeTypes.File (nativeFile) where +import qualified Data.Attoparsec.Text as AT+import qualified Data.Char as Char+import qualified Data.Map.Strict as Map+import qualified Data.Set as Set+import qualified Data.Text as Text+import Puppet.Language.NativeTypes.Helpers nativeFile :: (NativeTypeName, NativeTypeMethods) nativeFile = ("file", nativetypemethods parameterfunctions (validateSourceOrContent >=> validateMode)) - -- Autorequires: If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("backup" , [string])- ,("checksum" , [values ["md5", "md5lite", "mtime", "ctime", "none"]])- ,("content" , [string])- --,("ensure" , [defaultvalue "present", string, values ["directory","file","present","absent","link"]])- ,("ensure" , [defaultvalue "present", string])- ,("force" , [string, values ["true","false"]])- ,("group" , [defaultvalue "root", string])- ,("ignore" , [strings])- ,("links" , [string])- ,("mode" , [defaultvalue "0644", string])- ,("owner" , [string])- ,("path" , [nameval, fullyQualified, noTrailingSlash'])- ,("provider" , [values ["posix","windows"]])- ,("purge" , [string, values ["true","false"]])- ,("recurse" , [string, values ["inf","true","false","remote"]])- ,("recurselimit" , [integer])- ,("replace" , [string, values ["true","false","yes","no"]])- ,("show_diff" , [string, values ["true","false"]])- ,("sourceselect" , [values ["first","all"]])- ,("seltype" , [string])- ,("selrange" , [string])- ,("selinux_ignore_defaults", [string, values ["true","false"]])- ,("selrole" , [string])- ,("target" , [string])- ,("source" , [rarray, strings, flip runarray checkSource])- ,("seluser" , [string])- ,("validate_cmd" , [string])- ,("validate_replacement" , [string])- ]+ [ ("backup", [string]),+ ("checksum", [values ["md5", "md5lite", "mtime", "ctime", "none"]]),+ ("content", [string]),+ -- ,("ensure" , [defaultvalue "present", string, values ["directory","file","present","absent","link"]])+ ("ensure", [defaultvalue "present", string]),+ ("force", [string, values ["true", "false"]]),+ ("group", [defaultvalue "root", string]),+ ("ignore", [strings]),+ ("links", [string]),+ ("mode", [defaultvalue "0644", string]),+ ("owner", [string]),+ ("path", [nameval, fullyQualified, noTrailingSlash']),+ ("provider", [values ["posix", "windows"]]),+ ("purge", [string, values ["true", "false"]]),+ ("recurse", [string, values ["inf", "true", "false", "remote"]]),+ ("recurselimit", [integer]),+ ("replace", [string, values ["true", "false", "yes", "no"]]),+ ("show_diff", [string, values ["true", "false"]]),+ ("sourceselect", [values ["first", "all"]]),+ ("seltype", [string]),+ ("selrange", [string]),+ ("selinux_ignore_defaults", [string, values ["true", "false"]]),+ ("selrole", [string]),+ ("target", [string]),+ ("source", [rarray, strings, flip runarray checkSource]),+ ("seluser", [string]),+ ("validate_cmd", [string]),+ ("validate_replacement", [string])+ ] noTrailingSlash' :: Text -> NativeTypeValidate noTrailingSlash' param res@@ -54,41 +53,47 @@ validateMode :: NativeTypeValidate validateMode res = do- modestr <- case res ^. rattributes . at "mode" of- Just (PString s) -> return s- Just x -> throwError $ PrettyError ("Invalide mode type, should be a string " <+> pretty x)- Nothing -> throwError "Could not find mode!"- (numeric modestr <|> except (ugo modestr)) & runExcept & _Right %~ ($ res)+ modestr <- case res ^. rattributes . at "mode" of+ Just (PString s) -> return s+ Just x -> throwError $ PrettyError ("Invalide mode type, should be a string " <+> pretty x)+ Nothing -> throwError "Could not find mode!"+ (numeric modestr <|> except (ugo modestr)) & runExcept & _Right %~ ($ res) numeric :: Text -> Except PrettyError (Resource -> Resource) numeric modestr = do- when ((Text.length modestr /= 3) && (Text.length modestr /= 4)) (throwError "Invalid mode size")- unless (Text.all Char.isDigit modestr) (throwError "The mode should only be made of digits")- return $ if Text.length modestr == 3- then rattributes . at "mode" ?~ PString (Text.cons '0' modestr)- else identity+ when ((Text.length modestr /= 3) && (Text.length modestr /= 4)) (throwError "Invalid mode size")+ unless (Text.all Char.isDigit modestr) (throwError "The mode should only be made of digits")+ return $+ if Text.length modestr == 3+ then rattributes . at "mode" ?~ PString (Text.cons '0' modestr)+ else identity checkSource :: Text -> PValue -> NativeTypeValidate-checkSource _ (PString x) res | any (`Text.isPrefixOf` x) ["puppet://", "file://", "/", "http://", "https://"] = Right res- | otherwise = throwError "A source should start with either puppet://, http://, https:// or file:// or an absolute path"+checkSource _ (PString x) res+ | any (`Text.isPrefixOf` x) ["puppet://", "file://", "/", "http://", "https://"] = Right res+ | otherwise = throwError "A source should start with either puppet://, http://, https:// or file:// or an absolute path" checkSource _ x _ = throwError $ PrettyError ("Expected a string, not" <+> pretty x) data PermParts = Special | User | Group | Other- deriving (Eq, Ord)+ deriving (Eq, Ord) data PermSet = R | W | X- deriving (Ord, Eq)+ deriving (Ord, Eq) ugo :: Text -> Either PrettyError (Resource -> Resource)-ugo t = AT.parseOnly (modestring <* AT.endOfInput) t- & _Left %~ (\rr -> PrettyError $ "Could not parse the mode string: " <> ppstring rr)- & _Right %~ (\s -> rattributes . at "mode" ?~ PString (mkmode Special s <> mkmode User s <> mkmode Group s <> mkmode Other s))+ugo t =+ AT.parseOnly (modestring <* AT.endOfInput) t+ & _Left %~ (\rr -> PrettyError $ "Could not parse the mode string: " <> ppstring rr)+ & _Right %~ (\s -> rattributes . at "mode" ?~ PString (mkmode Special s <> mkmode User s <> mkmode Group s <> mkmode Other s)) mkmode :: PermParts -> Map PermParts (Set PermSet) -> Text-mkmode p m = let s = m ^. at p . non mempty- in Text.pack $ show $ fromEnum (Set.member R s) * 4- + fromEnum (Set.member W s) * 2- + fromEnum (Set.member X s)+mkmode p m =+ let s = m ^. at p . non mempty+ in Text.pack $+ show $+ fromEnum (Set.member R s) * 4+ + fromEnum (Set.member W s) * 2+ + fromEnum (Set.member X s) modestring :: AT.Parser (Map PermParts (Set.Set PermSet)) modestring = Map.fromList . mconcat <$> (modepart `AT.sepBy` AT.char ',')@@ -96,14 +101,16 @@ -- TODO suid, sticky and other funky things are not yet supported modepart :: AT.Parser [(PermParts, Set PermSet)] modepart = do- let permpart = (AT.char 'u' *> pure [User])- <|> (AT.char 'g' *> pure [Group])- <|> (AT.char 'o' *> pure [Other])- <|> (AT.char 'a' *> pure [User,Group,Other])- permission = (AT.char 'r' *> pure R)- <|> (AT.char 'w' *> pure W)- <|> (AT.char 'x' *> pure X)- pp <- mconcat <$> some permpart- void $ AT.char '='- pr <- Set.fromList <$> some permission- return (map (\p -> (p, pr)) pp)+ let permpart =+ (AT.char 'u' $> [User])+ <|> (AT.char 'g' $> [Group])+ <|> (AT.char 'o' $> [Other])+ <|> (AT.char 'a' $> [User, Group, Other])+ permission =+ (AT.char 'r' $> R)+ <|> (AT.char 'w' $> W)+ <|> (AT.char 'x' $> X)+ pp <- mconcat <$> some permpart+ void $ AT.char '='+ pr <- Set.fromList <$> some permission+ return (map (,pr) pp)
src/Puppet/Language/NativeTypes/Group.hs view
@@ -1,6 +1,6 @@ module Puppet.Language.NativeTypes.Group (nativeGroup) where -import Puppet.Language.NativeTypes.Helpers+import Puppet.Language.NativeTypes.Helpers nativeGroup :: (NativeTypeName, NativeTypeMethods) nativeGroup = ("group", nativetypemethods parameterfunctions return)@@ -8,15 +8,15 @@ -- Autorequires: If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("allowdupe" , [string, defaultvalue "false", values ["true","false"]])- ,("attribute_membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("attributes" , [strings])- ,("auth_membership" , [defaultvalue "minimum", string, values ["inclusive","minimum"]])- ,("ensure" , [defaultvalue "present", string, values ["present","absent"]])- ,("gid" , [integer])- ,("ia_load_module" , [string])- ,("members" , [strings])- ,("name" , [nameval])- ,("provider" , [string, values ["aix","directoryservice","groupadd","ldap","pw","window_adsi"]])- ,("system" , [string, defaultvalue "false", values ["true","false"]])- ]+ [ ("allowdupe", [string, defaultvalue "false", values ["true", "false"]]),+ ("attribute_membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("attributes", [strings]),+ ("auth_membership", [defaultvalue "minimum", string, values ["inclusive", "minimum"]]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent"]]),+ ("gid", [integer]),+ ("ia_load_module", [string]),+ ("members", [strings]),+ ("name", [nameval]),+ ("provider", [string, values ["aix", "directoryservice", "groupadd", "ldap", "pw", "window_adsi"]]),+ ("system", [string, defaultvalue "false", values ["true", "false"]])+ ]
src/Puppet/Language/NativeTypes/Helpers.hs view
@@ -1,62 +1,64 @@ {-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}++{-# HLINT ignore "Use camelCase" #-} -- Private modules. -- Function and data types that are used to define the native types. module Puppet.Language.NativeTypes.Helpers- ( module Exports- , ipaddr- , nativetypemethods- , NativeTypeName- , NativeTypeValidate- , NativeTypeMethods- , HasNativeTypeMethods(..)- , paramname- , rarray- , string- , strings- , string_s- , noTrailingSlash- , fullyQualified- , fullyQualifieds- , values- , defaultvalue- , nameval- , defaultValidate- , integer- , integers- , mandatory- , mandatoryIfNotAbsent- , inrange- , faketype- , defaulttype- , runarray- , perror- , validateSourceOrContent- ) where--import XPrelude as Exports--import Data.Char (isDigit)-import qualified Data.HashMap.Strict as Map-import qualified Data.HashSet as HS-import qualified Data.Text as Text-import qualified Data.Vector as V+ ( module Exports,+ ipaddr,+ nativetypemethods,+ NativeTypeName,+ NativeTypeValidate,+ NativeTypeMethods,+ HasNativeTypeMethods (..),+ paramname,+ rarray,+ string,+ strings,+ string_s,+ noTrailingSlash,+ fullyQualified,+ fullyQualifieds,+ values,+ defaultvalue,+ nameval,+ defaultValidate,+ integer,+ integers,+ mandatory,+ mandatoryIfNotAbsent,+ inrange,+ faketype,+ defaulttype,+ runarray,+ perror,+ validateSourceOrContent,+ )+where -import Puppet.Language.Core as Exports-import Puppet.Language.Resource as Exports-import Puppet.Language.Value as Exports+import Data.Char as Char+import qualified Data.HashMap.Strict as Map+import qualified Data.HashSet as HS+import qualified Data.Text as Text+import qualified Data.Vector as V+import Puppet.Language.Core as Exports+import Puppet.Language.Resource as Exports+import Puppet.Language.Value as Exports import qualified Text.Read-+import XPrelude as Exports metaparameters :: HS.HashSet Text-metaparameters = HS.fromList ["tag","stage","name","title","alias","audit","check","loglevel","noop","schedule", "EXPORTEDSOURCE", "require", "before", "register", "notify"]+metaparameters = HS.fromList ["tag", "stage", "name", "title", "alias", "audit", "check", "loglevel", "noop", "schedule", "EXPORTEDSOURCE", "require", "before", "register", "notify"] type NativeTypeName = Text+ type NativeTypeValidate = Resource -> Either PrettyError Resource -- | Attributes (and providers) of a puppet resource type bundled with validation rules data NativeTypeMethods = NativeTypeMethods- { _puppetValidate :: NativeTypeValidate- , _puppetFields :: HS.HashSet Text+ { _puppetValidate :: NativeTypeValidate,+ _puppetFields :: HS.HashSet Text } makeClassy ''NativeTypeMethods@@ -72,7 +74,7 @@ nativetypemethods :: [(Text, [Text -> NativeTypeValidate])] -> NativeTypeValidate -> NativeTypeMethods nativetypemethods def extraV = let params = fromKeys def- in NativeTypeMethods (defaultValidate params >=> parameterFunctions def >=> extraV) params+ in NativeTypeMethods (defaultValidate params >=> parameterFunctions def >=> extraV) params where fromKeys = HS.fromList . map fst @@ -82,72 +84,76 @@ defaulttype :: NativeTypeName -> (NativeTypeName, NativeTypeMethods) defaulttype tname = (tname, NativeTypeMethods (defaultValidate HS.empty) HS.empty) -{-| Validate resources given a list of valid parameters:-- * checks that no unknown parameters have been set (except metaparameters)--}+-- | Validate resources given a list of valid parameters:+--+-- * checks that no unknown parameters have been set (except metaparameters) defaultValidate :: HS.HashSet Text -> NativeTypeValidate defaultValidate validparameters = checkParameterList validparameters >=> addDefaults checkParameterList :: HS.HashSet Text -> NativeTypeValidate-checkParameterList validparameters res | HS.null validparameters = Right res- | otherwise = if HS.null setdiff- then Right res- else perror $ "Unknown parameters: " <+> list (map paramname $ HS.toList setdiff)- where- keyset = HS.fromList $ Map.keys (res ^. rattributes)- setdiff = HS.difference keyset (metaparameters `HS.union` validparameters)+checkParameterList validparameters res+ | HS.null validparameters = Right res+ | otherwise =+ if HS.null setdiff+ then Right res+ else perror $ "Unknown parameters: " <+> list (map paramname $ HS.toList setdiff)+ where+ keyset = HS.fromList $ Map.keys (res ^. rattributes)+ setdiff = HS.difference keyset (metaparameters `HS.union` validparameters) -- | This validator always accept the resources, but add the default parameters (to be defined :) addDefaults :: NativeTypeValidate addDefaults res = Right (res & rattributes %~ newparams)- where- def PUndef = False- def _ = True- newparams p = Map.filter def $ Map.union p defaults- defaults = Map.empty+ where+ def PUndef = False+ def _ = True+ newparams p = Map.filter def $ Map.union p defaults+ defaults = Map.empty -- | Helper function that runs a validor on a 'PArray' runarray :: Text -> (Text -> PValue -> NativeTypeValidate) -> NativeTypeValidate runarray param func res = case res ^. rattributes . at param of Just (PArray x) -> V.foldM (flip (func param)) res x- Just x -> perror $ "Parameter" <+> paramname param <+> "should be an array, not" <+> pretty x- Nothing -> Right res+ Just x -> perror $ "Parameter" <+> paramname param <+> "should be an array, not" <+> pretty x+ Nothing -> Right res -{-| This checks that a given parameter is a string. If it is a 'PBoolean' or-'PNumber' it will convert them to a string.--}+-- | This checks that a given parameter is a string. If it is a 'PBoolean' or+-- 'PNumber' it will convert them to a string. string :: Text -> NativeTypeValidate string param res = case res ^. rattributes . at param of- Just x -> string' param x res+ Just x -> string' param x res Nothing -> Right res strings :: Text -> NativeTypeValidate strings param = runarray param string' --- | Validates a string or an array of strings+-- | Validates a string or an array of strings string_s :: Text -> NativeTypeValidate string_s param res = case res ^. rattributes . at param of- Nothing -> Right res+ Nothing -> Right res Just (PArray _) -> strings param res- Just _ -> string param res+ Just _ -> string param res string' :: Text -> PValue -> NativeTypeValidate string' param rev res = case rev of- PString _ -> Right res- PBoolean True -> Right (res & rattributes . at param ?~ PString "true")+ PString _ -> Right res+ PBoolean True -> Right (res & rattributes . at param ?~ PString "true") PBoolean False -> Right (res & rattributes . at param ?~ PString "false")- PNumber n -> Right (res & rattributes . at param ?~ PString (scientific2text n))- x -> perror $ "Parameter" <+> paramname param <+> "should be a string, and not" <+> pretty x+ PNumber n -> Right (res & rattributes . at param ?~ PString (scientific2text n))+ PSensitive x -> case string' param x res of+ Right v -> Right v+ Left _ -> perror $ "Parameter" <+> paramname param <+> "should be a string, and not" <+> pretty x+ x -> perror $ "Parameter" <+> paramname param <+> "should be a string, and not" <+> pretty x -- | Makes sure that the parameter, if defined, has a value among this list. values :: [Text] -> Text -> NativeTypeValidate values valuelist param res = case res ^. rattributes . at param of- Just (PString x) -> if x `elem` valuelist- then Right res- else perror $ "Parameter" <+> paramname param <+> "value should be one of" <+> list (map ppline valuelist) <+> "and not" <+> ppline x- Just x -> perror $ "Parameter" <+> paramname param <+> "value should be one of" <+> list (map ppline valuelist) <+> "and not" <+> pretty x- Nothing -> Right res+ Just (PString x) ->+ if x `elem` valuelist+ then Right res+ else perror $ "Parameter" <+> paramname param <+> "value should be one of" <+> list (map ppline valuelist) <+> "and not" <+> ppline x+ Just x -> perror $ "Parameter" <+> paramname param <+> "value should be one of" <+> list (map ppline valuelist) <+> "and not" <+> pretty x+ Nothing -> Right res -- | This fills the default values of unset parameters. defaultvalue :: Text -> Text -> NativeTypeValidate@@ -184,7 +190,7 @@ mandatoryIfNotAbsent :: Text -> NativeTypeValidate mandatoryIfNotAbsent param res = case res ^. rattributes . at param of- Just _ -> Right res+ Just _ -> Right res Nothing -> case res ^. rattributes . at "ensure" of Just "absent" -> Right res _ -> perror $ "Parameter" <+> paramname param <+> "should be set."@@ -192,13 +198,13 @@ -- | Checks that a given parameter is set. mandatory :: Text -> NativeTypeValidate mandatory param res = case res ^. rattributes . at param of- Just _ -> Right res- Nothing -> perror $ "Parameter" <+> paramname param <+> "should be set."+ Just _ -> Right res+ Nothing -> perror $ "Parameter" <+> paramname param <+> "should be set." -- Helper that takes a list of stuff and will generate a validator. parameterFunctions :: [(Text, [Text -> NativeTypeValidate])] -> NativeTypeValidate parameterFunctions argrules rs = foldM parameterFunctions' rs argrules- where+ where parameterFunctions' :: Resource -> (Text, [Text -> NativeTypeValidate]) -> Either PrettyError Resource parameterFunctions' r (param, validationfunctions) = foldM (parameterFunctions'' param) r validationfunctions parameterFunctions'' :: Text -> Resource -> (Text -> NativeTypeValidate) -> Either PrettyError Resource@@ -207,15 +213,16 @@ -- checks that a parameter is fully qualified fullyQualified :: Text -> NativeTypeValidate fullyQualified param res = case res ^. rattributes . at param of- Just path -> fullyQualified' param path res- Nothing -> Right res+ Just path -> fullyQualified' param path res+ Nothing -> Right res noTrailingSlash :: Text -> NativeTypeValidate noTrailingSlash param res = case res ^. rattributes . at param of- Just (PString x) -> if Text.last x == '/'- then perror ("Parameter" <+> paramname param <+> "should not have a trailing slash")- else Right res+ Just (PString x) ->+ if Text.last x == '/'+ then perror ("Parameter" <+> paramname param <+> "should not have a trailing slash")+ else Right res _ -> Right res fullyQualifieds :: Text -> NativeTypeValidate@@ -224,55 +231,58 @@ fullyQualified' :: Text -> PValue -> NativeTypeValidate fullyQualified' param path res = case path of- PString ("") -> perror $ "Empty path for parameter" <+> paramname param- PString p -> if Text.head p == '/'- then Right res- else perror $ "Path must be absolute, not" <+> ppline p <+> "for parameter" <+> paramname param- x -> perror $ "SHOULD NOT HAPPEN: path is not a resolved string, but" <+> pretty x <+> "for parameter" <+> paramname param+ PString "" -> perror $ "Empty path for parameter" <+> paramname param+ PString p ->+ if Text.head p == '/'+ then Right res+ else perror $ "Path must be absolute, not" <+> ppline p <+> "for parameter" <+> paramname param+ x -> perror $ "SHOULD NOT HAPPEN: path is not a resolved string, but" <+> pretty x <+> "for parameter" <+> paramname param rarray :: Text -> NativeTypeValidate rarray param res = case res ^. rattributes . at param of- Just (PArray _) -> Right res- Just x -> Right $ res & rattributes . at param ?~ PArray (V.singleton x)- Nothing -> Right res+ Just (PArray _) -> Right res+ Just x -> Right $ res & rattributes . at param ?~ PArray (V.singleton x)+ Nothing -> Right res ipaddr :: Text -> NativeTypeValidate ipaddr param res = case res ^. rattributes . at param of Nothing -> Right res Just (PString ip) ->- if checkipv4 ip 0- then Right res- else perror $ "Invalid IP address for parameter" <+> paramname param+ if checkipv4 ip 0+ then Right res+ else perror $ "Invalid IP address for parameter" <+> paramname param Just x -> perror $ "Parameter" <+> paramname param <+> "should be an IP address string, not" <+> pretty x checkipv4 :: Text -> Int -> Bool-checkipv4 _ 4 = False -- means that there are more than 4 groups+checkipv4 _ 4 = False -- means that there are more than 4 groups checkipv4 "" _ = False -- should never get an empty string checkipv4 ip v =- let (cur, nxt) = Text.break (=='.') ip- nextfunc = if Text.null nxt- then v == 3- else checkipv4 (Text.tail nxt) (v+1)- goodcur = not (Text.null cur) && Text.all isDigit cur && (let rcur = Text.Read.read (Text.unpack cur) :: Int in (rcur >= 0) && (rcur <= 255))- in goodcur && nextfunc+ let (cur, nxt) = Text.break (== '.') ip+ nextfunc =+ if Text.null nxt+ then v == 3+ else checkipv4 (Text.tail nxt) (v + 1)+ goodcur = not (Text.null cur) && Text.all Char.isDigit cur && (let rcur = Text.Read.read (Text.unpack cur) :: Int in (rcur >= 0) && (rcur <= 255))+ in goodcur && nextfunc inrange :: Integer -> Integer -> Text -> NativeTypeValidate inrange mi ma param res =- let va = res ^. rattributes . at param- na = va ^? traverse . _PValueNumber- in case (va,na) of+ let va = res ^. rattributes . at param+ na = va ^? traverse . _PValueNumber+ in case (va, na) of (Nothing, _) -> Right res- (_,Just v) -> if (v >= fromIntegral mi) && (v <= fromIntegral ma)- then Right res- else perror $ "Parameter" <+> paramname param <> "'s value should be between" <+> pretty mi <+> "and" <+> pretty ma- (Just x,_) -> perror $ "Parameter" <+> paramname param <+> "should be an integer, and not" <+> pretty x+ (_, Just v) ->+ if (v >= fromIntegral mi) && (v <= fromIntegral ma)+ then Right res+ else perror $ "Parameter" <+> paramname param <> "'s value should be between" <+> pretty mi <+> "and" <+> pretty ma+ (Just x, _) -> perror $ "Parameter" <+> paramname param <+> "should be an integer, and not" <+> pretty x validateSourceOrContent :: NativeTypeValidate-validateSourceOrContent res = let- parammap = res ^. rattributes- source = Map.member "source" parammap- content = Map.member "content" parammap- in if source && content- then perror "Source and content can't be specified at the same time"- else Right res+validateSourceOrContent res =+ let parammap = res ^. rattributes+ source = Map.member "source" parammap+ content = Map.member "content" parammap+ in if source && content+ then perror "Source and content can't be specified at the same time"+ else Right res
src/Puppet/Language/NativeTypes/Host.hs view
@@ -1,9 +1,8 @@ module Puppet.Language.NativeTypes.Host (nativeHost) where -import qualified Data.Char as Char-import qualified Data.Text as Text--import Puppet.Language.NativeTypes.Helpers+import qualified Data.Char as Char+import qualified Data.Text as Text+import Puppet.Language.NativeTypes.Helpers nativeHost :: (NativeTypeName, NativeTypeMethods) nativeHost = ("host", nativetypemethods parameterfunctions return)@@ -11,34 +10,35 @@ -- Autorequires: If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("comment" , [string, values ["true","false"]])- ,("ensure" , [defaultvalue "present", string, values ["present","absent"]])- ,("host_aliases" , [rarray, strings, checkhostname])- ,("ip" , [string, mandatory, ipaddr])- ,("name" , [nameval, checkhostname])- ,("provider" , [string, values ["parsed"]])- ,("target" , [string, fullyQualified])- ]+ [ ("comment", [string, values ["true", "false"]]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent"]]),+ ("host_aliases", [rarray, strings, checkhostname]),+ ("ip", [string, mandatory, ipaddr]),+ ("name", [nameval, checkhostname]),+ ("provider", [string, values ["parsed"]]),+ ("target", [string, fullyQualified])+ ] checkhostname :: Text -> NativeTypeValidate checkhostname param res = case res ^. rattributes . at param of- Nothing -> Right res- Just (PArray xs) -> foldM (checkhostname' param) res xs- Just x@(PString _) -> checkhostname' param res x- Just x -> perror $ paramname param <+> "should be an array or a single string, not" <+> pretty x+ Nothing -> Right res+ Just (PArray xs) -> foldM (checkhostname' param) res xs+ Just x@(PString _) -> checkhostname' param res x+ Just x -> perror $ paramname param <+> "should be an array or a single string, not" <+> pretty x checkhostname' :: Text -> Resource -> PValue -> Either PrettyError Resource-checkhostname' prm _ (PString "") = perror $ "Empty hostname for parameter" <+> paramname prm-checkhostname' prm res (PString x ) = checkhostname'' prm res x-checkhostname' prm _ x = perror $ "Parameter " <+> paramname prm <+> "should be an string or an array of strings, but this was found :" <+> pretty x+checkhostname' prm _ (PString "") = perror $ "Empty hostname for parameter" <+> paramname prm+checkhostname' prm res (PString x) = checkhostname'' prm res x+checkhostname' prm _ x = perror $ "Parameter " <+> paramname prm <+> "should be an string or an array of strings, but this was found :" <+> pretty x checkhostname'' :: Text -> Resource -> Text -> Either PrettyError Resource-checkhostname'' prm _ "" = perror $ "Empty hostname part in parameter" <+> paramname prm+checkhostname'' prm _ "" = perror $ "Empty hostname part in parameter" <+> paramname prm checkhostname'' prm res prt =- let (cur,nxt) = Text.break (=='.') prt- nextfunc = if Text.null nxt- then Right res- else checkhostname'' prm res (Text.tail nxt)- in if Text.null cur || (Text.head cur == '-') || not (Text.all (\x -> Char.isAlphaNum x || (x=='-')) cur)- then perror $ "Invalid hostname part for parameter" <+> paramname prm- else nextfunc+ let (cur, nxt) = Text.break (== '.') prt+ nextfunc =+ if Text.null nxt+ then Right res+ else checkhostname'' prm res (Text.tail nxt)+ in if Text.null cur || (Text.head cur == '-') || not (Text.all (\x -> Char.isAlphaNum x || (x == '-')) cur)+ then perror $ "Invalid hostname part for parameter" <+> paramname prm+ else nextfunc
src/Puppet/Language/NativeTypes/Mount.hs view
@@ -1,22 +1,22 @@ module Puppet.Language.NativeTypes.Mount (nativeMount) where -import Puppet.Language.NativeTypes.Helpers+import Puppet.Language.NativeTypes.Helpers nativeMount :: (NativeTypeName, NativeTypeMethods) nativeMount = ("mount", nativetypemethods parameterfunctions return) parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("atboot" , [string, values ["true","false"]])- ,("blockdevice" , [string])- ,("device" , [string, mandatoryIfNotAbsent])- ,("dump" , [integer, inrange 0 2])- ,("ensure" , [defaultvalue "present", string, values ["present","absent","mounted"]])- ,("fstype" , [string, mandatoryIfNotAbsent])- ,("name" , [nameval])- ,("options" , [string])- ,("pass" , [defaultvalue "0", integer])- ,("provider" , [defaultvalue "parsed", string, values ["parsed"]])- ,("remounts" , [string, values ["true","false"]])- ,("target" , [string, fullyQualified])- ]+ [ ("atboot", [string, values ["true", "false"]]),+ ("blockdevice", [string]),+ ("device", [string, mandatoryIfNotAbsent]),+ ("dump", [integer, inrange 0 2]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent", "mounted"]]),+ ("fstype", [string, mandatoryIfNotAbsent]),+ ("name", [nameval]),+ ("options", [string]),+ ("pass", [defaultvalue "0", integer]),+ ("provider", [defaultvalue "parsed", string, values ["parsed"]]),+ ("remounts", [string, values ["true", "false"]]),+ ("target", [string, fullyQualified])+ ]
src/Puppet/Language/NativeTypes/Notify.hs view
@@ -1,12 +1,12 @@ module Puppet.Language.NativeTypes.Notify (nativeNotify) where -import Puppet.Language.NativeTypes.Helpers+import Puppet.Language.NativeTypes.Helpers nativeNotify :: (NativeTypeName, NativeTypeMethods) nativeNotify = ("notify", nativetypemethods parameterfunctions return) parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("message" , [])- ,("withpath" , [string, defaultvalue "false", values ["true","false"]])+ [ ("message", []),+ ("withpath", [string, defaultvalue "false", values ["true", "false"]]) ]
src/Puppet/Language/NativeTypes/Package.hs view
@@ -1,12 +1,13 @@ {-# LANGUAGE DeriveGeneric #-}-module Puppet.Language.NativeTypes.Package- ( nativePackage- ) where -import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as Set+module Puppet.Language.NativeTypes.Package+ ( nativePackage,+ )+where -import Puppet.Language.NativeTypes.Helpers+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as Set+import Puppet.Language.NativeTypes.Helpers nativePackage :: (NativeTypeName, NativeTypeMethods) nativePackage = ("package", nativetypemethods parameterfunctions (getFeature >=> checkFeatures))@@ -29,56 +30,58 @@ instance Hashable PackagingFeatures isFeatureSupported :: HashMap Text (HashSet PackagingFeatures)-isFeatureSupported = HM.fromList [ ("aix", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("appdmg", Set.fromList [Installable])- , ("apple", Set.fromList [Installable])- , ("apt", Set.fromList [Holdable, InstallOptions, Installable, Purgeable, Uninstallable, Upgradeable, Versionable])- , ("aptitude", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable, Versionable])- , ("aptrpm", Set.fromList [Installable, Purgeable, Uninstallable, Upgradeable, Versionable])- , ("blastwave", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("dpkg", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable])- , ("fink", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable, Versionable])- , ("freebsd", Set.fromList [Installable, Uninstallable])- , ("gem", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("hpux", Set.fromList [Installable, Uninstallable])- , ("macports", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("msi", Set.fromList [InstallOptions, Installable, UninstallOptions, Uninstallable])- , ("nim", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("openbsd", Set.fromList [Installable, Uninstallable, Versionable])- , ("pacman", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("pip", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("pkg", Set.fromList [Holdable, Installable, Uninstallable, Upgradeable, Versionable])- , ("pkgdmg", Set.fromList [Installable])- , ("pkgin", Set.fromList [Installable, Uninstallable])- , ("pkgutil", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("portage", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("ports", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("portupgrade", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("rpm", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("rug", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("sun", Set.fromList [InstallOptions, Installable, Uninstallable, Upgradeable])- , ("sunfreeware", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("up2date", Set.fromList [Installable, Uninstallable, Upgradeable])- , ("urpmi", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- , ("windows", Set.fromList [InstallOptions, Installable, UninstallOptions, Uninstallable])- , ("yum", Set.fromList [Installable, Purgeable, Uninstallable, Upgradeable, Versionable])- , ("zypper", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])- ]+isFeatureSupported =+ HM.fromList+ [ ("aix", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("appdmg", Set.fromList [Installable]),+ ("apple", Set.fromList [Installable]),+ ("apt", Set.fromList [Holdable, InstallOptions, Installable, Purgeable, Uninstallable, Upgradeable, Versionable]),+ ("aptitude", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable, Versionable]),+ ("aptrpm", Set.fromList [Installable, Purgeable, Uninstallable, Upgradeable, Versionable]),+ ("blastwave", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("dpkg", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable]),+ ("fink", Set.fromList [Holdable, Installable, Purgeable, Uninstallable, Upgradeable, Versionable]),+ ("freebsd", Set.fromList [Installable, Uninstallable]),+ ("gem", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("hpux", Set.fromList [Installable, Uninstallable]),+ ("macports", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("msi", Set.fromList [InstallOptions, Installable, UninstallOptions, Uninstallable]),+ ("nim", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("openbsd", Set.fromList [Installable, Uninstallable, Versionable]),+ ("pacman", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("pip", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("pkg", Set.fromList [Holdable, Installable, Uninstallable, Upgradeable, Versionable]),+ ("pkgdmg", Set.fromList [Installable]),+ ("pkgin", Set.fromList [Installable, Uninstallable]),+ ("pkgutil", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("portage", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("ports", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("portupgrade", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("rpm", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("rug", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("sun", Set.fromList [InstallOptions, Installable, Uninstallable, Upgradeable]),+ ("sunfreeware", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("up2date", Set.fromList [Installable, Uninstallable, Upgradeable]),+ ("urpmi", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable]),+ ("windows", Set.fromList [InstallOptions, Installable, UninstallOptions, Uninstallable]),+ ("yum", Set.fromList [Installable, Purgeable, Uninstallable, Upgradeable, Versionable]),+ ("zypper", Set.fromList [Installable, Uninstallable, Upgradeable, Versionable])+ ] parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("adminfile" , [string, fullyQualified])- ,("allowcdrom" , [string, values ["true","false"]])- ,("configfiles" , [string, values ["keep","replace"]])- --,("ensure" , [defaultvalue "present", string, values ["present","absent","latest","held","purged","installed"]])- ,("ensure" , [defaultvalue "present", string])- ,("flavor" , [])- ,("install_options" , [rarray])- ,("name" , [nameval])- ,("provider" , [defaultvalue "apt", string])- ,("responsefile" , [string, fullyQualified])- ,("source" , [string])- ,("uninstall_options", [rarray])+ [ ("adminfile", [string, fullyQualified]),+ ("allowcdrom", [string, values ["true", "false"]]),+ ("configfiles", [string, values ["keep", "replace"]]),+ -- ,("ensure" , [defaultvalue "present", string, values ["present","absent","latest","held","purged","installed"]])+ ("ensure", [defaultvalue "present", string]),+ ("flavor", []),+ ("install_options", [rarray]),+ ("name", [nameval]),+ ("provider", [defaultvalue "apt", string]),+ ("responsefile", [string, fullyQualified]),+ ("source", [string]),+ ("uninstall_options", [rarray]) ] getFeature :: Resource -> Either PrettyError (HashSet PackagingFeatures, Resource)@@ -86,36 +89,38 @@ case res ^. rattributes . at "provider" of Just (PString x) -> case HM.lookup x isFeatureSupported of- Just s -> Right (s, res)+ Just s -> Right (s, res) Nothing -> Left $ PrettyError ("Do not know provider" <+> ppline x) _ -> Left "Can't happen at Puppet.NativeTypes.Package" checkFeatures :: (HashSet PackagingFeatures, Resource) -> Either PrettyError Resource checkFeatures = checkAdminFile- >=> checkEnsure- >=> checkParam "install_options" InstallOptions- >=> checkParam "uninstall_options" UninstallOptions- >=> decap+ >=> checkEnsure+ >=> checkParam "install_options" InstallOptions+ >=> checkParam "uninstall_options" UninstallOptions+ >=> decap where- checkFeature :: HashSet PackagingFeatures -> Resource -> PackagingFeatures -> Either PrettyError (HashSet PackagingFeatures, Resource)- checkFeature s r f = if Set.member f s- then Right (s, r)- else Left $ PrettyError ("Feature" <+> pretty f <+> "is required for the current configuration")- checkParam :: Text -> PackagingFeatures -> (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)- checkParam pn f (s,r) = if has (ix pn) (r ^. rattributes)- then checkFeature s r f- else Right (s,r)- checkAdminFile :: (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)- checkAdminFile = Right -- TODO, check that it only works for aix- checkEnsure :: (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)- checkEnsure (s, res) = case res ^. rattributes . at "ensure" of- Just (PString "latest") -> checkFeature s res Installable- Just (PString "purged") -> checkFeature s res Purgeable- Just (PString "absent") -> checkFeature s res Uninstallable- Just (PString "installed") -> checkFeature s res Installable- Just (PString "present") -> checkFeature s res Installable- Just (PString "held") -> checkFeature s res Installable >> checkFeature s res Holdable- _ -> checkFeature s res Versionable- decap :: (HashSet PackagingFeatures, Resource) -> Either PrettyError Resource- decap = Right . snd+ checkFeature :: HashSet PackagingFeatures -> Resource -> PackagingFeatures -> Either PrettyError (HashSet PackagingFeatures, Resource)+ checkFeature s r f =+ if Set.member f s+ then Right (s, r)+ else Left $ PrettyError ("Feature" <+> pretty f <+> "is required for the current configuration")+ checkParam :: Text -> PackagingFeatures -> (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)+ checkParam pn f (s, r) =+ if has (ix pn) (r ^. rattributes)+ then checkFeature s r f+ else Right (s, r)+ checkAdminFile :: (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)+ checkAdminFile = Right -- TODO, check that it only works for aix+ checkEnsure :: (HashSet PackagingFeatures, Resource) -> Either PrettyError (HashSet PackagingFeatures, Resource)+ checkEnsure (s, res) = case res ^. rattributes . at "ensure" of+ Just (PString "latest") -> checkFeature s res Installable+ Just (PString "purged") -> checkFeature s res Purgeable+ Just (PString "absent") -> checkFeature s res Uninstallable+ Just (PString "installed") -> checkFeature s res Installable+ Just (PString "present") -> checkFeature s res Installable+ Just (PString "held") -> checkFeature s res Installable >> checkFeature s res Holdable+ _ -> checkFeature s res Versionable+ decap :: (HashSet PackagingFeatures, Resource) -> Either PrettyError Resource+ decap = Right . snd
src/Puppet/Language/NativeTypes/SshSecure.hs view
@@ -1,8 +1,9 @@ module Puppet.Language.NativeTypes.SshSecure- ( nativeSshSecure- ) where+ ( nativeSshSecure,+ )+where -import Puppet.Language.NativeTypes.Helpers+import Puppet.Language.NativeTypes.Helpers nativeSshSecure :: (NativeTypeName, NativeTypeMethods) nativeSshSecure = ("ssh_authorized_key_secure", nativetypemethods parameterfunctions (userOrTarget >=> keyIfPresent))@@ -10,12 +11,12 @@ -- Autorequires: If Puppet is managing the user or user that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [ ("type", [string, defaultvalue "ssh-rsa", values ["rsa", "dsa", "ssh-rsa", "ssh-dss"]])- , ("key", [string])- , ("user", [string])- , ("ensure", [defaultvalue "present", string, values ["present", "absent", "role"]])- , ("target", [string])- , ("options", [rarray, strings])+ [ ("type", [string, defaultvalue "ssh-rsa", values ["rsa", "dsa", "ssh-rsa", "ssh-dss"]]),+ ("key", [string]),+ ("user", [string]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent", "role"]]),+ ("target", [string]),+ ("options", [rarray, strings]) ] userOrTarget :: NativeTypeValidate@@ -23,7 +24,6 @@ case (res ^. rattributes & has (ix "user"), res ^. rattributes & has (ix "target")) of (False, False) -> Left "Parameters user or target are mandatory" _ -> Right res- keyIfPresent :: NativeTypeValidate keyIfPresent res =
src/Puppet/Language/NativeTypes/User.hs view
@@ -1,6 +1,6 @@ module Puppet.Language.NativeTypes.User (nativeUser) where -import Puppet.Language.NativeTypes.Helpers+import Puppet.Language.NativeTypes.Helpers nativeUser :: (NativeTypeName, NativeTypeMethods) nativeUser = ("user", nativetypemethods parameterfunctions return)@@ -9,35 +9,35 @@ -- If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("allowdupe" , [string, defaultvalue "false", values ["true","false"]])- ,("attribute_membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("attributes" , [rarray,strings])- ,("auth_membership" , [defaultvalue "minimum", string, values ["inclusive","minimum"]])- ,("auths" , [rarray,strings])- ,("comment" , [string])- ,("ensure" , [defaultvalue "present", string, values ["present","absent","role"]])- ,("expiry" , [string])- ,("gid" , [string])- ,("groups" , [rarray,strings])- ,("home" , [string, fullyQualified, noTrailingSlash])- ,("ia_load_module" , [string])- ,("iterations" , [integer])- ,("key_membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("keys" , [])- ,("managehome" , [string, defaultvalue "false", values ["true","false"]])- ,("membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("name" , [nameval])- ,("password" , [string])- ,("password_max_age" , [integer])- ,("password_min_age" , [integer])- ,("profile_membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("profiles" , [rarray,strings])- ,("project" , [string])- ,("provider" , [string, values ["aix","directoryservice","hpuxuseradd","useradd","ldap","pw","user_role_add","window_adsi"]])- ,("role_membership" , [string, defaultvalue "minimum", values ["inclusive","minimum"]])- ,("roles" , [rarray,strings])- ,("salt" , [string])- ,("shell" , [string, fullyQualified, noTrailingSlash])- ,("system" , [string, defaultvalue "false", values ["true","false"]])- ,("uid" , [integer])- ]+ [ ("allowdupe", [string, defaultvalue "false", values ["true", "false"]]),+ ("attribute_membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("attributes", [rarray, strings]),+ ("auth_membership", [defaultvalue "minimum", string, values ["inclusive", "minimum"]]),+ ("auths", [rarray, strings]),+ ("comment", [string]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent", "role"]]),+ ("expiry", [string]),+ ("gid", [string]),+ ("groups", [rarray, strings]),+ ("home", [string, fullyQualified, noTrailingSlash]),+ ("ia_load_module", [string]),+ ("iterations", [integer]),+ ("key_membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("keys", []),+ ("managehome", [string, defaultvalue "false", values ["true", "false"]]),+ ("membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("name", [nameval]),+ ("password", [string]),+ ("password_max_age", [integer]),+ ("password_min_age", [integer]),+ ("profile_membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("profiles", [rarray, strings]),+ ("project", [string]),+ ("provider", [string, values ["aix", "directoryservice", "hpuxuseradd", "useradd", "ldap", "pw", "user_role_add", "window_adsi"]]),+ ("role_membership", [string, defaultvalue "minimum", values ["inclusive", "minimum"]]),+ ("roles", [rarray, strings]),+ ("salt", [string]),+ ("shell", [string, fullyQualified, noTrailingSlash]),+ ("system", [string, defaultvalue "false", values ["true", "false"]]),+ ("uid", [integer])+ ]
src/Puppet/Language/NativeTypes/ZoneRecord.hs view
@@ -1,7 +1,6 @@ module Puppet.Language.NativeTypes.ZoneRecord (nativeZoneRecord) where -import Puppet.Language.NativeTypes.Helpers-+import Puppet.Language.NativeTypes.Helpers nativeZoneRecord :: (NativeTypeName, NativeTypeMethods) nativeZoneRecord = ("zone_record", nativetypemethods parameterfunctions validateMandatories)@@ -10,21 +9,21 @@ -- If Puppet is managing any parent directories of a file, the file resource will autorequire them. parameterfunctions :: [(Text, [Text -> NativeTypeValidate])] parameterfunctions =- [("name" , [nameval])- ,("owner" , [string])- ,("dest" , [string])- ,("ensure" , [defaultvalue "present", string, values ["present","absent"]])- ,("rtype" , [string, defaultvalue "A", values ["SOA", "A", "AAAA", "MX", "NS", "CNAME", "PTR", "SRV"]])- ,("rclass" , [defaultvalue "IN", string])- ,("ttl" , [defaultvalue "2d", string])- ,("target" , [string, mandatory])- ,("nsname" , [string])- ,("serial" , [string])- ,("slave_refresh" , [string])- ,("slave_retry" , [string])- ,("slave_expiration" , [string])- ,("min_ttl" , [string])- ,("email" , [string])+ [ ("name", [nameval]),+ ("owner", [string]),+ ("dest", [string]),+ ("ensure", [defaultvalue "present", string, values ["present", "absent"]]),+ ("rtype", [string, defaultvalue "A", values ["SOA", "A", "AAAA", "MX", "NS", "CNAME", "PTR", "SRV"]]),+ ("rclass", [defaultvalue "IN", string]),+ ("ttl", [defaultvalue "2d", string]),+ ("target", [string, mandatory]),+ ("nsname", [string]),+ ("serial", [string]),+ ("slave_refresh", [string]),+ ("slave_retry", [string]),+ ("slave_expiration", [string]),+ ("min_ttl", [string]),+ ("email", [string]) ] validateMandatories :: NativeTypeValidate
src/Puppet/Language/Paths.hs view
@@ -1,14 +1,20 @@ {-# LANGUAGE TemplateHaskell #-}+ module Puppet.Language.Paths where -import XPrelude+import XPrelude data PuppetDirPaths = PuppetDirPaths- { _baseDir :: FilePath -- ^ Puppet base working directory- , _manifestPath :: FilePath -- ^ The path to the manifests.- , _modulesPath :: FilePath -- ^ The path to the modules.- , _templatesPath :: FilePath -- ^ The path to the template.- , _testPath :: FilePath -- ^ The path to a tests folders to hold tests files such as the pdbfiles.+ { -- | Puppet base working directory+ _baseDir :: FilePath,+ -- | The path to the manifests.+ _manifestPath :: FilePath,+ -- | The path to the modules.+ _modulesPath :: FilePath,+ -- | The path to the template.+ _templatesPath :: FilePath,+ -- | The path to a tests folders to hold tests files such as the pdbfiles.+ _testPath :: FilePath } makeClassy ''PuppetDirPaths
src/Puppet/Language/Resource.hs view
@@ -1,60 +1,70 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-} module Puppet.Language.Resource- ( Resource(..)- , HasResource(..)- , RIdentifier(..)- , HasRIdentifier(..)- , LinkInformation(..)- , HasLinkInformation(..)- , LinkType(..)- , Virtuality(..)- , CurContainerDesc(..)- , FinalCatalog- , EdgeMap- ) where--import XPrelude--import Data.Aeson-import qualified Data.HashMap.Strict as Map-import qualified Data.HashSet as Set-import qualified Data.Text as Text-import qualified Data.Vector as Vector-import qualified GHC.Exts as Exts-import qualified Text.Megaparsec.Pos as Pos--import Puppet.Language.Core-import Puppet.Language.Value+ ( Resource (..),+ HasResource (..),+ RIdentifier (..),+ HasRIdentifier (..),+ LinkInformation (..),+ HasLinkInformation (..),+ LinkType (..),+ Virtuality (..),+ CurContainerDesc (..),+ FinalCatalog,+ EdgeMap,+ )+where +import Data.Aeson+import qualified Data.Aeson.KeyMap as KM+import qualified Data.HashMap.Strict as Map+import qualified Data.HashSet as Set+import qualified Data.Text as Text+import qualified Data.Vector as Vector+import qualified GHC.Exts as Exts+import Puppet.Language.Core+import Puppet.Language.Value+import qualified Text.Megaparsec.Pos as Pos+import XPrelude rel2text :: LinkType -> Text-rel2text RNotify = "notify"-rel2text RRequire = "require"-rel2text RBefore = "before"+rel2text RNotify = "notify"+rel2text RRequire = "require"+rel2text RBefore = "before" rel2text RSubscribe = "subscribe" data Virtuality- = Normal -- ^ Normal resource, that will be included in the catalog.- | Virtual -- ^ Type for virtual resources.- | Exported -- ^ Type for exported resources.- | ExportedRealized -- ^ These are resources that are exported AND realized in the catalog.+ = -- | Normal resource, that will be included in the catalog.+ Normal+ | -- | Type for virtual resources.+ Virtual+ | -- | Type for exported resources.+ Exported+ | -- | These are resources that are exported AND realized in the catalog.+ ExportedRealized deriving (Eq, Show) data CurContainerDesc- = ContRoot -- ^ Contained at node or root level.- | ContClass !Text -- ^ Contained in a class.- | ContDefine !Text- !Text- !PPosition -- ^ Contained in a define, along with the position where this define was ... defined- | ContImported !CurContainerDesc -- ^ Dummy container for imported resources, so that we know we must update the nodename- | ContImport !NodeName- !CurContainerDesc -- ^ This one is used when finalizing imported resources, and contains the current node name+ = -- | Contained at node or root level.+ ContRoot+ | -- | Contained in a class.+ ContClass !Text+ | -- | Contained in a define, along with the position where this define was ... defined+ ContDefine+ !Text+ !Text+ !PPosition+ | -- | Dummy container for imported resources, so that we know we must update the nodename+ ContImported !CurContainerDesc+ | -- | This one is used when finalizing imported resources, and contains the current node name+ ContImport+ !NodeName+ !CurContainerDesc deriving (Eq, Generic, Ord, Show) instance Pretty CurContainerDesc where- pretty (ContImport p x) = magenta "import" <> braces (ppline p) <> braces (pretty x)+ pretty (ContImport p x) = magenta "import" <> braces (ppline p) <> braces (pretty x) pretty (ContImported x) = magenta "imported" <> braces (pretty x) pretty ContRoot = dullyellow "::" pretty (ContClass cname) = dullyellow "class" <+> dullgreen (ppline cname)@@ -62,35 +72,40 @@ -- | Relationship/ordering between resources. data LinkType- = RRequire -- ^ Applies a resource after the target resource.- | RBefore -- ^ Applies a resource before the target resource.- | RNotify -- ^ Applies a resource before the target resource. The target resource refreshes if the notifying resource changes.- | RSubscribe -- ^ Applies a resource after the target resource. The subscribing resource refreshes if the target resource changes.+ = -- | Applies a resource after the target resource.+ RRequire+ | -- | Applies a resource before the target resource.+ RBefore+ | -- | Applies a resource before the target resource. The target resource refreshes if the notifying resource changes.+ RNotify+ | -- | Applies a resource after the target resource. The subscribing resource refreshes if the target resource changes.+ RSubscribe deriving (Show, Eq, Generic) instance Hashable LinkType instance FromJSON LinkType where- parseJSON (String "require") = return RRequire- parseJSON (String "notify") = return RNotify+ parseJSON (String "require") = return RRequire+ parseJSON (String "notify") = return RNotify parseJSON (String "subscribe") = return RSubscribe- parseJSON (String "before") = return RBefore- parseJSON _ = panic "invalid linktype"+ parseJSON (String "before") = return RBefore+ parseJSON _ = panic "invalid linktype" instance ToJSON LinkType where- toJSON = String . rel2text+ toJSON = String . rel2text instance Pretty LinkType where- pretty RNotify = "~>"- pretty RRequire = "<-"- pretty RBefore = "->"+ pretty RNotify = "~>"+ pretty RRequire = "<-"+ pretty RBefore = "->" pretty RSubscribe = "<~" -- | In Puppet, a resource is identified by a name and a type. data RIdentifier = RIdentifier- { _itype :: !Text- , _iname :: !Text- } deriving (Show, Eq, Generic, Ord)+ { _itype :: !Text,+ _iname :: !Text+ }+ deriving (Show, Eq, Generic, Ord) instance Pretty RIdentifier where pretty (RIdentifier t n) = pretty (PResourceReference t n)@@ -106,16 +121,26 @@ -- | A fully resolved puppet resource that will be used in the 'FinalCatalog'. data Resource = Resource- { _rid :: !RIdentifier -- ^ Resource name.- , _ralias :: !(HashSet Text) -- ^ All the resource aliases- , _rattributes :: !(Container PValue) -- ^ Resource parameters.- , _rrelations :: !(HashMap RIdentifier (HashSet LinkType)) -- ^ Resource relations.- , _rscope :: ![CurContainerDesc] -- ^ Resource scope when it was defined, the real container will be the first item- , _rvirtuality :: !Virtuality -- ^ Virtuality.- , _rtags :: !(HashSet Text) -- ^ Tags.- , _rpos :: !PPosition -- ^ Source code position of the resource definition.- , _rnode :: !NodeName -- ^ The node were this resource was created, if remote- } deriving (Eq, Show)+ { -- | Resource name.+ _rid :: !RIdentifier,+ -- | All the resource aliases+ _ralias :: !(HashSet Text),+ -- | Resource parameters.+ _rattributes :: !(Container PValue),+ -- | Resource relations.+ _rrelations :: !(HashMap RIdentifier (HashSet LinkType)),+ -- | Resource scope when it was defined, the real container will be the first item+ _rscope :: ![CurContainerDesc],+ -- | Virtuality.+ _rvirtuality :: !Virtuality,+ -- | Tags.+ _rtags :: !(HashSet Text),+ -- | Source code position of the resource definition.+ _rpos :: !PPosition,+ -- | The node were this resource was created, if remote+ _rnode :: !NodeName+ }+ deriving (Eq, Show) makeClassy ''Resource makeClassy ''RIdentifier@@ -129,111 +154,117 @@ meta r = showPPos (r ^. rpos) <+> red node <+> green (brackets scp) where node = ppline (r ^. rnode)- scp = "Scope" <+> pretty (r ^.. rscope . folded . filtered (/=ContRoot) . to pretty)+ scp = "Scope" <+> pretty (r ^.. rscope . folded . filtered (/= ContRoot) . to pretty) resourceBody :: Resource -> Doc resourceBody r = virtuality <> blue (ppline (r ^. rid . iname)) <> ":" <+> meta r <> line <> containerComma'' insde <> ";"- where- virtuality = case r ^. rvirtuality of- Normal -> mempty- Virtual -> dullred "@"- Exported -> dullred "@@"- ExportedRealized -> dullred "<@@>"- insde = alignlst dullblue attriblist1 ++ alignlst dullmagenta attriblist2- alignlst col = map (first (fill maxalign . col . ppline))- attriblist1 = Exts.sortWith fst $ Map.toList (r ^. rattributes) ++ aliasdiff- aliasWithoutTitle = r ^. ralias & contains (r ^. rid . iname) .~ False- aliasPValue = aliasWithoutTitle & PArray . Vector.fromList . map PString . Set.toList- aliasdiff | Set.null aliasWithoutTitle = []- | otherwise = [("alias", aliasPValue)]- attriblist2 = map totext (resourceRelations r)- totext (RIdentifier t n, lt) = (rel2text lt , PResourceReference t n)- maxalign = max (maxalign' attriblist1) (maxalign' attriblist2)- maxalign' [] = 0- maxalign' x = maximum . map (Text.length . fst) $ x+ where+ virtuality = case r ^. rvirtuality of+ Normal -> mempty+ Virtual -> dullred "@"+ Exported -> dullred "@@"+ ExportedRealized -> dullred "<@@>"+ insde = alignlst dullblue attriblist1 ++ alignlst dullmagenta attriblist2+ alignlst col = map (first (fill maxalign . col . ppline))+ attriblist1 = Exts.sortWith fst $ Map.toList (r ^. rattributes) ++ aliasdiff+ aliasWithoutTitle = r ^. ralias & contains (r ^. rid . iname) .~ False+ aliasPValue = aliasWithoutTitle & PArray . Vector.fromList . map PString . Set.toList+ aliasdiff+ | Set.null aliasWithoutTitle = []+ | otherwise = [("alias", aliasPValue)]+ attriblist2 = map totext (resourceRelations r)+ totext (RIdentifier t n, lt) = (rel2text lt, PResourceReference t n)+ maxalign = max (maxalign' attriblist1) (maxalign' attriblist2)+ maxalign' [] = 0+ maxalign' x = maximum . map (Text.length . fst) $ x instance Pretty Resource where prettyList lst =- let grouped = Map.toList $ Map.fromListWith (++) [ (r ^. rid . itype, [r]) | r <- lst ] :: [ (Text, [Resource]) ]- sorted = Exts.sortWith fst (map (second (Exts.sortWith (view (rid.iname)))) grouped)- showGroup :: (Text, [Resource]) -> Doc- showGroup (rt, res) = dullyellow (ppline rt) <+> lbrace <> line <> indent 2 (vcat (map resourceBody res)) <> line <> rbrace- in vcat (map showGroup sorted)+ let grouped = Map.toList $ Map.fromListWith (++) [(r ^. rid . itype, [r]) | r <- lst] :: [(Text, [Resource])]+ sorted = Exts.sortWith fst (map (second (Exts.sortWith (view (rid . iname)))) grouped)+ showGroup :: (Text, [Resource]) -> Doc+ showGroup (rt, res) = dullyellow (ppline rt) <+> lbrace <> line <> indent 2 (vcat (map resourceBody res)) <> line <> rbrace+ in vcat (map showGroup sorted) pretty r = dullyellow (ppline (r ^. rid . itype)) <+> lbrace <> line <> indent 2 (resourceBody r) <> line <> rbrace - instance ToJSON Resource where toJSON r =- object [ ("type", String $ r ^. rid . itype)- , ("title", String $ r ^. rid . iname)- , ("aliases", toJSON $ r ^. ralias)- , ("exported", Bool $ r ^. rvirtuality == Exported)- , ("tags", toJSON $ r ^. rtags)- , ("parameters", Object ( fmap toJSON (r ^. rattributes) `Map.union` relations ))- , ("sourceline", r ^. rpos . _1 . _sourceLine . to (toJSON . Pos.unPos))- , ("sourcefile", r ^. rpos . _1 . _sourceName . to toJSON)- ]+ object+ [ ("type", String $ r ^. rid . itype),+ ("title", String $ r ^. rid . iname),+ ("aliases", toJSON $ r ^. ralias),+ ("exported", Bool $ r ^. rvirtuality == Exported),+ ("tags", toJSON $ r ^. rtags),+ ("parameters", Object $ KM.fromHashMapText (fmap toJSON (r ^. rattributes) `Map.union` relations)),+ ("sourceline", r ^. rpos . _1 . _sourceLine . to (toJSON . Pos.unPos)),+ ("sourcefile", r ^. rpos . _1 . _sourceName . to toJSON)+ ] where relations = r ^. rrelations & Map.fromListWith (Vector.++) . concatMap changeRelations . Map.toList & fmap toValue- toValue v | Vector.length v == 1 = Vector.head v- | otherwise = Array v+ toValue v+ | Vector.length v == 1 = Vector.head v+ | otherwise = Array v changeRelations :: (RIdentifier, HashSet LinkType) -> [(Text, Vector Value)]- changeRelations (k,v) = do- c <- Set.toList v- return (rel2text c, Vector.singleton (String (rid2text k)))+ changeRelations (k, v) = do+ c <- Set.toList v+ return (rel2text c, Vector.singleton (String (rid2text k))) rid2text :: RIdentifier -> Text rid2text (RIdentifier t n) = capitalizeRT t `Text.append` "[" `Text.append` capn `Text.append` "]" where- capn = if t == "classe"- then capitalizeRT n- else n+ capn =+ if t == "classe"+ then capitalizeRT n+ else n instance FromJSON Resource where parseJSON (Object v) = do isExported <- v .: "exported"- let virtuality = if isExported- then Exported- else Normal+ let virtuality =+ if isExported+ then Exported+ else Normal getResourceIdentifier :: PValue -> Maybe RIdentifier getResourceIdentifier (PString x) =- let (restype, brckts) = Text.breakOn "[" x- rna | Text.null brckts = Nothing- | Text.null restype = Nothing- | Text.last brckts == ']' = Just (Text.tail (Text.init brckts))- | otherwise = Nothing- in case rna of- Just resname -> Just (RIdentifier (Text.toLower restype) (Text.toLower resname))- _ -> Nothing+ let (restype, brckts) = Text.breakOn "[" x+ rna+ | Text.null brckts = Nothing+ | Text.null restype = Nothing+ | Text.last brckts == ']' = Just (Text.tail (Text.init brckts))+ | otherwise = Nothing+ in case rna of+ Just resname -> Just (RIdentifier (Text.toLower restype) (Text.toLower resname))+ _ -> Nothing getResourceIdentifier _ = Nothing -- TODO : properly handle metaparameters separate :: (Container PValue, HashMap RIdentifier (HashSet LinkType)) -> Text -> PValue -> (Container PValue, HashMap RIdentifier (HashSet LinkType)) separate (curAttribs, curRelations) k val = case (fromJSON (String k), getResourceIdentifier val) of (Success rel, Just ri) -> (curAttribs, curRelations & at ri . non mempty . contains rel .~ True)- _ -> (curAttribs & at k ?~ val, curRelations)- (attribs,relations) <- Map.foldlWithKey' separate (mempty,mempty) <$> v .: "parameters"+ _ -> (curAttribs & at k ?~ val, curRelations)+ (attribs, relations) <- Map.foldlWithKey' separate (mempty, mempty) <$> v .: "parameters" contimport <- v .:? "certname" .!= "unknown"- Resource <$> (RIdentifier <$> fmap Text.toLower (v .: "type") <*> v .: "title")- <*> v .:? "aliases" .!= mempty- <*> pure attribs- <*> pure relations- <*> pure [ContImport contimport ContRoot]- <*> pure virtuality- <*> v .: "tags"- <*> (toPPos <$> v .:? "sourcefile" .!= "null" <*> v .:? "sourceline" .!= 1)- <*> pure contimport-+ Resource+ <$> (RIdentifier <$> fmap Text.toLower (v .: "type") <*> v .: "title")+ <*> v .:? "aliases" .!= mempty+ <*> pure attribs+ <*> pure relations+ <*> pure [ContImport contimport ContRoot]+ <*> pure virtuality+ <*> v .: "tags"+ <*> (toPPos <$> v .:? "sourcefile" .!= "null" <*> v .:? "sourceline" .!= 1)+ <*> pure contimport parseJSON _ = mempty type FinalCatalog = HashMap RIdentifier Resource -- | Relationship/ordering information between two resources (used in the 'EdgeMap'). data LinkInformation = LinkInformation- { _linksrc :: !RIdentifier- , _linkdst :: !RIdentifier- , _linkType :: !LinkType- , _linkPos :: !PPosition- } deriving (Show)+ { _linksrc :: !RIdentifier,+ _linkdst :: !RIdentifier,+ _linkType :: !LinkType,+ _linkPos :: !PPosition+ }+ deriving (Show) makeClassy ''LinkInformation
src/Puppet/Language/Value.hs view
@@ -1,65 +1,67 @@-{-# LANGUAGE TemplateHaskell #-}-module Puppet.Language.Value--where--import XPrelude+{-# LANGUAGE TemplateHaskell #-} -import Data.Aeson-import Data.Aeson.TH-import qualified Data.HashMap.Strict as HM-import Data.Scientific (isInteger)-import Foreign.Ruby.Helpers+module Puppet.Language.Value where -import Puppet.Language.Core+import Data.Aeson+import qualified Data.Aeson.KeyMap as KM+import Data.Aeson.TH+import Data.Scientific (isInteger)+import Foreign.Ruby.Helpers+import Puppet.Language.Core+import XPrelude data DataType- = DTType- | DTString (Maybe Int) (Maybe Int)- | DTInteger (Maybe Int) (Maybe Int)- | DTFloat (Maybe Double) (Maybe Double)- | DTBoolean- | DTArray DataType Int (Maybe Int)- | DTHash DataType DataType Int (Maybe Int)- | DTUndef- | DTScalar- | DTData- | DTOptional DataType- | NotUndef- | DTVariant (NonEmpty DataType)- | DTPattern (NonEmpty CompRegex)- | DTEnum (NonEmpty Text)- | DTAny- | DTCollection- | DTRegexp (Maybe CompRegex)- deriving (Show, Eq)+ = DTType+ | DTString (Maybe Int) (Maybe Int)+ | DTInteger (Maybe Int) (Maybe Int)+ | DTFloat (Maybe Double) (Maybe Double)+ | DTBoolean+ | DTArray DataType Int (Maybe Int)+ | DTHash DataType DataType Int (Maybe Int)+ | DTUndef+ | DTScalar+ | DTData+ | DTOptional DataType+ | NotUndef+ | DTVariant (NonEmpty DataType)+ | DTPattern (NonEmpty CompRegex)+ | DTEnum (NonEmpty Text)+ | DTAny+ | DTCollection+ | DTRegexp (Maybe CompRegex)+ | DTDeferred+ | DTSensitive DataType+ deriving (Show, Eq) instance Pretty DataType where pretty t = case t of- DTType -> "Type"- DTString ma mb -> bounded "String" ma mb- DTInteger ma mb -> bounded "Integer" ma mb- DTFloat ma mb -> bounded "Float" ma mb- DTBoolean -> "Boolean"- DTArray dt mi mmx -> "Array" <> list (pretty dt : pretty mi : maybe [] (pure . pretty) mmx)+ DTType -> "Type"+ DTString ma mb -> bounded "String" ma mb+ DTInteger ma mb -> bounded "Integer" ma mb+ DTFloat ma mb -> bounded "Float" ma mb+ DTBoolean -> "Boolean"+ DTArray dt mi mmx -> "Array" <> list (pretty dt : pretty mi : maybe [] (pure . pretty) mmx) DTHash kt dt mi mmx -> "Hash" <> list (pretty kt : pretty dt : pretty mi : maybe [] (pure . pretty) mmx)- DTUndef -> "Undef"- DTScalar -> "Scalar"- DTData -> "Data"- DTOptional o -> "Optional" <> brackets (pretty o)- NotUndef -> "NotUndef"- DTVariant vs -> "Variant" <> list (foldMap (pure . pretty) vs)- DTPattern vs -> "Pattern" <> list (foldMap (pure . pretty) vs)- DTEnum tx -> "Enum" <> list (foldMap (pure . ppline) tx)- DTAny -> "Any"- DTCollection -> "Collection"- DTRegexp mr -> "Regex" <> foldMap (brackets . pretty) mr+ DTUndef -> "Undef"+ DTScalar -> "Scalar"+ DTData -> "Data"+ DTOptional o -> "Optional" <> brackets (pretty o)+ NotUndef -> "NotUndef"+ DTVariant vs -> "Variant" <> list (foldMap (pure . pretty) vs)+ DTPattern vs -> "Pattern" <> list (foldMap (pure . pretty) vs)+ DTEnum tx -> "Enum" <> list (foldMap (pure . ppline) tx)+ DTAny -> "Any"+ DTCollection -> "Collection"+ DTRegexp mr -> "Regex" <> foldMap (brackets . pretty) mr+ DTDeferred -> "Deferred"+ DTSensitive o -> "Sensitive" <> brackets (pretty o) where bounded :: (Pretty a, Pretty b) => Doc -> Maybe a -> Maybe b -> Doc- bounded s ma mb = s <> case (ma, mb) of- (Just a, Nothing) -> list [pretty a]- (Just a, Just b) -> list [pretty a, pretty b]- _ -> mempty+ bounded s ma mb =+ s <> case (ma, mb) of+ (Just a, Nothing) -> list [pretty a]+ (Just a, Just b) -> list [pretty a, pretty b]+ _ -> mempty $(deriveJSON defaultOptions ''DataType) @@ -73,14 +75,15 @@ | PHash !(Container PValue) | PNumber !Scientific | PType !DataType- | PRegexp !CompRegex+ | PRegexp !CompRegex+ | PSensitive !PValue deriving (Eq, Show) makePrisms ''PValue instance Pretty PValue where- pretty (PBoolean True) = dullmagenta $ "true"- pretty (PBoolean False) = dullmagenta $ "false"+ pretty (PBoolean True) = dullmagenta "true"+ pretty (PBoolean False) = dullmagenta "false" pretty (PString s) = dullcyan (ppline (stringEscape s)) pretty (PNumber n) = cyan (ppline (scientific2text n)) pretty PUndef = dullmagenta "undef"@@ -89,6 +92,7 @@ pretty (PHash g) = containerComma g pretty (PType dt) = pretty dt pretty (PRegexp cr) = pretty cr+ pretty (PSensitive _) = red "[SENSITIVE]" instance IsString PValue where fromString = PString . toS@@ -110,9 +114,9 @@ _ScientificInteger :: Prism' Scientific Integer _ScientificInteger = prism fromIntegral $ \n ->- if isInteger n- then Right (truncate n)- else Left n+ if isInteger n+ then Right (truncate n)+ else Left n _PValueInteger :: Prism' PValue Integer _PValueInteger = _PValueNumber . _ScientificInteger@@ -123,8 +127,8 @@ parseJSON (String s) = return (PString s) parseJSON (Bool b) = return (PBoolean b) parseJSON (Array v) = fmap PArray (mapM parseJSON v)- parseJSON (Object o) | HM.size o == 1 && HM.keys o == ["regexp"] = o .: "regexp"- parseJSON (Object o) = fmap PHash (mapM parseJSON o)+ parseJSON (Object o) | KM.size o == 1 && KM.keys o == ["regexp"] = o .: "regexp"+ parseJSON (Object o) = fmap (PHash . KM.toHashMapText) (traverse parseJSON o) instance ToJSON PValue where toJSON (PType t) = toJSON t@@ -133,16 +137,18 @@ toJSON (PString s) = String s toJSON (PResourceReference _ _) = Null -- TODO toJSON (PArray r) = Array (fmap toJSON r)- toJSON (PHash x) = Object (fmap toJSON x)+ toJSON (PHash x) = Object (KM.fromHashMapText (fmap toJSON x)) toJSON (PNumber n) = Number n toJSON (PRegexp r) = object [("regexp", toJSON r)]+ toJSON (PSensitive x) = toJSON x instance ToRuby PValue where- toRuby = toRuby . toJSON+ toRuby = toRuby . toJSON+ instance FromRuby PValue where fromRuby = fmap chk . fromRuby where chk (Left x) = Left x chk (Right x) = case fromJSON x of- Error rr -> Left rr+ Error rr -> Left rr Success suc -> Right suc
src/Puppet/Language/WireCatalog.hs view
@@ -1,54 +1,56 @@ {-# LANGUAGE TemplateHaskell #-}-module Puppet.Language.WireCatalog -where--import XPrelude--import Data.Aeson+module Puppet.Language.WireCatalog where -import Puppet.Language.Core-import Puppet.Language.Resource+import Data.Aeson+import Puppet.Language.Core+import Puppet.Language.Resource+import XPrelude -- | Used to represent a relationship between two resources within the wired format (json). -- -- See <http://docs.puppetlabs.com/puppetdb/2.3/api/wire_format/catalog_format_v5.html#data-type-edge>-data PuppetEdge = PuppetEdge RIdentifier RIdentifier LinkType deriving Show+data PuppetEdge = PuppetEdge RIdentifier RIdentifier LinkType deriving (Show) instance FromJSON PuppetEdge where- parseJSON (Object v) = PuppetEdge <$> v .: "source" <*> v .: "target" <*> v .: "relationship"- parseJSON _ = fail "invalid puppet edge"+ parseJSON (Object v) = PuppetEdge <$> v .: "source" <*> v .: "target" <*> v .: "relationship"+ parseJSON _ = fail "invalid puppet edge" instance ToJSON PuppetEdge where- toJSON (PuppetEdge s t r) = object [("source", toJSON s), ("target", toJSON t), ("relationship", toJSON r)]+ toJSON (PuppetEdge s t r) = object [("source", toJSON s), ("target", toJSON t), ("relationship", toJSON r)] -- | See <http://docs.puppetlabs.com/puppetdb/1.5/api/wire_format/catalog_format.html puppet reference>. data WireCatalog = WireCatalog- { _wireCatalogNodename :: !NodeName- , _wireCatalogVersion :: !Text- , _wireCatalogEdges :: !(Vector PuppetEdge)- , _wireCatalogResources :: !(Vector Resource)- , _wireCatalogTransactionUUID :: !Text-} deriving Show+ { _wireCatalogNodename :: !NodeName,+ _wireCatalogVersion :: !Text,+ _wireCatalogEdges :: !(Vector PuppetEdge),+ _wireCatalogResources :: !(Vector Resource),+ _wireCatalogTransactionUUID :: !Text+ }+ deriving (Show) makeClassy ''WireCatalog instance FromJSON WireCatalog where- parseJSON (Object d) = d .: "data" >>= \case- (Object v) -> WireCatalog- <$> v .: "name"- <*> v .: "version"- <*> v .: "edges"- <*> v .: "resources"- <*> v .: "transaction-uuid"+ parseJSON (Object d) =+ d .: "data" >>= \case+ (Object v) ->+ WireCatalog+ <$> v .: "name"+ <*> v .: "version"+ <*> v .: "edges"+ <*> v .: "resources"+ <*> v .: "transaction-uuid" _ -> fail "Data is not an object" parseJSON _ = fail "invalid wire catalog" instance ToJSON WireCatalog where toJSON (WireCatalog n v e r t) = object [("metadata", object [("api_version", Number 1)]), ("data", object d)]- where d = [ ("name", String n)- , ("version", String v)- , ("edges", toJSON e)- , ("resources", toJSON r)- , ("transaction-uuid", String t)- ]+ where+ d =+ [ ("name", String n),+ ("version", String v),+ ("edges", toJSON e),+ ("resources", toJSON r),+ ("transaction-uuid", String t)+ ]
src/Puppet/Parser.hs view
@@ -1,25 +1,25 @@-{-| Parse puppet source code from text. -}-module Puppet.Parser (- -- * Runner- runPuppetParser- -- * Parsers- , puppetParser- , prettyParseError- -- * Pretty Print- , module Puppet.Parser.PrettyPrinter- , module Puppet.Parser.Types- , module Puppet.Parser.Lens-) where--import XPrelude+-- | Parse puppet source code from text.+module Puppet.Parser+ ( -- * Runner+ runPuppetParser, -import Text.Megaparsec+ -- * Parsers+ puppetParser,+ prettyParseError, -import Puppet.Parser.PrettyPrinter-import Puppet.Parser.Lens-import Puppet.Parser.Internal-import Puppet.Parser.Types+ -- * Pretty Print+ module Puppet.Parser.PrettyPrinter,+ module Puppet.Parser.Types,+ module Puppet.Parser.Lens,+ )+where +import Puppet.Parser.Internal+import Puppet.Parser.Lens+import Puppet.Parser.PrettyPrinter+import Puppet.Parser.Types+import Text.Megaparsec+import XPrelude -- | Build a 'PrettyError' from a 'ParseError' given the text source. -- The source is used to display the line on which the error occurs.
src/Puppet/Parser/Internal.hs view
@@ -1,24 +1,25 @@ {-# LANGUAGE TupleSections #-}-module Puppet.Parser.Internal-where+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} -import XPrelude.Extra hiding (many, option, some, try)+{-# HLINT ignore "Use id" #-}+{-# HLINT ignore "Use <$>" #-}+module Puppet.Parser.Internal where -import Control.Monad.Combinators.Expr-import qualified Data.Char as Char-import qualified Data.List as List-import qualified Data.List.NonEmpty as NE-import qualified Data.Maybe.Strict as S-import qualified Data.Scientific as Scientific-import qualified Data.Text as Text-import qualified Data.Vector as V-import Text.Megaparsec-import Text.Megaparsec.Char-import qualified Text.Megaparsec.Char.Lexer as Lexer+import Control.Monad.Combinators.Expr+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.List.NonEmpty as NE+import qualified Data.Maybe.Strict as S+import qualified Data.Scientific as Scientific+import qualified Data.Text as Text+import qualified Data.Vector as V+import Puppet.Language+import Puppet.Parser.Types+import Text.Megaparsec+import Text.Megaparsec.Char+import qualified Text.Megaparsec.Char.Lexer as Lexer import qualified Text.Regex.PCRE.ByteString.Utils as Regex--import Puppet.Language-import Puppet.Parser.Types+import XPrelude.Extra hiding (key, many, some, try) -- | Space consumer sc :: Parser ()@@ -61,16 +62,18 @@ ms <- optional $ do symbolic '?' let case_expr = do- c <- SelectorDefault <$ symbol "default" -- default case- <|> SelectorType <$> try datatype- <|> fmap SelectorValue- ( UVariableReference <$> variableReference- <|> UBoolean <$> puppetBool- <|> UUndef <$ symbol "undef"- <|> literalValue- <|> UInterpolable <$> interpolableString- <|> URegexp <$> termRegexp- )+ c <-+ SelectorDefault <$ symbol "default" -- default case+ <|> SelectorType <$> try datatype+ <|> fmap+ SelectorValue+ ( UVariableReference <$> variableReference+ <|> UBoolean <$> puppetBool+ <|> UUndef <$ symbol "undef"+ <|> literalValue+ <|> UInterpolable <$> interpolableString+ <|> URegexp <$> termRegexp+ ) symbol "=>" e <- expression pure (c :!: e)@@ -85,7 +88,7 @@ where interior = Text.pack . concat <$> many (some (noneOf ['\'', '\\']) <|> (char '\\' *> fmap escape anySingle)) escape '\'' = "'"- escape x = ['\\',x]+ escape x = ['\\', x] identifier :: Parser (Tokens Text) identifier = takeWhile1P Nothing isIdentifierChar@@ -111,7 +114,7 @@ qualif :: Parser Text -> Parser Text qualif p = do header <- option "" (chunk "::")- ( header <> ) . Text.intercalate "::" <$> p `sepBy1` chunk "::"+ (header <>) . Text.intercalate "::" <$> p `sepBy1` chunk "::" qualif1 :: Parser Text -> Parser Text qualif1 p = do@@ -151,10 +154,11 @@ genericModuleName :: Bool -> Parser Text genericModuleName isReference = do let acceptable x = Char.isAsciiLower x || Char.isDigit x || (x == '_')- firstletter = if isReference- then fmap Char.toLower (satisfy Char.isAsciiUpper)- else satisfy Char.isAsciiLower- (Text.cons) <$> firstletter <*> takeWhileP Nothing acceptable+ firstletter =+ if isReference+ then fmap Char.toLower (satisfy Char.isAsciiUpper)+ else satisfy Char.isAsciiLower+ Text.cons <$> firstletter <*> takeWhileP Nothing acceptable -- | Variable expression varExpression :: Parser Expression@@ -162,28 +166,34 @@ -- | String interpolation interpolableString :: Parser (Vector Expression)-interpolableString = V.fromList <$> between (char '"') (symbolic '"')- ( many (interpolableVariableReference <|> doubleQuotedStringContent <|> fmap (Terminal . UString . Text.singleton) (char '$')) )+interpolableString =+ V.fromList+ <$> between+ (char '"')+ (symbolic '"')+ (many (interpolableVariableReference <|> doubleQuotedStringContent <|> fmap (Terminal . UString . Text.singleton) (char '$'))) where- doubleQuotedStringContent = Terminal . UString . Text.pack . concat <$>- some ((char '\\' *> fmap escaper anySingle) <|> some (noneOf [ '"', '\\', '$' ]))+ doubleQuotedStringContent =+ Terminal . UString . Text.pack . concat+ <$> some ((char '\\' *> fmap escaper anySingle) <|> some (noneOf ['"', '\\', '$'])) escaper :: Char -> String- escaper 'n' = "\n"- escaper 't' = "\t"- escaper 'r' = "\r"- escaper '"' = "\""+ escaper 'n' = "\n"+ escaper 't' = "\t"+ escaper 'r' = "\r"+ escaper '"' = "\"" escaper '\\' = "\\"- escaper '$' = "$"- escaper x = ['\\',x]+ escaper '$' = "$"+ escaper x = ['\\', x] -- this is specialized because we can't be "tokenized" here varname = Text.concat <$> some (chunk "::" <|> identifier) varexpr = Terminal . UVariableReference <$> varname- indexchain = makeExprParser varexpr [[Postfix indexLookupChain]] -- e.g: os['release']['major']+ indexchain = makeExprParser varexpr [[Postfix indexLookupChain]] -- e.g: os['release']['major'] interpolableVariableReference = do void (char '$')- let fenced = try (indexchain <* char '}')- <|> try (varexpr <* char '}')- <|> (expression <* char '}')+ let fenced =+ try (indexchain <* char '}')+ <|> try (varexpr <* char '}')+ <|> (expression <* char '}') (symbolic '{' *> fenced) <|> try varexpr <|> pure (Terminal (UString (Text.singleton '$'))) integerOrDouble :: Parser (Either Integer Double)@@ -201,9 +211,9 @@ puppetBool :: Parser Bool puppetBool =- (reserved "true" >> pure True)- <|> (reserved "false" >> pure False)- <?> "Boolean"+ (reserved "true" >> pure True)+ <|> (reserved "false" >> pure False)+ <?> "Boolean" resourceReferenceRaw :: Parser (Text, [Expression]) resourceReferenceRaw = do@@ -215,41 +225,43 @@ resourceReference = do (restype, resnames) <- resourceReferenceRaw pure $ UResourceReference restype $ case resnames of- [x] -> x- _ -> Terminal $ UArray (V.fromList resnames)+ [x] -> x+ _ -> Terminal $ UArray (V.fromList resnames) -- | Functions that have named that are not valid ... specialFunctions :: Parser Text specialFunctions =- chunk "Integer"- <|> chunk "Numeric"+ chunk "Integer"+ <|> chunk "Numeric"+ <|> chunk "Sensitive" -- The first argument defines if non-parenthesized arguments are acceptable genFunctionCall :: Bool -> Parser (Text, Vector Expression) genFunctionCall nonparens = do fname <- (specialFunctions <|> funcName) <?> "Function name"- let- -- first check if the function arg is not a qualified name (ex.: include foo::bar)+ let -- first check if the function arg is not a qualified name (ex.: include foo::bar) -- if it is not, then we expect an expression- qualif_param = (Terminal . UString) <$> qualif1 moduleName <* notFollowedBy (single '(') -- <* lookAhead (anySingleBut '(')- func_arg expr = try qualif_param <|> expr <?> "Function argument"+ qualif_param = Terminal . UString <$> qualif1 moduleName <* notFollowedBy (single '(') -- <* lookAhead (anySingleBut '(')+ func_arg expr = try qualif_param <|> expr <?> "Function argument" terminalF = terminalG FunctionWithoutParens expressionF = makeExprParser (lexeme terminalF) expressionTable <?> "Function expression" withparens = parens (func_arg expression `sepEndBy` comma)- withoutparens = if nonparens- then func_arg expressionF `sepEndBy1` comma- else fail "Not an argument list allowed with function without parentheses"- args <- withparens <|> withoutparens+ withoutparens =+ if nonparens+ then func_arg expressionF `sepEndBy1` comma+ else fail "Not an argument list allowed with function without parentheses"+ args <- withparens <|> withoutparens pure (fname, V.fromList args) literalValue :: Parser UnresolvedValue literalValue = lexeme (fmap UString stringLiteral' <|> fmap UString bareword <|> fmap UNumber numericalvalue <?> "Literal Value") where- signed :: Num n => Parser (n -> n)+ signed :: (Num n) => Parser (n -> n) signed = (negate <$ char '-') <|> pure (\x -> x)- numericalvalue = ((,) <$> signed <*> integerOrDouble) >>= \case- (s, Left x) -> pure (s (fromIntegral x))- (s, Right y) -> pure (s (Scientific.fromFloatDigits y))+ numericalvalue =+ ((,) <$> signed <*> integerOrDouble) >>= \case+ (s, Left x) -> pure (s (fromIntegral x))+ (s, Right y) -> pure (s (Scientific.fromFloatDigits y)) data TerminalMode = FunctionWithoutParens@@ -258,30 +270,32 @@ -- this is a hack for functions :( terminalG :: TerminalMode -> Parser Expression terminalG mode =- parens expression- <|> fmap (Terminal . UInterpolable) interpolableString- <|> (Terminal UUndef <$ reserved "undef")- <|> fmap (Terminal . URegexp) termRegexp- <|> varExpression- <|> fmap Terminal puppetArray- <|> fmap Terminal puppetHash- <|> fmap (Terminal . UBoolean) puppetBool- <|> case mode of- FunctionWithoutParens -> remaining- StandardMode -> lambda <|> remaining- where- lambda = fmap Terminal (fmap UHOLambdaCall (try lambdaCall) <|> try funcCall)- remaining = fmap (Terminal . UDataType) datatype- <|> fmap Terminal resourceReference- <|> fmap Terminal literalValue- funcCall :: Parser UnresolvedValue- funcCall = uncurry UFunctionCall <$> genFunctionCall False+ parens expression+ <|> fmap (Terminal . UInterpolable) interpolableString+ <|> (Terminal UUndef <$ reserved "undef")+ <|> fmap (Terminal . URegexp) termRegexp+ <|> varExpression+ <|> fmap Terminal puppetArray+ <|> fmap Terminal puppetHash+ <|> fmap (Terminal . UBoolean) puppetBool+ <|> case mode of+ FunctionWithoutParens -> remaining+ StandardMode -> lambda <|> remaining+ where+ lambda = fmap Terminal (fmap UHOLambdaCall (try lambdaCall) <|> try funcCall)+ remaining =+ fmap (Terminal . UDataType) datatype+ <|> fmap Terminal resourceReference+ <|> fmap Terminal literalValue+ funcCall :: Parser UnresolvedValue+ funcCall = uncurry UFunctionCall <$> genFunctionCall False regexp :: Parser Text regexp = do void (single '/')- Text.pack . concat <$> many ( do { void (char '\\') ; x <- anySingle; return ['\\', x] } <|> some (noneOf [ '/', '\\' ]) )- <* symbolic '/'+ Text.pack . concat+ <$> many (do { void (char '\\'); x <- anySingle; return ['\\', x] } <|> some (noneOf ['/', '\\']))+ <* symbolic '/' compileRegexp :: Text -> Parser CompRegex compileRegexp p = case Regex.compile' Regex.compBlank Regex.execBlank (encodeUtf8 p) of@@ -295,35 +309,36 @@ terminal = terminalG StandardMode expressionTable :: [[Operator Parser Expression]]-expressionTable = [ [ Postfix indexLookupChain ] -- http://stackoverflow.com/questions/10475337/parsec-expr-repeated-prefix-postfix-operator-not-supported- , [ Prefix ( symbolic '-' *> pure Negate ) ]- , [ Prefix ( symbolic '!' *> pure Not ) ]- , [ InfixL ( symbolic '.' *> pure FunctionApplication ) ]- , [ InfixL ( reserved "in" *> pure Contains ) ]- , [ InfixL ( symbolic '/' *> pure Division )- , InfixL ( symbolic '*' *> pure Multiplication )- ]- , [ InfixL ( symbolic '+' *> pure Addition )- , InfixL ( symbolic '-' *> pure Substraction )- ]- , [ InfixL ( symbol "<<" *> pure LeftShift )- , InfixL ( symbol ">>" *> pure RightShift )- ]- , [ InfixL ( symbol "==" *> pure Equal )- , InfixL ( symbol "!=" *> pure Different )- ]- , [ InfixL ( symbol "=~" *> pure RegexMatch )- , InfixL ( symbol "!~" *> pure NotRegexMatch )- ]- , [ InfixL ( symbol ">=" *> pure MoreEqualThan )- , InfixL ( symbol "<=" *> pure LessEqualThan )- , InfixL ( symbol ">" *> pure MoreThan )- , InfixL ( symbol "<" *> pure LessThan )- ]- , [ InfixL ( reserved "and" *> pure And )- , InfixL ( reserved "or" *> pure Or )- ]- ]+expressionTable =+ [ [Postfix indexLookupChain], -- http://stackoverflow.com/questions/10475337/parsec-expr-repeated-prefix-postfix-operator-not-supported+ [Prefix (symbolic '-' $> Negate)],+ [Prefix (symbolic '!' $> Not)],+ [InfixL (symbolic '.' $> FunctionApplication)],+ [InfixL (reserved "in" $> Contains)],+ [ InfixL (symbolic '/' $> Division),+ InfixL (symbolic '*' $> Multiplication)+ ],+ [ InfixL (symbolic '+' $> Addition),+ InfixL (symbolic '-' $> Substraction)+ ],+ [ InfixL (symbol "<<" $> LeftShift),+ InfixL (symbol ">>" $> RightShift)+ ],+ [ InfixL (symbol "==" $> Equal),+ InfixL (symbol "!=" $> Different)+ ],+ [ InfixL (symbol "=~" $> RegexMatch),+ InfixL (symbol "!~" $> NotRegexMatch)+ ],+ [ InfixL (symbol ">=" $> MoreEqualThan),+ InfixL (symbol "<=" $> LessEqualThan),+ InfixL (symbol ">" $> MoreThan),+ InfixL (symbol "<" $> LessThan)+ ],+ [ InfixL (reserved "and" $> And),+ InfixL (reserved "or" $> Or)+ ]+ ] -- | Postfix of a chain of lookup indexes such as "['release']['major']" indexLookupChain :: Parser (Expression -> Expression)@@ -333,18 +348,18 @@ stringExpression :: Parser Expression stringExpression =- (Terminal . UInterpolable) <$> interpolableString- <|> (reserved "undef" $> Terminal UUndef)- <|> (Terminal . UBoolean) <$> puppetBool- <|> varExpression- <|> Terminal <$> literalValue+ Terminal . UInterpolable <$> interpolableString+ <|> (reserved "undef" $> Terminal UUndef)+ <|> Terminal . UBoolean <$> puppetBool+ <|> varExpression+ <|> Terminal <$> literalValue -- | a = b = 0 chainedVariableReferences :: Parser [Text] chainedVariableReferences = do h <- variableReference t <- many (try next)- pure (h:t)+ pure (h : t) where next = symbolic '=' *> variableReference <* lookAhead (single '=' *> space1) @@ -364,7 +379,7 @@ reserved "node" let toString (UString s) = s toString (UNumber n) = scientific2text n- toString _ = panic "Can't happen at nodeDecl"+ toString _ = panic "Can't happen at nodeDecl" nodename = (reserved "default" >> pure NodeDefault) <|> fmap (NodeName . toString) literalValue ns <- (fmap NodeMatch termRegexp <|> nodename) `sepBy1` comma inheritance <- option S.Nothing (fmap S.Just (reserved "inherits" *> nodename))@@ -387,13 +402,13 @@ puppetClassParameters = V.fromList <$> parens (sepComma var) where toStrictMaybe (Just x) = S.Just x- toStrictMaybe Nothing = S.Nothing+ toStrictMaybe Nothing = S.Nothing var :: Parser (Pair (Pair Text (S.Maybe UDataType)) (S.Maybe Expression)) var = do tp <- toStrictMaybe <$> optional datatype- n <- variableReference+ n <- variableReference df <- toStrictMaybe <$> optional (symbolic '=' *> expression)- pure (n :!: tp :!: df)+ pure ((n :!: tp) :!: df) puppetIfStyleCondition :: Parser (Pair Expression (Vector Statement)) puppetIfStyleCondition = (:!:) <$> expression <*> braces statementList@@ -404,22 +419,18 @@ reserved "unless" (cond :!: stmts) <- puppetIfStyleCondition elsecond <- option V.empty (reserved "else" *> braces statementList)- let ec = if V.null elsecond- then []- else [Terminal (UBoolean True) :!: elsecond]+ let ec = [Terminal (UBoolean True) :!: elsecond | not (V.null elsecond)] pe <- getSourcePos- pure (ConditionalDecl (V.fromList ((Not cond :!: stmts) : ec )) (p :!: pe))+ pure (ConditionalDecl (V.fromList ((Not cond :!: stmts) : ec)) (p :!: pe)) ifCondition :: Parser ConditionalDecl ifCondition = do p <- getSourcePos reserved "if" maincond <- puppetIfStyleCondition- others <- many (reserved "elsif" *> puppetIfStyleCondition)+ others <- many (reserved "elsif" *> puppetIfStyleCondition) elsecond <- option V.empty (reserved "else" *> braces statementList)- let ec = if V.null elsecond- then []- else [Terminal (UBoolean True) :!: elsecond]+ let ec = [Terminal (UBoolean True) :!: elsecond | not (V.null elsecond)] pe <- getSourcePos pure (ConditionalDecl (V.fromList (maincond : others ++ ec)) (p :!: pe)) @@ -428,10 +439,11 @@ let puppetRegexpCase = Terminal . URegexp <$> termRegexp defaultCase = Terminal (UBoolean True) <$ reserved "default" matchesToExpression e (x, stmts) = f x :!: stmts- where f = case x of- (Terminal (UBoolean _)) -> identity- (Terminal (URegexp _)) -> RegexMatch e- _ -> Equal e+ where+ f = case x of+ (Terminal (UBoolean _)) -> identity+ (Terminal (URegexp _)) -> RegexMatch e+ _ -> Equal e cases = do matches <- (puppetRegexpCase <|> defaultCase <|> expression) `sepBy1` comma void $ symbolic ':'@@ -442,38 +454,38 @@ expr1 <- expression condlist <- concat <$> braces (some cases) pe <- getSourcePos- pure (ConditionalDecl (V.fromList (map (matchesToExpression expr1) condlist)) (p :!: pe) )+ pure (ConditionalDecl (V.fromList (map (matchesToExpression expr1) condlist)) (p :!: pe)) data OperatorChain a = OperatorChain a LinkType (OperatorChain a) | EndOfChain a instance Foldable OperatorChain where- foldMap f (EndOfChain x) = f x+ foldMap f (EndOfChain x) = f x foldMap f (OperatorChain a _ nx) = f a <> foldMap f nx operatorChainStatement :: OperatorChain a -> a operatorChainStatement (OperatorChain a _ _) = a-operatorChainStatement (EndOfChain x) = x+operatorChainStatement (EndOfChain x) = x -zipChain :: OperatorChain a -> [ ( a, a, LinkType ) ]+zipChain :: OperatorChain a -> [(a, a, LinkType)] zipChain (OperatorChain a d nx) = (a, operatorChainStatement nx, d) : zipChain nx-zipChain (EndOfChain _) = []+zipChain (EndOfChain _) = [] depOperator :: Parser LinkType depOperator =- (RBefore <$ symbol "->")- <|> (RNotify <$ symbol "~>")+ (RBefore <$ symbol "->")+ <|> (RNotify <$ symbol "~>") assignment :: Parser AttributeDecl assignment =- (AttributeDecl <$> lexeme key <*> arrowOp <*> expression)- <|> (AttributeWildcard <$> (symbolic '*' *> symbol "=>" *> expression))+ (AttributeDecl <$> lexeme key <*> arrowOp <*> expression)+ <|> (AttributeWildcard <$> (symbolic '*' *> symbol "=>" *> expression)) where key = bareword <?> "Assignment key" arrowOp =- (AssignArrow <$ symbol "=>")- <|> (AppendArrow <$ symbol "+>")+ (AssignArrow <$ symbol "=>")+ <|> (AppendArrow <$ symbol "+>") -- | Resource Collector resCollDecl :: Position -> Text -> Parser ResCollDecl@@ -486,36 +498,41 @@ void (count (length openchev) (char '>')) sc overrides <- option [] $ braces (sepComma assignment)- let collectortype = if length openchev == 1- then Collector- else ExportedCollector+ let collectortype =+ if length openchev == 1+ then Collector+ else ExportedCollector pe <- getSourcePos- pure (ResCollDecl collectortype restype e (V.fromList overrides) (p :!: pe) )+ pure (ResCollDecl collectortype restype e (V.fromList overrides) (p :!: pe)) where searchExpression :: Parser SearchExpression searchExpression = let searchTable :: [[Operator Parser SearchExpression]]- searchTable = [ [ InfixL ( reserved "and" *> pure AndSearch )- , InfixL ( reserved "or" *> pure OrSearch )- ] ]+ searchTable =+ [ [ InfixL (reserved "and" $> AndSearch),+ InfixL (reserved "or" $> OrSearch)+ ]+ ] searchterm = parens searchExpression <|> check check = do attrib <- parameterName- opr <- (EqualitySearch <$ symbol "==")- <|> (NonEqualitySearch <$ symbol "!=")- term <- stringExpression+ opr <-+ (EqualitySearch <$ symbol "==")+ <|> (NonEqualitySearch <$ symbol "!=")+ term <- stringExpression pure (opr attrib term)- in makeExprParser (lexeme searchterm) searchTable+ in makeExprParser (lexeme searchterm) searchTable classDecl :: Parser ClassDecl classDecl = do p <- getSourcePos reserved "class"- ClassDecl <$> className- <*> option V.empty puppetClassParameters- <*> option S.Nothing (fmap S.Just (reserved "inherits" *> className))- <*> braces statementList- <*> ( (p :!:) <$> getSourcePos )+ ClassDecl+ <$> className+ <*> option V.empty puppetClassParameters+ <*> option S.Nothing (fmap S.Just (reserved "inherits" *> className))+ <*> braces statementList+ <*> ((p :!:) <$> getSourcePos) mainFuncDecl :: Parser MainFuncDecl mainFuncDecl = do@@ -546,11 +563,10 @@ _ -> fail "A method chained by dots." pure (HigherOrderLambdaDecl hf (p :!: pe)) - resDefaultDecl :: Parser ResDefaultDecl resDefaultDecl = do p <- getSourcePos- rnd <- resourceNameRef+ rnd <- resourceNameRef let assignmentList = V.fromList <$> sepComma1 assignment asl <- braces assignmentList pe <- getSourcePos@@ -559,11 +575,11 @@ resOverrideDecl :: Parser [ResOverrideDecl] resOverrideDecl = do p <- getSourcePos- restype <- resourceNameRef+ restype <- resourceNameRef names <- brackets (expression `sepBy1` comma) <?> "Resource reference values" assignments <- V.fromList <$> braces (sepComma assignment) pe <- getSourcePos- pure [ ResOverrideDecl restype n assignments (p :!: pe) | n <- names ]+ pure [ResOverrideDecl restype n assignments (p :!: pe) | n <- names] arrayof :: Parser p -> Parser [p] arrayof p = symbolic '[' *> sepBy p comma <* symbolic ']'@@ -577,31 +593,31 @@ chainableResources = do let withresname = do p <- getSourcePos- restype <- resourceNameRef+ restype <- resourceNameRef lookAhead anySingle >>= \case '[' -> do- resnames <- brackets (expression `sepBy1` comma)- pe <- getSourcePos- pure (ChainResRefr restype resnames (p :!: pe))+ resnames <- brackets (expression `sepBy1` comma)+ pe <- getSourcePos+ pure (ChainResRefr restype resnames (p :!: pe)) _ -> ChainResColl <$> resCollDecl p restype let oneresource = pure <$> try withresname <|> map ChainResDecl <$> resDeclGroup chain <- parseRelationships (oneresource <|> concat <$> arrayof oneresource) let relations = do (g1, g2, lt) <- zipChain chain- (rt1, rn1, _ :!: pe1) <- concatMap extractResRef g1- (rt2, rn2, ps2 :!: _ ) <- concatMap extractResRef g2+ (rt1, rn1, _ :!: pe1) <- concatMap extractResRef g1+ (rt2, rn2, ps2 :!: _) <- concatMap extractResRef g2 pure (DepDecl (rt1 :!: rn1) (rt2 :!: rn2) lt (pe1 :!: ps2)) pure $ map DependencyDeclaration relations <> (chain ^.. folded . folded . to extractChainStatement . folded) where extractResRef :: ChainableRes -> [(Text, Expression, PPosition)]- extractResRef (ChainResColl _) = []- extractResRef (ChainResDecl (ResDecl rt rn _ _ pp)) = [(rt,rn,pp)]- extractResRef (ChainResRefr rt rns pp) = [(rt,rn,pp) | rn <- rns]+ extractResRef (ChainResColl _) = []+ extractResRef (ChainResDecl (ResDecl rt rn _ _ pp)) = [(rt, rn, pp)]+ extractResRef (ChainResRefr rt rns pp) = [(rt, rn, pp) | rn <- rns] extractChainStatement :: ChainableRes -> [Statement] extractChainStatement (ChainResColl r) = [ResourceCollectionDeclaration r] extractChainStatement (ChainResDecl d) = [ResourceDeclaration d]- extractChainStatement ChainResRefr{} = []+ extractChainStatement ChainResRefr {} = [] parseRelationships :: Parser a -> Parser (OperatorChain a) parseRelationships p = do@@ -618,67 +634,69 @@ p <- getSourcePos names <- brackets (sepComma1 resourceName) <|> fmap pure resourceName void $ symbolic ':'- vals <- fmap V.fromList (sepComma assignment)+ vals <- fmap V.fromList (sepComma assignment) pe <- getSourcePos- pure [(n, vals, p :!: pe) | n <- names ]+ pure [(n, vals, p :!: pe) | n <- names] groupDeclaration = (,) <$> many (char '@') <*> typeName <* symbolic '{' (virts, rtype) <- try groupDeclaration -- for matching reasons, this gets a try until the opening brace let sep = symbolic ';' <|> comma x <- resourceDeclaration `sepEndBy1` sep void $ symbolic '}' virtuality <- case virts of- "" -> pure Normal- "@" -> pure Virtual+ "" -> pure Normal+ "@" -> pure Virtual "@@" -> pure Exported- _ -> fail "Invalid virtuality"- return [ ResDecl rtype rname conts virtuality pos | (rname, conts, pos) <- concat x ]+ _ -> fail "Invalid virtuality"+ return [ResDecl rtype rname conts virtuality pos | (rname, conts, pos) <- concat x] statement :: Parser [Statement] statement =- (pure . HigherOrderLambdaDeclaration <$> try dotLambdaDecl)- <|> (pure . VarAssignmentDeclaration <$> varAssign)- <|> (map NodeDeclaration <$> nodeDecl)- <|> (pure . DefineDeclaration <$> defineDecl)- <|> (pure . ConditionalDeclaration <$> unlessCondition)- <|> (pure . ConditionalDeclaration <$> ifCondition)- <|> (pure . ConditionalDeclaration <$> caseCondition)- <|> (pure . ResourceDefaultDeclaration <$> try resDefaultDecl)- <|> (map ResourceOverrideDeclaration <$> try resOverrideDecl)- <|> chainableResources- <|> (pure . ClassDeclaration <$> classDecl)- <|> (pure . HigherOrderLambdaDeclaration <$> try hoLambdaDecl)- <|> (pure . MainFunctionDeclaration <$> mainFuncDecl)- <?> "Statement"+ (pure . HigherOrderLambdaDeclaration <$> try dotLambdaDecl)+ <|> (pure . VarAssignmentDeclaration <$> varAssign)+ <|> (map NodeDeclaration <$> nodeDecl)+ <|> (pure . DefineDeclaration <$> defineDecl)+ <|> (pure . ConditionalDeclaration <$> unlessCondition)+ <|> (pure . ConditionalDeclaration <$> ifCondition)+ <|> (pure . ConditionalDeclaration <$> caseCondition)+ <|> (pure . ResourceDefaultDeclaration <$> try resDefaultDecl)+ <|> (map ResourceOverrideDeclaration <$> try resOverrideDecl)+ <|> chainableResources+ <|> (pure . ClassDeclaration <$> classDecl)+ <|> (pure . HigherOrderLambdaDeclaration <$> try hoLambdaDecl)+ <|> (pure . MainFunctionDeclaration <$> mainFuncDecl)+ <?> "Statement" datatype :: Parser UDataType datatype =- dtString- <|> dtInteger- <|> dtFloat- <|> dtNumeric- <|> (UDTBoolean <$ reserved "Boolean")- <|> (UDTScalar <$ reserved "Scalar")- <|> (UDTData <$ reserved "Data")- <|> (UDTAny <$ reserved "Any")- <|> (UDTCollection <$ reserved "Collection")- <|> dtArray- <|> dtHash- <|> (UDTUndef <$ reserved "Undef")- <|> (reserved "Optional" *> (UDTOptional <$> brackets datatype))- <|> (UNotUndef <$ reserved "NotUndef")- <|> (reserved "Variant" *> (UDTVariant . NE.fromList <$> brackets (datatype `sepBy1` symbolic ',')))- <|> (reserved "Regexp" *> (UDTRegexp <$> optional (brackets termRegexp)))- -- while all the other cases are straightforward, it seems that the- -- following syntax is a valid regexp for puppet:- -- '^dqsqsdqs$'- -- instead of:- -- /^dqsqsdqs$/- --- -- That is the reason there is a "quotedRegexp" case- <|> (reserved "Pattern" *> (UDTPattern . NE.fromList <$> brackets ( (termRegexp <|> quotedRegexp) `sepBy1` symbolic ',')))- <|> (reserved "Enum" *> (UDTEnum . NE.fromList <$> brackets (expression `sepBy1` symbolic ',')))- <|> dtExternal- <?> "UDataType"+ dtString+ <|> dtInteger+ <|> dtFloat+ <|> dtNumeric+ <|> (UDTBoolean <$ reserved "Boolean")+ <|> (UDTScalar <$ reserved "Scalar")+ <|> (UDTData <$ reserved "Data")+ <|> (UDTAny <$ reserved "Any")+ <|> (UDTCollection <$ reserved "Collection")+ <|> dtArray+ <|> dtHash+ <|> (UDTUndef <$ reserved "Undef")+ <|> (reserved "Optional" *> (UDTOptional <$> brackets datatype))+ <|> (UNotUndef <$ reserved "NotUndef")+ <|> (reserved "Variant" *> (UDTVariant . NE.fromList <$> brackets (datatype `sepBy1` symbolic ',')))+ <|> (reserved "Regexp" *> (UDTRegexp <$> optional (brackets termRegexp)))+ <|> (UDTDeferred <$ reserved "Deferred")+ <|> (reserved "Sensitive" *> (UDTSensitive <$> brackets datatype))+ -- while all the other cases are straightforward, it seems that the+ -- following syntax is a valid regexp for puppet:+ -- '^dqsqsdqs$'+ -- instead of:+ -- /^dqsqsdqs$/+ --+ -- That is the reason there is a "quotedRegexp" case+ <|> (reserved "Pattern" *> (UDTPattern . NE.fromList <$> brackets ((termRegexp <|> quotedRegexp) `sepBy1` symbolic ',')))+ <|> (reserved "Enum" *> (UDTEnum . NE.fromList <$> brackets (expression `sepBy1` symbolic ',')))+ <|> dtExternal+ <?> "UDataType" where quotedRegexp = stringLiteral' >>= compileRegexp integer = integerOrDouble >>= either (return . fromIntegral) (\d -> fail ("Integer value expected, instead of " ++ show d))@@ -689,9 +707,9 @@ dtbounded s constructor parser = dtArgs s (constructor Nothing Nothing) $ do lst <- parser `sepBy1` symbolic ',' case lst of- [minlen] -> return $ constructor (Just minlen) Nothing- [minlen,maxlen] -> return $ constructor (Just minlen) (Just maxlen)- _ -> fail ("Too many arguments to datatype " ++ Text.unpack s)+ [minlen] -> return $ constructor (Just minlen) Nothing+ [minlen, maxlen] -> return $ constructor (Just minlen) (Just maxlen)+ _ -> fail ("Too many arguments to datatype " ++ Text.unpack s) dtString = dtbounded "String" UDTString integer dtInteger = dtbounded "Integer" UDTInteger integer dtFloat = dtbounded "Float" UDTFloat float@@ -703,11 +721,11 @@ rst <- optional (symbolic ',' *> integer `sepBy1` symbolic ',') return (tp, rst) case ml of- Nothing -> return (UDTArray UDTData 0 Nothing)- Just (t, Nothing) -> return (UDTArray t 0 Nothing)- Just (t, Just [mi]) -> return (UDTArray t mi Nothing)+ Nothing -> return (UDTArray UDTData 0 Nothing)+ Just (t, Nothing) -> return (UDTArray t 0 Nothing)+ Just (t, Just [mi]) -> return (UDTArray t mi Nothing) Just (t, Just [mi, mx]) -> return (UDTArray t mi (Just mx))- Just (_, Just _) -> fail "Too many arguments to datatype Array"+ Just (_, Just _) -> fail "Too many arguments to datatype Array" dtHash = do reserved "Hash" ml <- optional $ brackets $ do@@ -717,62 +735,67 @@ rst <- optional (symbolic ',' *> integer `sepBy1` symbolic ',') return (tk, tv, rst) case ml of- Nothing -> return (UDTHash UDTScalar UDTData 0 Nothing)- Just (tk, tv, Nothing) -> return (UDTHash tk tv 0 Nothing)- Just (tk, tv, Just [mi]) -> return (UDTHash tk tv mi Nothing)+ Nothing -> return (UDTHash UDTScalar UDTData 0 Nothing)+ Just (tk, tv, Nothing) -> return (UDTHash tk tv 0 Nothing)+ Just (tk, tv, Just [mi]) -> return (UDTHash tk tv mi Nothing) Just (tk, tv, Just [mi, mx]) -> return (UDTHash tk tv mi (Just mx))- Just (_, _, Just _) -> fail "Too many arguments to datatype Hash"+ Just (_, _, Just _) -> fail "Too many arguments to datatype Hash" dtExternal =- choice [ reserved "Stdlib::Absolutepath" $> UDTData- , reserved "Stdlib::Base32" $> UDTData- , reserved "Stdlib::Base64" $> UDTData- , reserved "Stdlib::Compat::Absolute_path" $> UDTData- , reserved "Stdlib::Compat::Array" $> UDTData- , reserved "Stdlib::Compat::Bool" $> UDTData- , reserved "Stdlib::Compat::Float" $> UDTData- , reserved "Stdlib::Compat::Hash" $> UDTData- , reserved "Stdlib::Compat::Integer" $> UDTData- , reserved "Stdlib::Compat::Ip_address" $> UDTData- , reserved "Stdlib::Compat::Ipv4" $> UDTData- , reserved "Stdlib::Compat::Ipv6" $> UDTData- , reserved "Stdlib::Compat::Numeric" $> UDTData- , reserved "Stdlib::Compat::String" $> UDTData- , reserved "Stdlib::Ensure::Service" $> UDTData- , reserved "Stdlib::Filemode" $> UDTData- , reserved "Stdlib::Filesource" $> UDTData- , reserved "Stdlib::Fqdn" $> UDTData- , reserved "Stdlib::Host" $> UDTData- , reserved "Stdlib::HTTPSUrl" $> UDTData- , reserved "Stdlib::HTTPUrl" $> UDTData- , reserved "Stdlib::IP::Address::Nosubnet" $> UDTData- , reserved "Stdlib::Ip_address" $> UDTData- , reserved "Stdlib::IP::Address" $> UDTData- , reserved "Stdlib::IP::Address::V4::CIDR" $> UDTData- , reserved "Stdlib::IP::Address::V4::Nosubnet" $> UDTData- , reserved "Stdlib::IP::Address::V4" $> UDTData- , reserved "Stdlib::IP::Address::V6::Alternative" $> UDTData- , reserved "Stdlib::IP::Address::V6::Compressed" $> UDTData- , reserved "Stdlib::IP::Address::V6::Full" $> UDTData- , reserved "Stdlib::IP::Address::V6::Nosubnet::Alternative" $> UDTData- , reserved "Stdlib::IP::Address::V6::Nosubnet::Compressed" $> UDTData- , reserved "Stdlib::IP::Address::V6::Nosubnet::Full" $> UDTData- , reserved "Stdlib::IP::Address::V6::Nosubnet" $> UDTData- , reserved "Stdlib::IP::Address::V6" $> UDTData- , reserved "Stdlib::Ipv4" $> UDTData- , reserved "Stdlib::Ipv6" $> UDTData- , reserved "Stdlib::MAC" $> UDTData- , reserved "Stdlib::Port::Privileged" $> UDTData- , reserved "Stdlib::Port" $> UDTData- , reserved "Stdlib::Port::Unprivileged" $> UDTData- , reserved "Stdlib::Unixpath" $> UDTData- , reserved "Stdlib::Windowspath" $> UDTData- , reserved "Nginx::ErrorLogSeverity" $> UDTData- , reserved "Jenkins::Tunnel" $> UDTData- , reserved "Systemd::Unit" $> UDTData- , reserved "Systemd::ServiceLimits" $> UDTData- , reserved "Systemd::Dropin" $> UDTData- , reserved "Systemd::JournaldSettings" $> UDTData- ]+ choice+ [ reserved "Stdlib::Absolutepath" $> UDTData,+ reserved "Stdlib::Base32" $> UDTData,+ reserved "Stdlib::Base64" $> UDTData,+ reserved "Stdlib::Compat::Absolute_path" $> UDTData,+ reserved "Stdlib::Compat::Array" $> UDTData,+ reserved "Stdlib::Compat::Bool" $> UDTData,+ reserved "Stdlib::Compat::Float" $> UDTData,+ reserved "Stdlib::Compat::Hash" $> UDTData,+ reserved "Stdlib::Compat::Integer" $> UDTData,+ reserved "Stdlib::Compat::Ip_address" $> UDTData,+ reserved "Stdlib::Compat::Ipv4" $> UDTData,+ reserved "Stdlib::Compat::Ipv6" $> UDTData,+ reserved "Stdlib::Compat::Numeric" $> UDTData,+ reserved "Stdlib::Compat::String" $> UDTData,+ reserved "Stdlib::Ensure::Service" $> UDTData,+ reserved "Stdlib::Filemode" $> UDTData,+ reserved "Stdlib::Filesource" $> UDTData,+ reserved "Stdlib::Fqdn" $> UDTData,+ reserved "Stdlib::Host" $> UDTData,+ reserved "Stdlib::HTTPSUrl" $> UDTData,+ reserved "Stdlib::HTTPUrl" $> UDTData,+ reserved "Stdlib::IP::Address::Nosubnet" $> UDTData,+ reserved "Stdlib::Ip_address" $> UDTData,+ reserved "Stdlib::IP::Address" $> UDTData,+ reserved "Stdlib::IP::Address::V4::CIDR" $> UDTData,+ reserved "Stdlib::IP::Address::V4::Nosubnet" $> UDTData,+ reserved "Stdlib::IP::Address::V4" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Alternative" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Compressed" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Full" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Nosubnet::Alternative" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Nosubnet::Compressed" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Nosubnet::Full" $> UDTData,+ reserved "Stdlib::IP::Address::V6::Nosubnet" $> UDTData,+ reserved "Stdlib::IP::Address::V6" $> UDTData,+ reserved "Stdlib::Ipv4" $> UDTData,+ reserved "Stdlib::Ipv6" $> UDTData,+ reserved "Stdlib::MAC" $> UDTData,+ reserved "Stdlib::Port::Privileged" $> UDTData,+ reserved "Stdlib::Port" $> UDTData,+ reserved "Stdlib::Port::Unprivileged" $> UDTData,+ reserved "Stdlib::Unixpath" $> UDTData,+ reserved "Stdlib::Windowspath" $> UDTData,+ reserved "Nginx::ErrorLogSeverity" $> UDTData,+ reserved "Jenkins::Tunnel" $> UDTData,+ reserved "Systemd::Unit" $> UDTData,+ reserved "Systemd::ServiceLimits" $> UDTData,+ reserved "Systemd::Dropin" $> UDTData,+ reserved "Systemd::JournaldSettings" $> UDTData,+ reserved "Systemd::JournaldSettings::Ensure" $> UDTData,+ reserved "Systemd::LogindSettings" $> UDTData,+ reserved "Systemd::LogindSettings::Ensure" $> UDTData,+ reserved "Systemd::CoredumpSettings" $> UDTData+ ] statementList :: Parser (Vector Statement) statementList = V.fromList . concat <$> many statement@@ -780,12 +803,14 @@ lambdaCall :: Parser HOLambdaCall lambdaCall = do let tostrict (Just x) = S.Just x- tostrict Nothing = S.Nothing- HOLambdaCall <$> lambFunc- <*> parameters- <*> lambParams- <*> (symbolic '{' *> fmap (V.fromList . concat) (many (try statement)))- <*> fmap tostrict (optional expression) <* symbolic '}'+ tostrict Nothing = S.Nothing+ HOLambdaCall+ <$> lambFunc+ <*> parameters+ <*> lambParams+ <*> (symbolic '{' *> fmap (V.fromList . concat) (many (try statement)))+ <*> fmap tostrict (optional expression)+ <* symbolic '}' where parameters :: Parser (V.Vector Expression) parameters = maybe V.empty V.fromList <$> optional (parens (expression `sepBy` comma))
src/Puppet/Parser/Lens.hs view
@@ -1,53 +1,52 @@-{-# LANGUAGE TemplateHaskell #-}-module Puppet.Parser.Lens- (- -- * Prism for 'Statement's- _Statements- , _ResDecl- , _ResDefaultDecl- , _ResOverrDecl- , _ResCollDecl- , _ConditionalDecl- , _ClassDecl- , _DefineDecl- , _NodeDecl- , _VarAssignDecl- , _MainFuncDecl- , _HigherOrderLambdaDecl- , _DepDecl- -- * Prism for 'Expression's- , _Equal- , _Different- , _Not- , _And- , _Or- , _LessThan- , _MoreThan- , _LessEqualThan- , _MoreEqualThan- , _RegexMatch- , _NotRegexMatch- , _Contains- , _Addition- , _Substraction- , _Division- , _Multiplication- , _Modulo- , _RightShift- , _LeftShift- , _Lookup- , _Negate- , _ConditionalValue- , _FunctionApplication- , _Terminal- ) where-+{-# LANGUAGE TemplateHaskell #-} -import XPrelude+module Puppet.Parser.Lens+ ( -- * Prism for 'Statement's+ _Statements,+ _ResDecl,+ _ResDefaultDecl,+ _ResOverrDecl,+ _ResCollDecl,+ _ConditionalDecl,+ _ClassDecl,+ _DefineDecl,+ _NodeDecl,+ _VarAssignDecl,+ _MainFuncDecl,+ _HigherOrderLambdaDecl,+ _DepDecl, -import qualified Data.Vector as V+ -- * Prism for 'Expression's+ _Equal,+ _Different,+ _Not,+ _And,+ _Or,+ _LessThan,+ _MoreThan,+ _LessEqualThan,+ _MoreEqualThan,+ _RegexMatch,+ _NotRegexMatch,+ _Contains,+ _Addition,+ _Substraction,+ _Division,+ _Multiplication,+ _Modulo,+ _RightShift,+ _LeftShift,+ _Lookup,+ _Negate,+ _ConditionalValue,+ _FunctionApplication,+ _Terminal,+ )+where -import Puppet.Parser.Types+import qualified Data.Vector as V+import Puppet.Parser.Types+import XPrelude makePrisms ''Expression @@ -56,91 +55,91 @@ prism ResourceDeclaration $ \x -> case x of ResourceDeclaration a -> Right a- _ -> Left x+ _ -> Left x _ResDefaultDecl :: Prism' Statement ResDefaultDecl _ResDefaultDecl = prism ResourceDefaultDeclaration $ \x -> case x of ResourceDefaultDeclaration a -> Right a- _ -> Left x+ _ -> Left x _ResOverrDecl :: Prism' Statement ResOverrideDecl _ResOverrDecl = prism ResourceOverrideDeclaration $ \x -> case x of ResourceOverrideDeclaration a -> Right a- _ -> Left x+ _ -> Left x _ResCollDecl :: Prism' Statement ResCollDecl _ResCollDecl = prism ResourceCollectionDeclaration $ \x -> case x of ResourceCollectionDeclaration a -> Right a- _ -> Left x+ _ -> Left x _ConditionalDecl :: Prism' Statement ConditionalDecl _ConditionalDecl = prism ConditionalDeclaration $ \x -> case x of ConditionalDeclaration a -> Right a- _ -> Left x+ _ -> Left x _ClassDecl :: Prism' Statement ClassDecl _ClassDecl = prism ClassDeclaration $ \x -> case x of ClassDeclaration a -> Right a- _ -> Left x+ _ -> Left x _DefineDecl :: Prism' Statement DefineDecl _DefineDecl = prism DefineDeclaration $ \x -> case x of DefineDeclaration a -> Right a- _ -> Left x+ _ -> Left x _NodeDecl :: Prism' Statement NodeDecl _NodeDecl = prism NodeDeclaration $ \x -> case x of NodeDeclaration a -> Right a- _ -> Left x+ _ -> Left x _VarAssignDecl :: Prism' Statement VarAssignDecl _VarAssignDecl = prism VarAssignmentDeclaration $ \x -> case x of VarAssignmentDeclaration a -> Right a- _ -> Left x+ _ -> Left x _MainFuncDecl :: Prism' Statement MainFuncDecl _MainFuncDecl = prism MainFunctionDeclaration $ \x -> case x of MainFunctionDeclaration a -> Right a- _ -> Left x+ _ -> Left x _HigherOrderLambdaDecl :: Prism' Statement HigherOrderLambdaDecl _HigherOrderLambdaDecl = prism HigherOrderLambdaDeclaration $ \x -> case x of HigherOrderLambdaDeclaration a -> Right a- _ -> Left x+ _ -> Left x _DepDecl :: Prism' Statement DepDecl _DepDecl = prism DependencyDeclaration $ \x -> case x of DependencyDeclaration a -> Right a- _ -> Left x+ _ -> Left x _TopContainer :: Prism' Statement (V.Vector Statement, Statement) _TopContainer = prism (uncurry TopContainer) $ \x -> case x of TopContainer vs s -> Right (vs, s)- _ -> Left x+ _ -> Left x _Statements :: Lens' Statement [Statement] _Statements =
src/Puppet/Parser/PrettyPrinter.hs view
@@ -1,22 +1,23 @@+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -fno-warn-orphans #-}-module Puppet.Parser.PrettyPrinter- ( ppStatements- ) where -import XPrelude.Extra hiding ((<$>))-import XPrelude.PP--import qualified Data.Maybe.Strict as S-import qualified Data.Text as Text-import qualified Data.Tuple.Strict as Tuple-import qualified Data.Vector as V-import Text.PrettyPrint.ANSI.Leijen ((<$>))--import Puppet.Language-import Puppet.Parser.Types+{-# HLINT ignore "Functor law" #-}+module Puppet.Parser.PrettyPrinter+ ( ppStatements,+ )+where +import qualified Data.Maybe.Strict as S+import qualified Data.Text as Text+import qualified Data.Tuple.Strict as Tuple+import qualified Data.Vector as V+import Puppet.Language+import Puppet.Parser.Types+import Text.PrettyPrint.ANSI.Leijen ((<$>))+import XPrelude.Extra hiding ((<$>))+import XPrelude.PP -parensList :: Pretty a => Vector a -> Doc+parensList :: (Pretty a) => Vector a -> Doc parensList = tupled . fmap pretty . V.toList hashComma :: (Pretty a, Pretty b) => Vector (Pair a b) -> Doc@@ -24,37 +25,39 @@ where showC (a :!: b) = pretty a <+> "=>" <+> pretty b - instance Pretty UDataType where pretty t = case t of- UDTType -> "Type"- UDTString ma mb -> bounded "String" ma mb- UDTInteger ma mb -> bounded "Integer" ma mb- UDTFloat ma mb -> bounded "Float" ma mb- UDTBoolean -> "Boolean"- UDTArray dt mi mmx -> "Array" <> list (pretty dt : pretty mi : maybe [] (pure . pretty) mmx)+ UDTType -> "Type"+ UDTString ma mb -> bounded "String" ma mb+ UDTInteger ma mb -> bounded "Integer" ma mb+ UDTFloat ma mb -> bounded "Float" ma mb+ UDTBoolean -> "Boolean"+ UDTArray dt mi mmx -> "Array" <> list (pretty dt : pretty mi : maybe [] (pure . pretty) mmx) UDTHash kt dt mi mmx -> "Hash" <> list (pretty kt : pretty dt : pretty mi : maybe [] (pure . pretty) mmx)- UDTUndef -> "Undef"- UDTScalar -> "Scalar"- UDTData -> "Data"- UDTOptional o -> "Optional" <> brackets (pretty o)- UNotUndef -> "NotUndef"- UDTVariant vs -> "Variant" <> list (foldMap (pure . pretty) vs)- UDTPattern vs -> "Pattern" <> list (foldMap (pure . pretty) vs)- UDTEnum tx -> "Enum" <> list (foldMap (pure . pretty) tx)- UDTAny -> "Any"- UDTCollection -> "Collection"- UDTRegexp mr -> "Regexp" <> foldMap (brackets . pretty) mr+ UDTUndef -> "Undef"+ UDTScalar -> "Scalar"+ UDTData -> "Data"+ UDTOptional o -> "Optional" <> brackets (pretty o)+ UNotUndef -> "NotUndef"+ UDTVariant vs -> "Variant" <> list (foldMap (pure . pretty) vs)+ UDTPattern vs -> "Pattern" <> list (foldMap (pure . pretty) vs)+ UDTEnum tx -> "Enum" <> list (foldMap (pure . pretty) tx)+ UDTAny -> "Any"+ UDTCollection -> "Collection"+ UDTRegexp mr -> "Regexp" <> foldMap (brackets . pretty) mr+ UDTDeferred -> "Deferred"+ UDTSensitive o -> "Sensitive" <> brackets (pretty o) where bounded :: (Pretty a, Pretty b) => Doc -> Maybe a -> Maybe b -> Doc- bounded s ma mb = s <> case (ma, mb) of- (Just a, Nothing) -> list [pretty a]- (Just a, Just b) -> list [pretty a, pretty b]- _ -> mempty+ bounded s ma mb =+ s <> case (ma, mb) of+ (Just a, Nothing) -> list [pretty a]+ (Just a, Just b) -> list [pretty a, pretty b]+ _ -> mempty instance Pretty Expression where pretty (Equal a b) = parens (pretty a <+> "==" <+> pretty b)- pretty (Different a b) = parens (pretty a <+>"!=" <+> pretty b)+ pretty (Different a b) = parens (pretty a <+> "!=" <+> pretty b) pretty (And a b) = parens (pretty a <+> "and" <+> pretty b) pretty (Or a b) = parens (pretty a <+> "or" <+> pretty b) pretty (LessThan a b) = parens (pretty a <+> pretty '<' <+> pretty b)@@ -116,7 +119,6 @@ pretty (UHOLambdaCall c) = pretty c pretty (UDataType dt) = pretty dt - instance Pretty HOLambdaCall where pretty (HOLambdaCall hf me bp stts mee) = pretty hf <> parensList me <+> pretty bp <+> nest 2 (pretty '{' <> line <> ppStatements stts <> mmee) <$> pretty '}'@@ -125,6 +127,7 @@ case mee of S.Just x -> mempty </> pretty x S.Nothing -> mempty+ instance Pretty SelectorCase where pretty SelectorDefault = dullmagenta "default" pretty (SelectorType t) = pretty t@@ -134,13 +137,15 @@ pretty AssignArrow = "=>" pretty AppendArrow = "+>" - showAss :: Vector AttributeDecl -> Doc showAss vx = folddoc (\a b -> a <> pretty ',' <$> b) prettyDecl (V.toList vx) where folddoc _ _ [] = mempty- folddoc acc docGen (x:xs) = foldl acc (docGen x) (map docGen xs)- maxlen = maximum (fmap (\(AttributeDecl k _ _) -> Text.length k) vx)+ folddoc acc docGen (x : xs) = foldl acc (docGen x) (map docGen xs)+ maxlen = maximum (fmap attributeLength vx)+ attributeLength ad = case ad of+ AttributeDecl k _ _ -> Text.length k+ AttributeWildcard _ -> 0 prettyDecl (AttributeDecl k op v) = dullblue (fill maxlen (ppline k)) <+> pretty op <+> pretty v prettyDecl (AttributeWildcard v) = dullblue "*" <+> pretty AssignArrow <+> pretty v @@ -149,10 +154,13 @@ where lst = V.toList vec maxlen = maximum (map (Text.length . Tuple.fst . Tuple.fst) lst)- ra (argname :!: mtype :!: rval) =- dullblue (pretty '$' <> foldMap (\t -> pretty t <+> mempty) mtype- <> fill maxlen (ppline argname))- <> foldMap (\v -> mempty <+> pretty '=' <+> pretty v) rval+ ra ((argname :!: mtype) :!: rval) =+ dullblue+ ( pretty '$'+ <> foldMap (\t -> pretty t <+> mempty) mtype+ <> fill maxlen (ppline argname)+ )+ <> foldMap (\v -> mempty <+> pretty '=' <+> pretty v) rval showFunc :: Text -> Vector Expression -> Doc showFunc funcname args = bold (red (ppline funcname)) <> parensList args@@ -170,49 +178,55 @@ foldMap (\t -> pretty t <+> mempty) mt <> dullblue (foldMap (\v -> pretty '$' <> ppline v) vs) <+> pretty '=' <+> pretty expr <+> showPPos p instance Pretty Statement where- pretty (HigherOrderLambdaDeclaration (HigherOrderLambdaDecl c p)) = pretty c <+> showPPos p- pretty (ConditionalDeclaration (ConditionalDecl conds p))- | V.null conds = mempty- | otherwise = "if" <+> pretty firstcond <+> showPPos p <+> braceStatements firststts <$> vcat (map rendernexts xs)- where- ( (firstcond :!: firststts) : xs ) = V.toList conds- rendernexts (Terminal (UBoolean True) :!: st) = "else" <+> braceStatements st- rendernexts (c :!: st) | V.null st = mempty- | otherwise = "elsif" <+> pretty c <+> braceStatements st- pretty (MainFunctionDeclaration (MainFuncDecl funcname args p)) = showFunc funcname args <+> showPPos p- pretty (ResourceDefaultDeclaration (ResDefaultDecl rtype defaults p)) = capitalizeR rtype <+> nest 2 (pretty '{' <+> showPPos p <$> showAss defaults) <$> pretty '}'- pretty (ResourceOverrideDeclaration (ResOverrideDecl rtype rnames overs p)) = pretty (UResourceReference rtype rnames) <+> nest 2 (pretty '{' <+> showPPos p <$> showAss overs) <$> pretty '}'- pretty (ResourceDeclaration (ResDecl rtype rname args virt p)) = nest 2 (red vrt <> dullgreen (ppline rtype) <+> pretty '{' <+> showPPos p- <$> nest 2 (pretty rname <> pretty ':' <$> showAss args))- <$> pretty '}'- where- vrt = case virt of- Normal -> mempty- Virtual -> pretty '@'- Exported -> "@@"- ExportedRealized -> "!!"- pretty (DefineDeclaration (DefineDecl cname args stts p)) = dullyellow "define" <+> dullgreen (ppline cname) <> showArgs args <+> showPPos p <$> braceStatements stts- pretty (ClassDeclaration (ClassDecl cname args inherit stts p)) = dullyellow "class" <+> dullgreen (ppline cname) <> showArgs args <> inheritance <+> showPPos p- <$> braceStatements stts- where- inheritance = case inherit of- S.Nothing -> mempty- S.Just x -> mempty <+> "inherits" <+> ppline x- pretty (VarAssignmentDeclaration decl) = pretty decl- pretty (NodeDeclaration (NodeDecl nodename stmts i p)) = dullyellow "node" <+> pretty nodename <> inheritance <+> showPPos p <$> braceStatements stmts- where- inheritance = case i of- S.Nothing -> mempty- S.Just n -> mempty <+> ppline "inherits" <+> pretty n- pretty (DependencyDeclaration (DepDecl (st :!: sn) (dt :!: dn) lt p)) = pretty (UResourceReference st sn) <+> pretty lt <+> pretty (UResourceReference dt dn) <+> showPPos p- pretty (TopContainer a b) = "TopContainer:" <+> braces ( nest 2 ("TOP" <$> braceStatements a <$> "STATEMENT" <$> pretty b))- pretty (ResourceCollectionDeclaration (ResCollDecl coltype restype search overrides p)) = capitalizeR restype <> enc (pretty search) <+> overs- where- overs | V.null overrides = showPPos p- | otherwise = nest 2 (pretty '{' <+> showPPos p <$> showAss overrides) <$> pretty '}'- enc = case coltype of- Collector -> enclose "<|" "|>"- ExportedCollector -> enclose "<<|" "|>>"+ pretty (HigherOrderLambdaDeclaration (HigherOrderLambdaDecl c p)) = pretty c <+> showPPos p+ pretty (ConditionalDeclaration (ConditionalDecl conds p)) = case V.toList conds of+ [] -> mempty+ (firstcond :!: firststts) : xs ->+ let rendernexts (Terminal (UBoolean True) :!: st) = "else" <+> braceStatements st+ rendernexts (c :!: st)+ | V.null st = mempty+ | otherwise = "elsif" <+> pretty c <+> braceStatements st+ in "if" <+> pretty firstcond <+> showPPos p <+> braceStatements firststts <$> vcat (map rendernexts xs)+ pretty (MainFunctionDeclaration (MainFuncDecl funcname args p)) = showFunc funcname args <+> showPPos p+ pretty (ResourceDefaultDeclaration (ResDefaultDecl rtype defaults p)) = capitalizeR rtype <+> nest 2 (pretty '{' <+> showPPos p <$> showAss defaults) <$> pretty '}'+ pretty (ResourceOverrideDeclaration (ResOverrideDecl rtype rnames overs p)) = pretty (UResourceReference rtype rnames) <+> nest 2 (pretty '{' <+> showPPos p <$> showAss overs) <$> pretty '}'+ pretty (ResourceDeclaration (ResDecl rtype rname args virt p)) =+ nest+ 2+ ( red vrt <> dullgreen (ppline rtype) <+> pretty '{' <+> showPPos p+ <$> nest 2 (pretty rname <> pretty ':' <$> showAss args)+ )+ <$> pretty '}'+ where+ vrt = case virt of+ Normal -> mempty+ Virtual -> pretty '@'+ Exported -> "@@"+ ExportedRealized -> "!!"+ pretty (DefineDeclaration (DefineDecl cname args stts p)) = dullyellow "define" <+> dullgreen (ppline cname) <> showArgs args <+> showPPos p <$> braceStatements stts+ pretty (ClassDeclaration (ClassDecl cname args inherit stts p)) =+ dullyellow "class" <+> dullgreen (ppline cname) <> showArgs args <> inheritance <+> showPPos p+ <$> braceStatements stts+ where+ inheritance = case inherit of+ S.Nothing -> mempty+ S.Just x -> mempty <+> "inherits" <+> ppline x+ pretty (VarAssignmentDeclaration decl) = pretty decl+ pretty (NodeDeclaration (NodeDecl nodename stmts i p)) = dullyellow "node" <+> pretty nodename <> inheritance <+> showPPos p <$> braceStatements stmts+ where+ inheritance = case i of+ S.Nothing -> mempty+ S.Just n -> mempty <+> ppline "inherits" <+> pretty n+ pretty (DependencyDeclaration (DepDecl (st :!: sn) (dt :!: dn) lt p)) = pretty (UResourceReference st sn) <+> pretty lt <+> pretty (UResourceReference dt dn) <+> showPPos p+ pretty (TopContainer a b) = "TopContainer:" <+> braces (nest 2 ("TOP" <$> braceStatements a <$> "STATEMENT" <$> pretty b))+ pretty (ResourceCollectionDeclaration (ResCollDecl coltype restype search overrides p)) = capitalizeR restype <> enc (pretty search) <+> overs+ where+ overs+ | V.null overrides = showPPos p+ | otherwise = nest 2 (pretty '{' <+> showPPos p <$> showAss overrides) <$> pretty '}'+ enc = case coltype of+ Collector -> enclose "<|" "|>"+ ExportedCollector -> enclose "<<|" "|>>" -- | Pretty print a series of statements. ppStatements :: Vector Statement -> Doc
src/Puppet/Parser/Types.hs view
@@ -1,63 +1,65 @@-{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TemplateHaskell #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeFamilies #-}+ -- | All the types used for parsing, and helpers working on these types. module Puppet.Parser.Types- ( -- ** Expressions- Expression(..),- SelectorCase(..),- UnresolvedValue(..),- LambdaFunc(..),- HOLambdaCall(..),- ChainableRes(..),- HasHOLambdaCall(..),- LambdaParameter(..),- LambdaParameters,- CompRegex(..),- CollectorType(..),- Virtuality(..),- NodeDesc(..),- LinkType(..),- -- ** Synonyms- Parser,- PuppetParseError,- -- ** Datatypes- UDataType(..),- -- ** Search Expressions- SearchExpression(..),- -- ** Declaration- AttributeDecl(..),- ArrowOp(..),- ConditionalDecl(..),- ClassDecl(..),- ResDefaultDecl(..),- DepDecl(..),- Statement(..),- ResDecl(..),- ResOverrideDecl(..),- DefineDecl(..),- NodeDecl(..),- VarAssignDecl(..),- MainFuncDecl(..),- HigherOrderLambdaDecl(..),- ResCollDecl(..),- Parameters- ) where+ ( -- ** Expressions+ Expression (..),+ SelectorCase (..),+ UnresolvedValue (..),+ LambdaFunc (..),+ HOLambdaCall (..),+ ChainableRes (..),+ HasHOLambdaCall (..),+ LambdaParameter (..),+ LambdaParameters,+ CompRegex (..),+ CollectorType (..),+ Virtuality (..),+ NodeDesc (..),+ LinkType (..), -import XPrelude hiding (show)+ -- ** Synonyms+ Parser,+ PuppetParseError, -import Data.List.NonEmpty (NonEmpty)-import qualified Data.Maybe.Strict as S-import qualified Data.Text as Text-import qualified Data.Vector as V-import qualified GHC.Exts as Exts-import GHC.Show (Show (..))-import Text.Megaparsec+ -- ** Datatypes+ UDataType (..), -import Puppet.Language+ -- ** Search Expressions+ SearchExpression (..), + -- ** Declaration+ AttributeDecl (..),+ ArrowOp (..),+ ConditionalDecl (..),+ ClassDecl (..),+ ResDefaultDecl (..),+ DepDecl (..),+ Statement (..),+ ResDecl (..),+ ResOverrideDecl (..),+ DefineDecl (..),+ NodeDecl (..),+ VarAssignDecl (..),+ MainFuncDecl (..),+ HigherOrderLambdaDecl (..),+ ResCollDecl (..),+ Parameters,+ )+where +import qualified Data.Maybe.Strict as S+import qualified Data.Text as Text+import qualified Data.Vector as V+import qualified GHC.Exts as Exts+import Puppet.Language+import Text.Megaparsec+import XPrelude hiding (show)+ type PuppetParseError = ParseError Char Void+ type Parser = Parsec Void Text -- | /High Order lambdas/.@@ -71,14 +73,14 @@ deriving (Eq, Show) -- The description of the /higher level lambda/ call.-data HOLambdaCall- = HOLambdaCall- { _hoLambdaFunc :: !LambdaFunc- , _hoLambdaExpr :: !(Vector Expression)- , _hoLambdaParams :: !LambdaParameters- , _hoLambdaStatements :: !(Vector Statement)- , _hoLambdaLastExpr :: !(S.Maybe Expression)- } deriving (Eq,Show)+data HOLambdaCall = HOLambdaCall+ { _hoLambdaFunc :: !LambdaFunc,+ _hoLambdaExpr :: !(Vector Expression),+ _hoLambdaParams :: !LambdaParameters,+ _hoLambdaStatements :: !(Vector Statement),+ _hoLambdaLastExpr :: !(S.Maybe Expression)+ }+ deriving (Eq, Show) data ChainableRes = ChainResColl !ResCollDecl@@ -92,20 +94,28 @@ deriving (Show, Eq) data ArrowOp- = AppendArrow -- ^ `+>`- | AssignArrow -- ^ `=>`+ = -- | `+>`+ AppendArrow+ | -- | `=>`+ AssignArrow deriving (Show, Eq) -- | An unresolved value, typically the parser's output. data UnresolvedValue- = UBoolean !Bool -- ^ Special tokens generated when parsing the @true@ or @false@ literals.- | UString !Text -- ^ Raw string.- | UInterpolable !(Vector Expression) -- ^ A string that might contain variable references. The type should be refined at one point.- | UUndef -- ^ Special token that is generated when parsing the @undef@ literal.- | UResourceReference !Text !Expression -- ^ Alike @Resource[reference]@+ = -- | Special tokens generated when parsing the @true@ or @false@ literals.+ UBoolean !Bool+ | -- | Raw string.+ UString !Text+ | -- | A string that might contain variable references. The type should be refined at one point.+ UInterpolable !(Vector Expression)+ | -- | Special token that is generated when parsing the @undef@ literal.+ UUndef+ | -- | Alike @Resource[reference]@+ UResourceReference !Text !Expression | UArray !(Vector Expression) | UHash !(Vector (Pair Expression Expression))- | URegexp !CompRegex -- ^ The regular expression compilation is performed during parsing.+ | -- | The regular expression compilation is performed during parsing.+ URegexp !CompRegex | UVariableReference !Text | UFunctionCall !Text !(Vector Expression) | UHOLambdaCall !HOLambdaCall@@ -114,11 +124,11 @@ deriving (Show, Eq) instance Exts.IsList UnresolvedValue where- type Item UnresolvedValue = Expression+ type Item UnresolvedValue = Expression fromList = UArray . V.fromList toList u = case u of UArray lst -> V.toList lst- _ -> [Terminal u]+ _ -> [Terminal u] instance IsString UnresolvedValue where fromString = UString . Text.pack@@ -150,11 +160,15 @@ | Modulo !Expression !Expression | RightShift !Expression !Expression | LeftShift !Expression !Expression- | Lookup !Expression !Expression -- ^ Hash lookup @$var[\'key0\'][\'key1\']@+ | -- | Hash lookup @$var[\'key0\'][\'key1\']@+ Lookup !Expression !Expression | Negate !Expression- | ConditionalValue !Expression !(Vector (Pair SelectorCase Expression)) -- ^ All conditionals are stored in this format.- | FunctionApplication !Expression !Expression -- ^ This is for /higher order functions/.- | Terminal !UnresolvedValue -- ^ Terminal object contains no expression+ | -- | All conditionals are stored in this format.+ ConditionalValue !Expression !(Vector (Pair SelectorCase Expression))+ | -- | This is for /higher order functions/.+ FunctionApplication !Expression !Expression+ | -- | Terminal object contains no expression+ Terminal !UnresolvedValue deriving (Eq, Show) data UDataType@@ -176,6 +190,8 @@ | UDTAny | UDTCollection | UDTRegexp (Maybe CompRegex)+ | UDTDeferred+ | UDTSensitive UDataType -- Tuple (NonEmpty DataType) Integer Integer -- DTDefault -- Struct TODO@@ -186,7 +202,7 @@ fromList = Terminal . Exts.fromList toList u = case u of Terminal t -> Exts.toList t- _ -> [u]+ _ -> [u] instance Num Expression where (+) = Addition@@ -194,9 +210,15 @@ (*) = Multiplication fromInteger = Terminal . UNumber . fromInteger abs x = ConditionalValue (MoreEqualThan x 0) (V.fromList [SelectorValue (UBoolean True) :!: x, SelectorDefault :!: negate x])- signum x = ConditionalValue (MoreThan x 0) (V.fromList [SelectorValue (UBoolean True) :!: 1, SelectorDefault :!:- ConditionalValue (Equal x 0) (V.fromList [SelectorValue (UBoolean True) :!: 0, SelectorDefault :!: (-1)])- ])+ signum x =+ ConditionalValue+ (MoreThan x 0)+ ( V.fromList+ [ SelectorValue (UBoolean True) :!: 1,+ SelectorDefault+ :!: ConditionalValue (Equal x 0) (V.fromList [SelectorValue (UBoolean True) :!: 0, SelectorDefault :!: (-1)])+ ]+ ) instance Fractional Expression where (/) = Division@@ -216,8 +238,10 @@ deriving (Eq, Show) data CollectorType- = Collector -- ^ Single angle brackets @\<| |>@- | ExportedCollector -- ^ Double angle brackets @\<\<| |>>@+ = -- | Single angle brackets @\<| |>@+ Collector+ | -- | Double angle brackets @\<\<| |>>@+ ExportedCollector deriving (Eq, Show) data NodeDesc@@ -226,7 +250,6 @@ | NodeDefault deriving (Show, Eq) - -- | Resource declaration: -- -- @ file { mode => 755} @@@ -259,7 +282,7 @@ -- * an optional inherits -- * a list of statements -- * a position-data ClassDecl = ClassDecl !Text !Parameters !(S.Maybe Text) !(Vector Statement) !PPosition deriving (Eq, Show)+data ClassDecl = ClassDecl !Text !Parameters !(S.Maybe Text) !(Vector Statement) !PPosition deriving (Eq, Show) -- | Declare a define with -- * a name@@ -274,15 +297,15 @@ data NodeDecl = NodeDecl !NodeDesc !(Vector Statement) !(S.Maybe NodeDesc) !PPosition deriving (Eq, Show) -- | @ $newvar = 'world' @-data VarAssignDecl- = VarAssignDecl- { _vadtype :: Maybe UDataType- , _vadnames :: [Text]- , _vadvalue :: !Expression- , _vadpos :: !PPosition- } deriving (Eq, Show)+data VarAssignDecl = VarAssignDecl+ { _vadtype :: Maybe UDataType,+ _vadnames :: [Text],+ _vadvalue :: !Expression,+ _vadpos :: !PPosition+ }+ deriving (Eq, Show) -data MainFuncDecl = MainFuncDecl !Text !(Vector Expression) !PPosition deriving (Eq, Show)+data MainFuncDecl = MainFuncDecl !Text !(Vector Expression) !PPosition deriving (Eq, Show) -- | /Higher order function/ call. data HigherOrderLambdaDecl = HigherOrderLambdaDecl !HOLambdaCall !PPosition deriving (Eq, Show)@@ -310,7 +333,8 @@ | MainFunctionDeclaration !MainFuncDecl | HigherOrderLambdaDeclaration !HigherOrderLambdaDecl | DependencyDeclaration !DepDecl- | TopContainer !(Vector Statement) !Statement -- ^ Special statement used to include the expressions that are top level. Certainly buggy, but probably just like the original implementation.+ | -- | Special statement used to include the expressions that are top level. Certainly buggy, but probably just like the original implementation.+ TopContainer !(Vector Statement) !Statement deriving (Eq, Show) makeClassy ''HOLambdaCall
src/Puppet/Runner.hs view
@@ -7,27 +7,32 @@ -- * a bunch of pure runners -- -- Naturally nothing from "Puppet.Runner" should be used in lower abstraction layers.-module Puppet.Runner(- -- * Preferences- module Puppet.Runner.Preferences- -- * Pure- , module Puppet.Runner.Pure- -- * Stats- , module Puppet.Runner.Stats- -- * Sdlib- , module Puppet.Runner.Stdlib- -- * Daemon- , module Puppet.Runner.Daemon- -- * Re-export- , module Puppet.Runner.Erb.Evaluate- , module Puppet.Interpreter+module Puppet.Runner+ ( -- * Preferences+ module Puppet.Runner.Preferences,++ -- * Pure+ module Puppet.Runner.Pure,++ -- * Stats+ module Puppet.Runner.Stats,++ -- * Sdlib+ module Puppet.Runner.Stdlib,++ -- * Daemon+ module Puppet.Runner.Daemon,++ -- * Re-export+ module Puppet.Runner.Erb.Evaluate,+ module Puppet.Interpreter, )- where+where +import Puppet.Interpreter import Puppet.Runner.Daemon+import Puppet.Runner.Erb.Evaluate import Puppet.Runner.Preferences+import Puppet.Runner.Pure import Puppet.Runner.Stats import Puppet.Runner.Stdlib-import Puppet.Runner.Pure-import Puppet.Runner.Erb.Evaluate-import Puppet.Interpreter
src/Puppet/Runner/Daemon.hs view
@@ -1,133 +1,137 @@-{-# LANGUAGE GADTs #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE TupleSections #-}-module Puppet.Runner.Daemon (- Daemon(..)- , initDaemon-) where--import XPrelude--import qualified Data.Either.Strict as S-import Data.FileCache as FileCache-import qualified Data.HashMap.Strict as HM-import qualified Data.Text as Text-import Debug.Trace (traceEventIO)-import Foreign.Ruby.Safe-import qualified System.Directory as Directory-import qualified System.Log.Formatter as Log (simpleLogFormatter)-import qualified System.Log.Handler as Log (setFormatter)-import qualified System.Log.Handler.Simple as Log (streamHandler)-import qualified System.Log.Logger as Log--import Facter-import Hiera.Server-import Puppet.Runner.Daemon.FileParser-import Puppet.Runner.Daemon.OptionalTests-import Puppet.Runner.Erb-import Puppet.Interpreter-import Puppet.Parser-import Puppet.Runner.Preferences-import Puppet.Runner.Stats---{-| API for the Daemon.-The main method is `getCatalog`: given a node and a list of facts, it returns the result of the compilation.-This will be either an error, or a tuple containing:+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} -- all the resources in this catalog-- the dependency map-- the exported resources-- a list of known resources, that might not be up to date, but are here for code coverage tests.+{-# HLINT ignore "Use <$>" #-}+module Puppet.Runner.Daemon+ ( Daemon (..),+ initDaemon,+ )+where -Notes :+import qualified Data.Either.Strict as S+import Data.FileCache as FileCache+import qualified Data.HashMap.Strict as HM+import qualified Data.Text as Text+import Debug.Trace (traceEventIO)+import Facter+import Foreign.Ruby.Safe+import Hiera.Server+import Puppet.Interpreter+import Puppet.Parser+import Puppet.Runner.Daemon.FileParser+import Puppet.Runner.Daemon.OptionalTests+import Puppet.Runner.Erb+import Puppet.Runner.Preferences+import Puppet.Runner.Stats+import qualified System.Directory as Directory+import qualified System.Log.Formatter as Log (simpleLogFormatter)+import qualified System.Log.Handler as Log (setFormatter)+import qualified System.Log.Handler.Simple as Log (streamHandler)+import qualified System.Log.Logger as Log+import XPrelude -* It might be buggy when top level statements that are not class\/define\/nodes are altered.--}+-- | API for the Daemon.+-- The main method is `getCatalog`: given a node and a list of facts, it returns the result of the compilation.+-- This will be either an error, or a tuple containing:+--+-- - all the resources in this catalog+-- - the dependency map+-- - the exported resources+-- - a list of known resources, that might not be up to date, but are here for code coverage tests.+--+-- Notes :+--+-- * It might be buggy when top level statements that are not class\/define\/nodes are altered. data Daemon = Daemon- { getCatalog :: NodeName -> Facts -> IO (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource]))- , parserStats :: MStats- , catalogStats :: MStats- , templateStats :: MStats+ { getCatalog :: NodeName -> Facts -> IO (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource])),+ parserStats :: MStats,+ catalogStats :: MStats,+ templateStats :: MStats } -{-| Entry point to get a Daemon.-It will initialize the parsing and interpretation infrastructure from the 'Preferences'.--Cache the AST of every .pp file. It could use a bit of memory. As a comparison, it-fits in 60 MB with the author's manifests, but really breathes when given 300 MB-of heap space. In this configuration, even if it spawns a ruby process for every-template evaluation, it is way faster than the puppet stack.--It can optionally talk with PuppetDB, by setting an URL via the 'prefPDB'.-The recommended way to set it to http://localhost:8080 and set a SSH tunnel :--> ssh -L 8080:localhost:8080 puppet.host--}-initDaemon :: Preferences IO- -> IO Daemon+-- | Entry point to get a Daemon.+-- It will initialize the parsing and interpretation infrastructure from the 'Preferences'.+--+-- Cache the AST of every .pp file. It could use a bit of memory. As a comparison, it+-- fits in 60 MB with the author's manifests, but really breathes when given 300 MB+-- of heap space. In this configuration, even if it spawns a ruby process for every+-- template evaluation, it is way faster than the puppet stack.+--+-- It can optionally talk with PuppetDB, by setting an URL via the 'prefPDB'.+-- The recommended way to set it to http://localhost:8080 and set a SSH tunnel :+--+-- > ssh -L 8080:localhost:8080 puppet.host+initDaemon ::+ Preferences IO ->+ IO Daemon initDaemon pref = do setupLogger (pref ^. prefLogLevel) logDebug "Initialize daemon" traceEventIO "initDaemon"- hquery <- hieraQuery pref- fcache <- newFileCache- intr <- startRubyInterpreter- templStats <- newStats+ hquery <- hieraQuery pref+ fcache <- newFileCache+ intr <- startRubyInterpreter+ templStats <- newStats getTemplate <- initTemplateDaemon intr pref templStats- catStats <- newStats- parseStats <- newStats- pure (Daemon- (getCatalog' pref (parseFunc (pref ^. prefPuppetPaths) fcache parseStats) getTemplate catStats hquery)- parseStats- catStats- templStats- )+ catStats <- newStats+ parseStats <- newStats+ pure+ ( Daemon+ (getCatalog' pref (parseFunc (pref ^. prefPuppetPaths) fcache parseStats) getTemplate catStats hquery)+ parseStats+ catStats+ templStats+ ) -getCatalog' :: Preferences IO- -> ( TopLevelType -> Text -> IO (S.Either PrettyError Statement) )- -> (TemplateSource -> InterpreterState -> InterpreterReader IO -> IO (S.Either PrettyError Text))- -> MStats- -> HieraQueryLayers IO- -> NodeName- -> Facts- -> IO (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource]))+getCatalog' ::+ Preferences IO ->+ (TopLevelType -> Text -> IO (S.Either PrettyError Statement)) ->+ (TemplateSource -> InterpreterState -> InterpreterReader IO -> IO (S.Either PrettyError Text)) ->+ MStats ->+ HieraQueryLayers IO ->+ NodeName ->+ Facts ->+ IO (Either PrettyError (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) getCatalog' pref parsingfunc getTemplate stats hquery node facts = do logDebug ("Received query for node " <> node) traceEventIO ("START getCatalog' " <> Text.unpack node)- let catalogComputation = interpretCatalog (InterpreterReader- (pref ^. prefNatTypes)- parsingfunc- getTemplate- (pref ^. prefPDB)- (pref ^. prefExtFuncs)- node- hquery- defaultImpureMethods- (pref ^. prefIgnoredmodules)- (pref ^. prefExternalmodules)- (pref ^. prefStrictness == Strict)- (pref ^. prefPuppetPaths)- (pref ^. prefRebaseFile)- ((pref ^. prefFactsOverride) `HM.union` (pref ^. prefFactsDefault))- )- node- facts- (pref ^. prefPuppetSettings)+ let catalogComputation =+ interpretCatalog+ ( InterpreterReader+ (pref ^. prefNatTypes)+ parsingfunc+ getTemplate+ (pref ^. prefPDB)+ (pref ^. prefExtFuncs)+ node+ hquery+ defaultImpureMethods+ (pref ^. prefIgnoredmodules)+ (pref ^. prefExternalmodules)+ (pref ^. prefStrictness == Strict)+ (pref ^. prefPuppetPaths)+ (pref ^. prefRebaseFile)+ ((pref ^. prefFactsOverride) `HM.union` (pref ^. prefFactsDefault))+ )+ node+ facts+ (pref ^. prefPuppetSettings) (stmts :!: warnings) <- measure stats node catalogComputation mapM_ (\(p :!: m) -> Log.logM loggerName p (displayS (renderCompact (ppline node <> ":" <+> m)) "")) warnings traceEventIO ("STOP getCatalog' " <> toS node) case stmts of Left _ -> pure stmts -- no catalog so we can't do the extra tests- Right r@(c,_,_,_) -> do+ Right r@(c, _, _, _) -> do if pref ^. prefExtraTests- then second (const r) <$> (testCatalog pref c)+ then second (const r) <$> testCatalog pref c else pure stmts -- Build the 'HieraQueryLayers' needed by the interpreter to lookup hiera values. hieraQuery :: Preferences IO -> IO (HieraQueryLayers IO) hieraQuery pref = do global_api <- case pref ^. prefHieraPath of- Just p -> startHiera "global" p+ Just p -> startHiera "global" p Nothing -> pure dummyHiera env_api <- getEnvApi mod_api <- getModApis@@ -135,29 +139,31 @@ where getEnvApi :: IO (HieraQueryFunc IO) getEnvApi = do- let f = (pref ^. prefPuppetPaths . baseDir) <> "/hiera.yaml"+ let f = (pref ^. prefPuppetPaths . baseDir) <> "/hiera.yaml" found <- Directory.doesFileExist f if found then startHiera "environment" f else pure dummyHiera getModApis :: IO (Container (HieraQueryFunc IO)) getModApis = do- let ignored_modules = pref^.prefIgnoredmodules- modpath = pref^.prefPuppetPaths.modulesPath+ let ignored_modules = pref ^. prefIgnoredmodules+ modpath = pref ^. prefPuppetPaths . modulesPath dirs <- Directory.listDirectory modpath- (HM.fromList . catMaybes) <$>- for dirs (\dir -> runMaybeT $ do- let modname = toS dir- fp = modpath <> "/" <> dir <> "/hiera.yaml"- guard (modname `notElem` ignored_modules)- guard =<< liftIO (Directory.doesFileExist fp)- liftIO $ (modname, ) <$> startHiera "module" fp)-+ HM.fromList . catMaybes+ <$> for+ dirs+ ( \dir -> runMaybeT $ do+ let modname = toS dir+ fp = modpath <> "/" <> dir <> "/hiera.yaml"+ guard (modname `notElem` ignored_modules)+ guard =<< liftIO (Directory.doesFileExist fp)+ liftIO $ (modname,) <$> startHiera "module" fp+ ) -defaultImpureMethods :: MonadIO m => IoMethods m+defaultImpureMethods :: (MonadIO m) => IoMethods m defaultImpureMethods = IoMethods (liftIO currentCallStack) (liftIO . file) (liftIO . traceEventIO) where file [] = return $ Left ""- file (x:xs) = (Right <$> readFile (Text.unpack x)) `catch` (\SomeException {} -> file xs)+ file (x : xs) = (Right <$> readFile (Text.unpack x)) `catch` (\SomeException {} -> file xs) setupLogger :: Log.Priority -> IO () setupLogger p = do@@ -165,6 +171,7 @@ hs <- consoleLogHandler Log.updateGlobalLogger Log.rootLoggerName $ Log.setHandlers [hs] where- consoleLogHandler = Log.setFormatter- <$> Log.streamHandler stdout Log.DEBUG- <*> pure (Log.simpleLogFormatter "$prio: $msg")+ consoleLogHandler =+ Log.setFormatter+ <$> Log.streamHandler stdout Log.DEBUG+ <*> pure (Log.simpleLogFormatter "$prio: $msg")
src/Puppet/Runner/Daemon/FileParser.hs view
@@ -1,44 +1,42 @@ {-# LANGUAGE GADTs #-}-module Puppet.Runner.Daemon.FileParser (parseFunc) where -import XPrelude+module Puppet.Runner.Daemon.FileParser (parseFunc) where -import qualified Data.Either.Strict as S-import Data.FileCache as FileCache-import qualified Data.HashMap.Strict as Map-import qualified Data.List as List-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Data.Vector as V-import Debug.Trace (traceEventIO)-import qualified Text.Megaparsec as Megaparsec+import qualified Data.Either.Strict as S+import Data.FileCache as FileCache+import qualified Data.HashMap.Strict as Map+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Vector as V+import Debug.Trace (traceEventIO)+import Puppet.Interpreter+import Puppet.Parser+import Puppet.Runner.Stats+import qualified Text.Megaparsec as Megaparsec import qualified Text.Regex.PCRE.ByteString.Utils as Regex--import Puppet.Interpreter-import Puppet.Parser-import Puppet.Runner.Stats+import XPrelude -- | Return an HOF that would parse the file associated with a toplevel. -- The toplevel is defined by the tuple (type, name) -- The result of the parsing is a single Statement (which recursively contains others statements) parseFunc :: PuppetDirPaths -> FileCacheR PrettyError (V.Vector Statement) -> MStats -> TopLevelType -> Text -> IO (S.Either PrettyError Statement)-parseFunc ppath filecache stats = \toptype topname ->- let nameparts = Text.splitOn "::" topname in- let topLevelFilePath :: TopLevelType -> Text -> Either PrettyError Text- topLevelFilePath TopNode _ = Right $ Text.pack (ppath^.manifestPath <> "/site.pp")- topLevelFilePath _ name- | length nameparts == 1 = Right $ Text.pack (ppath^.modulesPath) <> "/" <> name <> "/manifests/init.pp"- | null nameparts = Left $ PrettyError ("Invalid toplevel" <+> squotes (ppline name))- | otherwise = Right $ Text.pack (ppath^.modulesPath) <> "/" <> List.head nameparts <> "/manifests/" <> Text.intercalate "/" (List.tail nameparts) <> ".pp"- in- case topLevelFilePath toptype topname of- Left rr -> return (S.Left rr)- Right fname -> do+parseFunc ppath filecache stats toptype topname =+ let nameparts = Text.splitOn "::" topname+ topLevelFilePath :: TopLevelType -> Text -> Either PrettyError Text+ topLevelFilePath TopNode _ = Right $ Text.pack (ppath ^. manifestPath <> "/site.pp")+ topLevelFilePath _ name+ | length nameparts == 1 = Right $ Text.pack (ppath ^. modulesPath) <> "/" <> name <> "/manifests/init.pp"+ | null nameparts = Left $ PrettyError ("Invalid toplevel" <+> squotes (ppline name))+ | otherwise = Right $ Text.pack (ppath ^. modulesPath) <> "/" <> List.head nameparts <> "/manifests/" <> Text.intercalate "/" (List.tail nameparts) <> ".pp"+ in case topLevelFilePath toptype topname of+ Left rr -> return (S.Left rr)+ Right fname -> do let sfname = Text.unpack fname x <- measure stats fname (FileCache.query filecache sfname (parseFile sfname)) case x of S.Right stmts -> filterStatements toptype topname stmts- S.Left rr -> return (S.Left rr)+ S.Left rr -> return (S.Left rr) parseFile :: FilePath -> IO (S.Either PrettyError (V.Vector Statement)) parseFile fname = do@@ -60,38 +58,38 @@ let (!spurious, !directnodes, !regexpmatches, !defaultnode) = V.foldl' triage (V.empty, Map.empty, V.empty, Nothing) stmts triage curstuff n@(NodeDeclaration (NodeDecl (NodeName !nm) _ _ _)) = curstuff & _2 . at nm ?~ n triage curstuff n@(NodeDeclaration (NodeDecl (NodeMatch (CompRegex _ !rg)) _ _ _)) = curstuff & _3 %~ (|> (rg :!: n))- triage curstuff n@(NodeDeclaration (NodeDecl NodeDefault _ _ _)) = curstuff & _4 ?~ n+ triage curstuff n@(NodeDeclaration (NodeDecl NodeDefault _ _ _)) = curstuff & _4 ?~ n triage curstuff x = curstuff & _1 %~ (|> x) bsnodename = Text.encodeUtf8 ndename checkRegexp :: [Pair Regex Statement] -> ExceptT PrettyError IO (Maybe Statement) checkRegexp [] = return Nothing- checkRegexp ((regexp :!: s):xs) =+ checkRegexp ((regexp :!: s) : xs) = case Regex.execute' regexp bsnodename of- Left rr -> throwError (PrettyError ("Regexp match error:" <+> ppline (show rr)))- Right Nothing -> checkRegexp xs+ Left rr -> throwError (PrettyError ("Regexp match error:" <+> ppline (show rr)))+ Right Nothing -> checkRegexp xs Right (Just _) -> return (Just s)- strictEither (Left x) = S.Left x+ strictEither (Left x) = S.Left x strictEither (Right x) = S.Right x- in case directnodes ^. at ndename of -- check if there is a node specifically called after my name- Just r -> return (S.Right (TopContainer spurious r))- Nothing -> fmap strictEither $ runExceptT $ do- regexpMatchM <- checkRegexp (V.toList regexpmatches) -- match regexps- case regexpMatchM <|> defaultnode of -- check for regexp matches or use the default node- Just r -> return (TopContainer spurious r)- Nothing -> throwError (PrettyError ("Couldn't find node" <+> ppline ndename))+ in case directnodes ^. at ndename of -- check if there is a node specifically called after my name+ Just r -> return (S.Right (TopContainer spurious r))+ Nothing -> fmap strictEither $ runExceptT $ do+ regexpMatchM <- checkRegexp (V.toList regexpmatches) -- match regexps+ case regexpMatchM <|> defaultnode of -- check for regexp matches or use the default node+ Just r -> return (TopContainer spurious r)+ Nothing -> throwError (PrettyError ("Couldn't find node" <+> ppline ndename)) filterStatements x ndename stmts = let (!spurious, !defines, !classes) = V.foldl' triage (V.empty, Map.empty, Map.empty) stmts triage curstuff n@(ClassDeclaration (ClassDecl cname _ _ _ _)) = curstuff & _3 . at cname ?~ n triage curstuff n@(DefineDeclaration (DefineDecl cname _ _ _)) = curstuff & _2 . at cname ?~ n triage curstuff n = curstuff & _1 %~ (|> n)- tc n = if V.null spurious- then n- else TopContainer spurious n- in case x of- TopNode -> return (S.Left "Case already covered, shoudln't happen in Puppet.Manifests")+ tc n =+ if V.null spurious+ then n+ else TopContainer spurious n+ in case x of TopDefine -> case defines ^. at ndename of- Just n -> return (S.Right (tc n))+ Just n -> return (S.Right (tc n)) Nothing -> return (S.Left (PrettyError ("Couldn't find define " <+> ppline ndename))) TopClass -> case classes ^. at ndename of- Just n -> return (S.Right (tc n))+ Just n -> return (S.Right (tc n)) Nothing -> return (S.Left (PrettyError ("Couldn't find class " <+> ppline ndename)))
src/Puppet/Runner/Daemon/OptionalTests.hs view
@@ -1,24 +1,22 @@ -- | The module accumulates 'PrettyError's in the ExceptT monad transformer. module Puppet.Runner.Daemon.OptionalTests (testCatalog) where -import XPrelude--import qualified Data.HashSet as Set-import qualified Data.Text as Text-import qualified System.Directory as Directory--import Puppet.Language-import Puppet.Runner.Preferences-+import qualified Data.HashSet as Set+import qualified Data.Text as Text+import Puppet.Language+import Puppet.Runner.Preferences+import qualified System.Directory as Directory+import XPrelude -- | Entry point for all optional tests-testCatalog :: Preferences IO- -> FinalCatalog- -> IO (Either PrettyError ())+testCatalog ::+ Preferences IO ->+ FinalCatalog ->+ IO (Either PrettyError ()) testCatalog prefs c =- runExceptT- $ testFileSources (prefs ^. prefPuppetPaths.baseDir) c- *> testUsersGroups (prefs ^. prefKnownusers) (prefs ^. prefKnowngroups) c+ runExceptT $+ testFileSources (prefs ^. prefPuppetPaths . baseDir) c+ *> testUsersGroups (prefs ^. prefKnownusers) (prefs ^. prefKnowngroups) c -- | Tests that all users and groups are defined testUsersGroups :: [Text] -> [Text] -> FinalCatalog -> ExceptT PrettyError IO ()@@ -27,43 +25,51 @@ groups = Set.fromList $ "" : "0" : map (view (rid . iname)) (getResourceFrom "group") ++ kgroups checkResource lu lg = mapM_ (checkResource' lu lg) checkResource' lu lg res = do- let msg att name = align (vsep [ "Resource" <+> ppline (res^.rid.itype)- <+> ppline (res^.rid.iname) <+> showPos (res^.rpos._1)- , "references the unknown" <+> att <+> squotes (ppline name)])- <> line- case lu of- Just lu' -> do- let u = res ^. rattributes . lu' . _PString- unless (Set.member u users) $ throwE $ PrettyError (msg "user" u)- Nothing -> pure ()- case lg of- Just lg' -> do- let g = res ^. rattributes . lg' . _PString- unless (Set.member g groups) $ throwE $ PrettyError (msg "group" g)- Nothing -> pure ()+ let msg att name =+ align+ ( vsep+ [ "Resource"+ <+> ppline (res ^. rid . itype)+ <+> ppline (res ^. rid . iname)+ <+> showPos (res ^. rpos . _1),+ "references the unknown" <+> att <+> squotes (ppline name)+ ]+ )+ <> line+ case lu of+ Just lu' -> do+ let u = res ^. rattributes . lu' . _PString+ unless (Set.member u users) $ throwE $ PrettyError (msg "user" u)+ Nothing -> pure ()+ case lg of+ Just lg' -> do+ let g = res ^. rattributes . lg' . _PString+ unless (Set.member g groups) $ throwE $ PrettyError (msg "group" g)+ Nothing -> pure () do- checkResource (Just $ ix "owner") (Just $ ix "group") (getResourceFrom "file")- checkResource (Just $ ix "user") (Just $ ix "group") (getResourceFrom "exec")- checkResource (Just $ ix "user") Nothing (getResourceFrom "cron")- checkResource (Just $ ix "user") Nothing (getResourceFrom "ssh_authorized_key")- checkResource (Just $ ix "user") Nothing (getResourceFrom "ssh_authorized_key_secure")- checkResource Nothing (Just $ ix "gid") (getResourceFrom "users")+ checkResource (Just $ ix "owner") (Just $ ix "group") (getResourceFrom "file")+ checkResource (Just $ ix "user") (Just $ ix "group") (getResourceFrom "exec")+ checkResource (Just $ ix "user") Nothing (getResourceFrom "cron")+ checkResource (Just $ ix "user") Nothing (getResourceFrom "ssh_authorized_key")+ checkResource (Just $ ix "user") Nothing (getResourceFrom "ssh_authorized_key_secure")+ checkResource Nothing (Just $ ix "gid") (getResourceFrom "users") where getResourceFrom t = c ^.. traverse . filtered (\r -> r ^. rid . itype == t && r ^. rattributes . at "ensure" /= Just "absent") -- | Test source for every file resources in the catalog. testFileSources :: FilePath -> FinalCatalog -> ExceptT PrettyError IO () testFileSources basedir c = do- let getfiles = filter presentFile . toList- presentFile r = r ^. rid . itype == "file"- && (r ^. rattributes . at "ensure") `elem` [Nothing, Just "present"]- && r ^. rattributes . at "source" /= Just PUndef- recurse r = case r ^? rattributes . ix "recurse" of- Just (PString "true") -> True- Just (PBoolean b) -> b- _ -> False- getsource = mapMaybe (\r -> (,,) <$> pure r <*> r ^. rattributes . at "source" <*> pure (recurse r))- checkAllSources basedir $ (getsource . getfiles) c+ let getfiles = filter presentFile . toList+ presentFile r =+ r ^. rid . itype == "file"+ && (r ^. rattributes . at "ensure") `elem` [Nothing, Just "present"]+ && r ^. rattributes . at "source" /= Just PUndef+ recurse r = case r ^? rattributes . ix "recurse" of+ Just (PString "true") -> True+ Just (PBoolean b) -> b+ _ -> False+ getsource = mapMaybe (\r -> (,,) r <$> r ^. rattributes . at "source" <*> pure (recurse r))+ checkAllSources basedir $ (getsource . getfiles) c -- | Check source for all file resources and append failures along. checkAllSources :: FilePath -> [(Resource, PValue, Bool)] -> ExceptT PrettyError IO ()@@ -74,25 +80,35 @@ go fs [] where go :: [(Resource, PValue, Bool)] -> [PrettyError] -> ExceptT PrettyError IO ()- go ((res, filesrc, recurse):xs) es = ExceptT $ do+ go ((res, filesrc, recurse) : xs) es = ExceptT $ do runExceptT (checkFile fp filesrc recurse) >>= \case Right () -> runExceptT $ go xs es Left err ->- runExceptT- $ go xs ((PrettyError $ align (vsep [ "Could not find" <+> pretty filesrc- , getError err- , showPos (res^.rpos^._1)- ])):es)+ runExceptT $+ go+ xs+ ( PrettyError+ ( align+ ( vsep+ [ "Could not find" <+> pretty filesrc,+ getError err,+ showPos (res ^. (rpos . _1))+ ]+ )+ )+ : es+ ) go [] [] = pure () go [] es = throwE (mconcat es) testFile :: Bool -> FilePath -> ExceptT PrettyError IO () testFile recurse fp = do- p <- liftIO (Directory.doesFileExist fp)- p' <- if recurse && not p- then liftIO (Directory.doesDirectoryExist fp)- else return p- unless p' (throwE $ PrettyError $ "searched in" <+> squotes (pptext fp))+ p <- liftIO (Directory.doesFileExist fp)+ p' <-+ if recurse && not p+ then liftIO (Directory.doesDirectoryExist fp)+ else return p+ unless p' (throwE $ PrettyError $ "searched in" <+> squotes (pptext fp)) -- | Only test the `puppet:///` protocol (files managed by the puppet server) -- we don't test absolute path (puppet client files)@@ -100,11 +116,11 @@ checkFile basedir (PString f) recurse = case Text.stripPrefix "puppet:///" f of Just stringdir -> case Text.splitOn "/" stringdir of- ("modules":modname:rest) -> testFile recurse (basedir <> "/modules/" <> toS modname <> "/files/" <> toS (Text.intercalate "/" rest))- ("files":rest) -> testFile recurse (basedir <> "/files/" <> toS (Text.intercalate "/" rest))- ("private":_) -> pure ()- _ -> throwE (PrettyError $ "Invalid file source:" <+> ppline f)- Nothing -> return ()+ ("modules" : modname : rest) -> testFile recurse (basedir <> "/modules/" <> toS modname <> "/files/" <> toS (Text.intercalate "/" rest))+ ("files" : rest) -> testFile recurse (basedir <> "/files/" <> toS (Text.intercalate "/" rest))+ ("private" : _) -> pure ()+ _ -> throwE (PrettyError $ "Invalid file source:" <+> ppline f)+ Nothing -> return () -- source is always an array of possible paths. We only fails if none of them check.-checkFile basedir (PArray xs) recurse = asum [checkFile basedir x recurse | x <- toList xs]+checkFile basedir (PArray xs) recurse = asum [checkFile basedir x recurse | x <- toList xs] checkFile _ x _ = throwE (PrettyError $ "Source was not a string, but" <+> pretty x)
src/Puppet/Runner/Erb.hs view
@@ -1,48 +1,45 @@-{-# LANGUAGE NamedFieldPuns #-} -- | Internal module used to initialize the erb template daemon.-module Puppet.Runner.Erb (- initTemplateDaemon- , rubyEvaluate-) where--import XPrelude--import qualified Data.Either.Strict as S-import qualified Data.FileCache as Cache-import qualified Data.List as List-import qualified Data.List.Split as List-import qualified Data.Text as Text-import Data.Tuple.Strict (Pair (..))-import qualified Data.Vector as Vector-import Debug.Trace-import Foreign.Ruby-import qualified Foreign.Ruby.Bindings as FR-import qualified Foreign.Ruby.Helpers as FR-import GHC.Conc (labelThread)-import Paths_language_puppet (getDataFileName)-import System.Environment (getExecutablePath)-import System.FilePath-import System.Posix.Files-import Text.Parsec hiding (string)-import Text.Parsec.Error-import Text.Parsec.Pos--import Erb-import Puppet.Interpreter.Helpers-import Puppet.Interpreter.IO-import Puppet.Interpreter.Resolve-import Puppet.Interpreter.Types-import Puppet.Runner.Erb.Evaluate-import Puppet.Runner.Preferences-import Puppet.Runner.Stats+module Puppet.Runner.Erb+ ( initTemplateDaemon,+ rubyEvaluate,+ )+where +import qualified Data.Either.Strict as S+import qualified Data.FileCache as Cache+import qualified Data.List as List+import qualified Data.List.Split as List+import qualified Data.Text as Text+import qualified Data.Vector as Vector+import Debug.Trace+import Erb+import Foreign.Ruby+import qualified Foreign.Ruby.Bindings as FR+import qualified Foreign.Ruby.Helpers as FR+import GHC.Conc (labelThread)+import Paths_language_puppet (getDataFileName)+import Puppet.Interpreter.Helpers+import Puppet.Interpreter.IO+import Puppet.Interpreter.Resolve+import Puppet.Interpreter.Types+import Puppet.Runner.Erb.Evaluate+import Puppet.Runner.Preferences+import Puppet.Runner.Stats+import System.Environment (getExecutablePath)+import System.FilePath+import System.Posix.Files+import Text.Parsec hiding (string)+import Text.Parsec.Error+import Text.Parsec.Pos+import XPrelude instance IsString TemplateParseError where fromString s = TemplateParseError $ newErrorMessage (Message s) (initialPos mempty) -newtype TemplateParseError = TemplateParseError { tgetError :: ParseError }+newtype TemplateParseError = TemplateParseError {tgetError :: ParseError} type TemplateQuery = (Chan TemplateAnswer, TemplateSource, InterpreterState, InterpreterReader IO)+ type TemplateAnswer = S.Either PrettyError Text showRubyError :: RubyError -> PrettyError@@ -51,10 +48,11 @@ showRubyError (OtherError rr) = PrettyError (dullred (pptext rr)) -- | Parse and resolve erb files. Initializes a thread for the Ruby interpreter .-initTemplateDaemon :: RubyInterpreter- -> Preferences IO- -> MStats- -> IO (TemplateSource -> InterpreterState -> InterpreterReader IO -> IO (S.Either PrettyError Text))+initTemplateDaemon ::+ RubyInterpreter ->+ Preferences IO ->+ MStats ->+ IO (TemplateSource -> InterpreterState -> InterpreterReader IO -> IO (S.Either PrettyError Text)) initTemplateDaemon rubyintp prefs mvstats = do controlchan <- newChan templatecache <- Cache.newFileCache@@ -63,12 +61,16 @@ liftIO (getRubyScriptPath "hrubyerb.rb") >>= ExceptT . loadFile rubyintp ExceptT (registerGlobalFunction4 rubyintp "varlookup" hrresolveVariable) ExceptT (registerGlobalFunction5 rubyintp "callextfunc" hrcallfunction)- liftIO $ void $ forkIO $ templateDaemon rubyintp- (prefs ^. prefPuppetPaths.modulesPath)- (prefs ^. prefPuppetPaths.templatesPath)- controlchan- mvstats- templatecache+ liftIO $+ void $+ forkIO $+ templateDaemon+ rubyintp+ (prefs ^. prefPuppetPaths . modulesPath)+ (prefs ^. prefPuppetPaths . templatesPath)+ controlchan+ mvstats+ templatecache pure $! templateQuery controlchan either returnError pure x @@ -87,10 +89,12 @@ case fileinfo of Filename filename -> do let prts = List.splitOn "/" filename- searchpathes | length prts > 1 = [ modpath </> List.head prts </> "templates" </> List.intercalate "/" (List.tail prts)- , templatepath </> filename- ]- | otherwise = [templatepath </> filename]+ searchpathes+ | length prts > 1 =+ [ modpath </> List.head prts </> "templates" </> List.intercalate "/" (List.tail prts),+ templatepath </> filename+ ]+ | otherwise = [templatepath </> filename] acceptablefiles <- filterM fileExist searchpathes if null acceptablefiles then writeChan respchan (S.Left $ PrettyError $ "Can't find template file for" <+> pptext filename <+> ", looked in" <+> list (map pptext searchpathes))@@ -102,16 +106,17 @@ computeTemplate rubyintp srcinfo intpstate intpreader mstats filecache = do let (curcontext, fvariables) = case extractScope intpstate of- Nothing -> (mempty, mempty)- Just (c,v) -> (c,v)+ Nothing -> (mempty, mempty)+ Just (c, v) -> (c, v) template_src = templateSrc srcinfo- mkSafe a = makeSafe rubyintp a >>= \case- Left err -> return (S.Left (showRubyError err))- Right x -> return x+ mkSafe a =+ makeSafe rubyintp a >>= \case+ Left err -> return (S.Left (showRubyError err))+ Right x -> return x encapsulateError = _Left %~ TemplateParseError variables = fvariables & traverse . scopeVariables . traverse . _1 . _1 %~ toStr toStr (PNumber n) = PString (scientific2text n)- toStr x = x+ toStr x = x traceEventIO ("START template " <> template_src) parsed <- case srcinfo of Filename _ -> measure mstats ("parsing - " <> toS template_src) $ Cache.lazyQuery filecache template_src $ fmap encapsulateError (parseErbFile template_src)@@ -125,8 +130,8 @@ Right ast -> case rubyEvaluate variables curcontext ast of Right ev -> pure (S.Right ev) Left err -> do- let !msg = "At " <> showPPos'(intpstate^.curPos) <> " the evaluation of template '" <> template_src <> "' failed. " <> show err- -- if the haskell evaluation fails the ruby one will fallback. It is likely that the reason for the failure is a real template issue.+ let !msg = "At " <> showPPos' (intpstate ^. curPos) <> " the evaluation of template '" <> template_src <> "' failed. " <> show err+ -- if the haskell evaluation fails the ruby one will fallback. It is likely that the reason for the failure is a real template issue. logErrorStr msg measure mstats ("ruby efail - " <> toS template_src) $ mkSafe $ computeTemplateWRuby srcinfo curcontext variables intpstate intpreader traceEventIO ("STOP template " <> template_src)@@ -135,10 +140,7 @@ getRubyScriptPath :: String -> IO String getRubyScriptPath rubybin = do let checkpath :: FilePath -> IO FilePath -> IO FilePath- checkpath fp nxt =- ifM (fileExist fp)- (pure fp)- nxt+ checkpath fp = ifM (fileExist fp) (pure fp) withExecutablePath = do path <- fmap takeDirectory getExecutablePath let fullpath = path </> rubybin@@ -155,13 +157,13 @@ toresolve <- FR.fromRuby rtoresolve let answer = case toresolve of Right "~g~e~t_h~a~s~h~" ->- let getvars ctx = (variables ^. ix ctx . scopeVariables) & traverse %~ view (_1 . _1)- vars = getvars "::" <> getvars scope- in Right (PHash vars)+ let getvars ctx = (variables ^. ix ctx . scopeVariables) & traverse %~ view (_1 . _1)+ vars = getvars "::" <> getvars scope+ in Right (PHash vars) Right t -> getVariable variables scope t Left err -> Left ("The variable name is not a string" <+> pptext err) case answer of- Left _ -> getSymbol "undef"+ Left _ -> getSymbol "undef" Right r -> FR.toRuby r hrcallfunction :: RValue -> RValue -> RValue -> RValue -> RValue -> IO RValue@@ -171,21 +173,22 @@ rdr <- FR.extractHaskellValue rrdr stt <- FR.extractHaskellValue rstt let rubyerr :: String -> IO RValue- rubyerr err = fmap (either snd identity) (FR.toRuby (Text.pack err) >>= FR.safeMethodCall "MyError" "new" . (:[]))+ rubyerr err = fmap (either snd identity) (FR.toRuby (Text.pack err) >>= FR.safeMethodCall "MyError" "new" . (: [])) case (,) <$> efname <*> eargs of- Right (fname, varray) | fname `elem` ["template", "inline_template"] -> do- logWarning $ "Can't parse a call to the external ruby function '" <> fname <> "' n an erb file.\n\tIt is not possible to call it from a Ruby function. It would stall (yes it sucks ...).\n\tChoosing to output \"undef\" !"- getSymbol "undef"- | otherwise -> do- let args = case varray of- [PArray vargs] -> Vector.toList vargs- _ -> varray- (x,_,_) <- interpretMonad rdr stt (resolveFunction' fname args)- case x of- Right o -> case o ^? _PValueNumber of- Just n -> FR.toRuby n- Nothing -> FR.toRuby o- Left err -> rubyerr (show err)+ Right (fname, varray)+ | fname `elem` ["template", "inline_template"] -> do+ logWarning $ "Can't parse a call to the external ruby function '" <> fname <> "' n an erb file.\n\tIt is not possible to call it from a Ruby function. It would stall (yes it sucks ...).\n\tChoosing to output \"undef\" !"+ getSymbol "undef"+ | otherwise -> do+ let args = case varray of+ [PArray vargs] -> Vector.toList vargs+ _ -> varray+ (x, _, _) <- interpretMonad rdr stt (resolveFunction' fname args)+ case x of+ Right o -> case o ^? _PValueNumber of+ Just n -> FR.toRuby n+ Nothing -> FR.toRuby o+ Left err -> rubyerr (show err) Left err -> rubyerr err computeTemplateWRuby :: TemplateSource -> Text -> Container ScopeInformation -> InterpreterState -> InterpreterReader IO -> IO TemplateAnswer@@ -198,18 +201,18 @@ -- must be called from a "makeSafe" thingie contentinfo <- case fileinfo of Filename fname -> FR.toRuby (Text.pack fname)- Inline _ -> FR.toRuby ("-" :: Text)+ Inline _ -> FR.toRuby ("-" :: Text) let withBinding f =- FR.safeMethodCall "ErbBinding" "new" [rscp,rvariables,rstt,rrdr,contentinfo] >>= \case+ FR.safeMethodCall "ErbBinding" "new" [rscp, rvariables, rstt, rrdr, contentinfo] >>= \case Left x -> pure (Left x) Right v -> do- forM_ (itoList varlist) $ \(varname, varval :!: _ :!: _) -> FR.toRuby varval >>= FR.rb_iv_set v (toS varname)- f v+ forM_ (itoList varlist) $ \(varname, (varval :!: _) :!: _) -> FR.toRuby varval >>= FR.rb_iv_set v (toS varname)+ f v o <- case fileinfo of- Filename fname -> do+ Filename fname -> do rfname <- FR.toRuby (Text.pack fname)- withBinding $ \v -> FR.safeMethodCall "Controller" "runFromFile" [rfname,v]- Inline content -> withBinding $ \v -> FR.toRuby content >>= FR.safeMethodCall "Controller" "runFromContent" . (:[v])+ withBinding $ \v -> FR.safeMethodCall "Controller" "runFromFile" [rfname, v]+ Inline content -> withBinding $ \v -> FR.toRuby content >>= FR.safeMethodCall "Controller" "runFromContent" . (: [v]) FR.freeHaskellValue rrdr FR.freeHaskellValue rstt FR.freeHaskellValue rvariables@@ -217,15 +220,16 @@ case o of Left (err, _) -> pure (S.Left $ PrettyError (dullred (pptext err) <+> "in" <+> dullgreen (pptext (templateSrc fileinfo))))- Right r -> FR.fromRuby r >>= \case- Right result -> pure (S.Right result)- Left err -> pure (S.Left $ PrettyError ("Could not deserialiaze ruby output" <+> pptext err))+ Right r ->+ FR.fromRuby r >>= \case+ Right result -> pure (S.Right result)+ Left err -> pure (S.Left $ PrettyError ("Could not deserialiaze ruby output" <+> pptext err)) eitherDocIO :: IO (S.Either PrettyError a) -> IO (S.Either PrettyError a) eitherDocIO computation = (computation >>= check) `catch` (\e -> pure $ S.Left $ PrettyError $ dullred $ ppline $ show (e :: SomeException)) where- check (S.Left r) = pure (S.Left r)+ check (S.Left r) = pure (S.Left r) check (S.Right x) = pure (S.Right x) templateSrc :: TemplateSource -> String
src/Puppet/Runner/Erb/Evaluate.hs view
@@ -1,47 +1,47 @@ -- | Private module. Evaluates a ruby template from what's generated by "Erb.Parser".-module Puppet.Runner.Erb.Evaluate (- rubyEvaluate- ) where--import XPrelude+module Puppet.Runner.Erb.Evaluate+ ( rubyEvaluate,+ )+where -import qualified Data.Char as Char+import qualified Data.Char as Char import qualified Data.HashMap.Strict as HM-import qualified Data.Text as Text-import qualified Data.Vector as V--import Erb.Ruby-import Puppet.Interpreter+import qualified Data.Text as Text+import qualified Data.Vector as V+import Erb.Ruby+import Puppet.Interpreter+import XPrelude type ScopeName = Text -- | Evaluate a list of ruby statements.-rubyEvaluate :: Container ScopeInformation- -> ScopeName- -> [RubyStatement]- -> Either Doc Text+rubyEvaluate ::+ Container ScopeInformation ->+ ScopeName ->+ [RubyStatement] ->+ Either Doc Text rubyEvaluate vars ctx = foldl (evalruby vars ctx) (Right "") . optimize where- optimize [] = []+ optimize [] = [] optimize (Puts x : DropPrevSpace' : xs) = optimize $ DropPrevSpace (Puts x) : xs- optimize (x:xs) = x : optimize xs+ optimize (x : xs) = x : optimize xs spaceNotCR :: Char -> Bool spaceNotCR c = Char.isSpace c && c /= '\n' && c /= '\r' evalruby :: Container ScopeInformation -> ScopeName -> Either Doc Text -> RubyStatement -> Either Doc Text-evalruby _ _ (Left err) _ = Left err-evalruby _ _ (Right _) (DropPrevSpace') = Left "Could not evaluate a non optimize DropPrevSpace'"+evalruby _ _ (Left err) _ = Left err+evalruby _ _ (Right _) DropPrevSpace' = Left "Could not evaluate a non optimize DropPrevSpace'" evalruby mp ctx (Right curstr) (DropNextSpace x) = case evalruby mp ctx (Right curstr) x of Left err -> Left err- Right y -> Right (Text.dropWhile spaceNotCR y)+ Right y -> Right (Text.dropWhile spaceNotCR y) evalruby mp ctx (Right curstr) (DropPrevSpace x) = case evalruby mp ctx (Right curstr) x of Left err -> Left err- Right y -> Right (Text.dropWhileEnd spaceNotCR y)+ Right y -> Right (Text.dropWhileEnd spaceNotCR y) evalruby mp ctx (Right curstr) (Puts e) =- case (evalExpression mp ctx e >>= evalValue) of+ case evalExpression mp ctx e >>= evalValue of Left err -> Left err Right ex -> Right (curstr <> ex) @@ -53,10 +53,10 @@ PArray arr -> case idx ^? _PValueInteger of Nothing -> Left $ "Can't convert index to integer when resolving" <+> pretty val <> brackets (pretty idx)- Just i ->+ Just i -> if fromIntegral (V.length arr) <= i- then Left $ "Array out of bound" <+> pretty val <> brackets (pretty idx)- else Right (arr V.! fromIntegral i)+ then Left $ "Array out of bound" <+> pretty val <> brackets (pretty idx)+ else Right (arr V.! fromIntegral i) PHash hs -> case idx of PString idx' ->@@ -65,25 +65,23 @@ _ -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty (PHash hs) _ -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty (PHash hs) unexpectedval -> Left $ "Can't index variable" <+> pretty val <+> ", it is " <+> pretty unexpectedval--evalExpression _ _ (Value (Literal x)) = Right (PString x)+evalExpression _ _ (Value (Literal x)) = Right (PString x) evalExpression mp ctx (ScopeObject (Value (Literal x))) = getVariable mp ctx x evalExpression mp ctx (Object (Value (Literal x))) = do case Text.stripPrefix "@" x of Nothing -> Left $ "Erb variables '" <> ppline x <> "' should be prefixed by '@' in puppet version 4 and above." Just x' -> getVariable mp ctx x'--evalExpression _ _ x = Left $ "Can't evaluate" <+> pretty x+evalExpression _ _ x = Left $ "Can't evaluate" <+> pretty x evalValue :: PValue -> Either Doc Text evalValue = go False where go escaped p = case p of- PString x -> Right $ if escaped then show x else x- PNumber x -> Right (scientific2text x)- PUndef -> Right "nil"- PBoolean True -> Right "true"+ PString x -> Right $ if escaped then show x else x+ PNumber x -> Right (scientific2text x)+ PUndef -> Right "nil"+ PBoolean True -> Right "true" PBoolean False -> Right "false"- PArray lst -> fmap (\c -> "[" <> Text.intercalate ", " c <> "]") (mapM (go True) (V.toList lst))- PHash hash -> fmap (\l -> "{" <> Text.intercalate ", " (map (\(k,v) -> show k <> "=>" <> v) l) <> "}") (mapM (traverse (go True)) (HM.toList hash))- _ -> Left ("Can't display the ruby equivalent of" <+> pretty p)+ PArray lst -> fmap (\c -> "[" <> Text.intercalate ", " c <> "]") (mapM (go True) (V.toList lst))+ PHash hash -> fmap (\l -> "{" <> Text.intercalate ", " (map (\(k, v) -> show k <> "=>" <> v) l) <> "}") (mapM (traverse (go True)) (HM.toList hash))+ _ -> Left ("Can't display the ruby equivalent of" <+> pretty p)
src/Puppet/Runner/Preferences.hs view
@@ -1,150 +1,156 @@ {-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TemplateHaskell #-}-module Puppet.Runner.Preferences (- Preferences(Preferences)- , prefPuppetPaths- , prefPDB- , prefNatTypes- , prefExtFuncs- , prefHieraPath- , prefIgnoredmodules- , prefStrictness- , prefExtraTests- , prefKnownusers- , prefKnowngroups- , prefExternalmodules- , prefPuppetSettings- , prefFactsOverride- , prefFactsDefault- , prefLogLevel- , prefRebaseFile- , dfPreferences- , PuppetDirPaths- , HasPuppetDirPaths(..)-) where--import XPrelude+{-# LANGUAGE TemplateHaskell #-} -import Data.Aeson-import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as HS-import qualified Data.List as List-import qualified Data.Text as Text-import qualified Data.Yaml as Yaml-import qualified System.Directory as Directory-import qualified System.FilePath as FilePath-import qualified System.Log.Logger as LOG+module Puppet.Runner.Preferences+ ( Preferences (Preferences),+ prefPuppetPaths,+ prefPDB,+ prefNatTypes,+ prefExtFuncs,+ prefHieraPath,+ prefIgnoredmodules,+ prefStrictness,+ prefExtraTests,+ prefKnownusers,+ prefKnowngroups,+ prefExternalmodules,+ prefPuppetSettings,+ prefFactsOverride,+ prefFactsDefault,+ prefLogLevel,+ prefRebaseFile,+ dfPreferences,+ PuppetDirPaths,+ HasPuppetDirPaths (..),+ )+where -import Puppet.Interpreter+import Data.Aeson+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Data.Yaml as Yaml+import Puppet.Interpreter import qualified Puppet.Runner.Puppetlabs as Puppetlabs-import Puppet.Runner.Stdlib-import PuppetDB+import Puppet.Runner.Stdlib+import PuppetDB+import qualified System.Directory as Directory+import qualified System.FilePath as FilePath+import qualified System.Log.Logger as LOG+import XPrelude data Preferences m = Preferences- { _prefPuppetPaths :: PuppetDirPaths- , _prefPDB :: PuppetDBAPI m- , _prefNatTypes :: Container NativeTypeMethods -- ^ The list of native types.- , _prefExtFuncs :: Container ([PValue] -> InterpreterMonad PValue)- , _prefHieraPath :: Maybe FilePath- , _prefIgnoredmodules :: HS.HashSet Text- , _prefStrictness :: Strictness- , _prefExtraTests :: Bool- , _prefKnownusers :: [Text]- , _prefKnowngroups :: [Text]- , _prefExternalmodules :: HS.HashSet Text- , _prefPuppetSettings :: Container Text -- ^ Puppet server settings- , _prefFactsOverride :: Container PValue- , _prefFactsDefault :: Container PValue- , _prefLogLevel :: LOG.Priority- , _prefRebaseFile :: Maybe FilePath -- ^ Make all calls to file() with absolute pathes relative to the given path.+ { _prefPuppetPaths :: PuppetDirPaths,+ _prefPDB :: PuppetDBAPI m,+ -- | The list of native types.+ _prefNatTypes :: Container NativeTypeMethods,+ _prefExtFuncs :: Container ([PValue] -> InterpreterMonad PValue),+ _prefHieraPath :: Maybe FilePath,+ _prefIgnoredmodules :: HS.HashSet Text,+ _prefStrictness :: Strictness,+ _prefExtraTests :: Bool,+ _prefKnownusers :: [Text],+ _prefKnowngroups :: [Text],+ _prefExternalmodules :: HS.HashSet Text,+ -- | Puppet server settings+ _prefPuppetSettings :: Container Text,+ _prefFactsOverride :: Container PValue,+ _prefFactsDefault :: Container PValue,+ _prefLogLevel :: LOG.Priority,+ -- | Make all calls to file() with absolute pathes relative to the given path.+ _prefRebaseFile :: Maybe FilePath } data Defaults = Defaults- { _dfKnownusers :: Maybe [Text]- , _dfKnowngroups :: Maybe [Text]- , _dfIgnoredmodules :: Maybe [Text]- , _dfStrictness :: Maybe Strictness- , _dfExtratests :: Maybe Bool- , _dfExternalmodules :: Maybe [Text]- , _dfPuppetSettings :: Maybe (Container Text)- , _dfFactsDefault :: Maybe (Container PValue)- , _dfFactsOverride :: Maybe (Container PValue)- , _dfRebaseFile :: Maybe FilePath- } deriving (Show)-+ { _dfKnownusers :: Maybe [Text],+ _dfKnowngroups :: Maybe [Text],+ _dfIgnoredmodules :: Maybe [Text],+ _dfStrictness :: Maybe Strictness,+ _dfExtratests :: Maybe Bool,+ _dfExternalmodules :: Maybe [Text],+ _dfPuppetSettings :: Maybe (Container Text),+ _dfFactsDefault :: Maybe (Container PValue),+ _dfFactsOverride :: Maybe (Container PValue),+ _dfRebaseFile :: Maybe FilePath+ }+ deriving (Show) makeLenses ''Preferences instance FromJSON Defaults where- parseJSON (Object v) = Defaults- <$> v .:? "knownusers"- <*> v .:? "knowngroups"- <*> v .:? "ignoredmodules"- <*> v .:? "strict"- <*> v .:? "extratests"- <*> v .:? "externalmodules"- <*> v .:? "settings"- <*> v .:? "factsdefault"- <*> v .:? "factsoverride"- <*> v .:? "rebasefile"+ parseJSON (Object v) =+ Defaults+ <$> v .:? "knownusers"+ <*> v .:? "knowngroups"+ <*> v .:? "ignoredmodules"+ <*> v .:? "strict"+ <*> v .:? "extratests"+ <*> v .:? "externalmodules"+ <*> v .:? "settings"+ <*> v .:? "factsdefault"+ <*> v .:? "factsoverride"+ <*> v .:? "rebasefile" parseJSON _ = mzero -- | Generate default preferences.-dfPreferences :: FilePath- -> IO (Preferences IO)+dfPreferences ::+ FilePath ->+ IO (Preferences IO) dfPreferences basedir = do- let dirpaths = puppetPaths basedir- modulesdir = dirpaths ^. modulesPath- testdir = dirpaths ^. testPath- hierafile = basedir <> "/hiera.yaml"- defaultfile = testdir <> "/defaults.yaml"- defaults <- ifM (Directory.doesFileExist defaultfile) (Yaml.decodeFileThrow defaultfile) (pure Nothing)- hieradir <- ifM (Directory.doesFileExist hierafile) (pure $ Just hierafile) (pure Nothing)- loadedtypes <- loadedTypes modulesdir- labsFunctions <- Puppetlabs.extFunctions modulesdir- return $ Preferences dirpaths- dummyPuppetDB- (baseNativeTypes `HM.union` loadedtypes)- (HM.union stdlibFunctions labsFunctions)- hieradir- (getIgnoredmodules defaults)- (getStrictness defaults)- (getExtraTests defaults)- (getKnownusers defaults)- (getKnowngroups defaults)- (getExternalmodules defaults)- (getPuppetSettings dirpaths defaults)- (getFactsOverride defaults)- (getFactsDefault defaults)- LOG.NOTICE -- good default as INFO is quite noisy- Nothing+ let dirpaths = puppetPaths basedir+ modulesdir = dirpaths ^. modulesPath+ testdir = dirpaths ^. testPath+ hierafile = basedir <> "/hiera.yaml"+ defaultfile = testdir <> "/defaults.yaml"+ defaults <- ifM (Directory.doesFileExist defaultfile) (Yaml.decodeFileThrow defaultfile) (pure Nothing)+ hieradir <- ifM (Directory.doesFileExist hierafile) (pure $ Just hierafile) (pure Nothing)+ loadedtypes <- loadedTypes modulesdir+ labsFunctions <- Puppetlabs.extFunctions modulesdir+ return $+ Preferences+ dirpaths+ dummyPuppetDB+ (baseNativeTypes `HM.union` loadedtypes)+ (HM.union stdlibFunctions labsFunctions)+ hieradir+ (getIgnoredmodules defaults)+ (getStrictness defaults)+ (getExtraTests defaults)+ (getKnownusers defaults)+ (getKnowngroups defaults)+ (getExternalmodules defaults)+ (getPuppetSettings dirpaths defaults)+ (getFactsOverride defaults)+ (getFactsDefault defaults)+ LOG.NOTICE -- good default as INFO is quite noisy+ Nothing loadedTypes :: FilePath -> IO (HM.HashMap NativeTypeName NativeTypeMethods) loadedTypes modulesdir = do typenames <- map (Text.pack . FilePath.takeBaseName) <$> getFiles modulesdir "lib/puppet/type" ".rb" pure $ HM.fromList (map defaulttype typenames) where- getFiles :: FilePath -> FilePath -> FilePath -> IO [FilePath]- getFiles moduledir subdir extension =- fmap concat- $ Directory.listDirectory moduledir- >>= mapM ( checkForSubFiles extension . (\x -> moduledir <> "/" <> x <> "/" <> subdir))- checkForSubFiles :: FilePath -> FilePath -> IO [FilePath]- checkForSubFiles extension dir =- catch (fmap Right (Directory.listDirectory dir)) (\e -> return $ Left (e :: IOException)) >>= \case- Right o -> return ((map (\x -> dir <> "/" <> x) . filter (List.isSuffixOf extension)) o )- Left _ -> return []+ getFiles :: FilePath -> FilePath -> FilePath -> IO [FilePath]+ getFiles moduledir subdir extension =+ fmap concat $+ Directory.listDirectory moduledir+ >>= mapM (checkForSubFiles extension . (\x -> moduledir <> "/" <> x <> "/" <> subdir))+ checkForSubFiles :: FilePath -> FilePath -> IO [FilePath]+ checkForSubFiles extension dir =+ catch (fmap Right (Directory.listDirectory dir)) (\e -> return $ Left (e :: IOException)) >>= \case+ Right o -> return ((map (\x -> dir <> "/" <> x) . filter (List.isSuffixOf extension)) o)+ Left _ -> return [] -- Utilities for getting default values from the yaml file -- It provides (the same) static defaults (see the 'Nothing' case) when -- no default yaml file or -- not key/value for the option has been provided getKnownusers :: Maybe Defaults -> [Text]-getKnownusers = fromMaybe ["mysql", "vagrant","nginx", "nagios", "postgres", "puppet", "root", "syslog", "www-data"] . (>>= _dfKnownusers)+getKnownusers = fromMaybe ["mysql", "vagrant", "nginx", "nagios", "postgres", "puppet", "root", "syslog", "www-data"] . (>>= _dfKnownusers) getKnowngroups :: Maybe Defaults -> [Text]-getKnowngroups = fromMaybe ["adm", "syslog", "mysql", "nagios","postgres", "puppet", "root", "www-data", "postfix"] . (>>= _dfKnowngroups)+getKnowngroups = fromMaybe ["adm", "syslog", "mysql", "nagios", "postgres", "puppet", "root", "www-data", "postfix"] . (>>= _dfKnowngroups) getStrictness :: Maybe Defaults -> Strictness getStrictness = fromMaybe Permissive . (>>= _dfStrictness)@@ -162,9 +168,11 @@ getPuppetSettings dirpaths = fromMaybe df . (>>= _dfPuppetSettings) where df :: Container Text- df = HM.fromList [ ("confdir", Text.pack $ dirpaths^.baseDir)- , ("strict_variables", "true")- ]+ df =+ HM.fromList+ [ ("confdir", Text.pack $ dirpaths ^. baseDir),+ ("strict_variables", "true")+ ] getFactsOverride :: Maybe Defaults -> Container PValue getFactsOverride = fromMaybe mempty . (>>= _dfFactsOverride)
src/Puppet/Runner/Puppetlabs.hs view
@@ -1,119 +1,114 @@ {-# LANGUAGE OverloadedLists #-}+ -- | Contains an Haskell implementation (or mock implementation) of some ruby functions found in puppetlabs modules. module Puppet.Runner.Puppetlabs (extFunctions) where -import XPrelude--import Crypto.Hash as Crypto-import Data.ByteString (ByteString)-import Data.Char (isDigit)-import Data.Foldable (foldlM)+import Crypto.Hash as Crypto import qualified Data.HashMap.Strict as Map-import Data.Scientific as Sci-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import Data.Vector (Vector)-import Formatting (scifmt, sformat, (%), (%.))-import qualified Formatting as FMT-import qualified System.Directory as Directory-import System.FilePath ((</>), (<.>))-import System.Random (mkStdGen, randomRs)--import Puppet.Interpreter+import Data.Scientific as Sci+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import Formatting (scifmt, sformat, (%), (%.))+import qualified Formatting as FMT+import Puppet.Interpreter+import qualified System.Directory as Directory+import System.FilePath ((<.>), (</>))+import System.Random (mkStdGen, randomRs)+import XPrelude md5 :: Text -> Text md5 = Text.pack . show . (Crypto.hash :: ByteString -> Digest MD5) . Text.encodeUtf8 extFun :: [(Text, Text, [PValue] -> InterpreterMonad PValue)]-extFun = [ ("apache", "bool2httpd", apacheBool2httpd)- , ("docker", "docker_swarm_join_flags", mockDockerSwarmJoinFlags)- , ("docker", "docker_swarm_init_flags", mockDockerSwarmInitFlags)- , ("docker", "docker_run_flags", mockDockerRunFlags)- , ("docker", "docker_stack_flags", mockDockerStackFlags)- , ("docker", "docker_secrets_flags", mockDockerSecretsFlags)- , ("docker", "sanitised_name", dockerSanitisedName)- , ("jenkins", "jenkins_port", mockJenkinsPort)- , ("jenkins", "jenkins_prefix", mockJenkinsPrefix)- , ("postgresql", "postgresql_acls_to_resources_hash", pgAclsToHash)- , ("postgresql", "postgresql_password", pgPassword)- , ("puppetdb", "puppetdb_create_subsetting_resource_hash", puppetdbCreateSubsettingResourceHash)- , ("extlib", "random_password", randomPassword)- , ("extlib", "cache_data", mockCacheData)- , ("kubernetes", "kubeadm_init_flags", mockKubernetesInitFlags)- , ("kubernetes", "kubeadm_join_flags", mockKubernetesJoinFlags)- ]+extFun =+ [ ("apache", "bool2httpd", apacheBool2httpd),+ ("docker", "docker_swarm_join_flags", mockDockerSwarmJoinFlags),+ ("docker", "docker_swarm_init_flags", mockDockerSwarmInitFlags),+ ("docker", "docker_run_flags", mockDockerRunFlags),+ ("docker", "docker_stack_flags", mockDockerStackFlags),+ ("docker", "docker_secrets_flags", mockDockerSecretsFlags),+ ("docker", "sanitised_name", dockerSanitisedName),+ ("jenkins", "jenkins_port", mockJenkinsPort),+ ("jenkins", "jenkins_prefix", mockJenkinsPrefix),+ ("postgresql", "postgresql_acls_to_resources_hash", pgAclsToHash),+ ("postgresql", "postgresql_password", pgPassword),+ ("puppetdb", "puppetdb_create_subsetting_resource_hash", puppetdbCreateSubsettingResourceHash),+ ("extlib", "random_password", randomPassword),+ ("extlib", "cache_data", mockCacheData),+ ("kubernetes", "kubeadm_init_flags", mockKubernetesInitFlags),+ ("kubernetes", "kubeadm_join_flags", mockKubernetesJoinFlags)+ ] -- | Build the map of available external functions. -- -- If the ruby/puppet file is not found on the local filesystem the record is ignored. This is to avoid potential namespace conflict.-extFunctions :: FilePath -> IO (Container ( [PValue] -> InterpreterMonad PValue))+extFunctions :: FilePath -> IO (Container ([PValue] -> InterpreterMonad PValue)) extFunctions modpath = foldlM f Map.empty extFun where f acc (nsp, name, fn) = do test <- testFile (toS nsp) name if test- then pure $ Map.insert name fn acc- else pure acc+ then pure $ Map.insert name fn acc+ else pure acc testFile nspath funcname = let funcpath0 = modpath </> nspath funcpath1 = funcpath0 </> "lib/puppet" funcpath2 = funcpath1 </> "parser/functions" funcpath3 = funcpath1 </> "functions"- in- isJust <$> Directory.findFile [ funcpath0 </> "functions"] (toS funcname <.> "pp")- ||^- isJust <$> Directory.findFile [ funcpath2- , funcpath3- , funcpath2 </> nspath- , funcpath3 </> nspath- ] (toS funcname <.> "rb")+ in (\a b -> isJust a || isJust b)+ <$> Directory.findFile [funcpath0 </> "functions"] (toS funcname <.> "pp")+ <*> Directory.findFile+ [ funcpath2,+ funcpath3,+ funcpath2 </> nspath,+ funcpath3 </> nspath+ ]+ (toS funcname <.> "rb") -apacheBool2httpd :: MonadThrowPos m => [PValue] -> m PValue-apacheBool2httpd [PBoolean True] = pure $ PString "On"+apacheBool2httpd :: (MonadThrowPos m) => [PValue] -> m PValue+apacheBool2httpd [PBoolean True] = pure $ PString "On" apacheBool2httpd [PString "true"] = pure $ PString "On"-apacheBool2httpd [_] = pure $ PString "Off"-apacheBool2httpd arg@_ = throwPosError $ "expect one single argument" <+> pretty arg+apacheBool2httpd [_] = pure $ PString "Off"+apacheBool2httpd arg = throwPosError $ "expect one single argument" <+> pretty arg -pgPassword :: MonadThrowPos m => [PValue] -> m PValue+pgPassword :: (MonadThrowPos m) => [PValue] -> m PValue pgPassword [PString username, PString pwd] =- return $ PString $ "md5" <> md5 (pwd <> username)+ return $ PString $ "md5" <> md5 (pwd <> username) pgPassword _ = throwPosError "expects 2 string arguments" -- | The function is pure and always return the same "random" password.-randomPassword :: MonadThrowPos m => [PValue] -> m PValue+randomPassword :: (MonadThrowPos m) => [PValue] -> m PValue randomPassword [PNumber s] = PString . Text.pack . randomChars <$> scientificToInt s where randomChars n = take n $ randomRs ('a', 'z') (mkStdGen 1)- randomPassword _ = throwPosError "expect one single string arguments" - -- To be implemented if needed.-mockJenkinsPrefix :: MonadThrowPos m => [PValue] -> m PValue-mockJenkinsPrefix [] = return $ PString ""-mockJenkinsPrefix arg@_ = throwPosError $ "expect no argument" <+> pretty arg+mockJenkinsPrefix :: (MonadThrowPos m) => [PValue] -> m PValue+mockJenkinsPrefix [] = return $ PString ""+mockJenkinsPrefix arg = throwPosError $ "expect no argument" <+> pretty arg -- To be implemented if needed.-mockJenkinsPort :: MonadThrowPos m => [PValue] -> m PValue-mockJenkinsPort [] = return $ PString "8080"-mockJenkinsPort arg@_ = throwPosError $ "expect no argument" <+> pretty arg+mockJenkinsPort :: (MonadThrowPos m) => [PValue] -> m PValue+mockJenkinsPort [] = return $ PString "8080"+mockJenkinsPort arg = throwPosError $ "expect no argument" <+> pretty arg -mockCacheData :: MonadThrowPos m => [PValue] -> m PValue+mockCacheData :: (MonadThrowPos m) => [PValue] -> m PValue mockCacheData [_, _, b] = return b-mockCacheData arg@_ = throwPosError $ "expect 3 string arguments" <+> pretty arg+mockCacheData arg = throwPosError $ "expect 3 string arguments" <+> pretty arg -- | Simple implemenation that does not handle all cases. -- For instance 'auth_option' is currently not implemented. -- Please add cases as needed.-pgAclsToHash :: MonadThrowPos m => [PValue] -> m PValue+pgAclsToHash :: (MonadThrowPos m) => [PValue] -> m PValue pgAclsToHash [PArray as, PString ident, PNumber offset] = PHash <$> aclsToHash as ident offset pgAclsToHash _ = throwPosError "expects 3 arguments; one array one string and one number" -aclsToHash :: MonadThrowPos m => Vector PValue -> Text -> Scientific -> m (Container PValue)+aclsToHash :: (MonadThrowPos m) => Vector PValue -> Text -> Scientific -> m (Container PValue) aclsToHash vec ident offset = ifoldlM f Map.empty vec where- f :: MonadThrowPos m => Int -> Container PValue -> PValue -> m (Container PValue)+ f :: (MonadThrowPos m) => Int -> Container PValue -> PValue -> m (Container PValue) f idx acc (PString acl) = do let order = offset + scientific (toInteger idx) 0 keymsg = sformat ("postgresql class generated rule " % FMT.stext % " " % FMT.int) ident idx@@ -124,81 +119,93 @@ aclToHash :: (MonadThrowPos m) => [Text] -> Scientific -> m PValue aclToHash acl@(typ : db : usr : remaining) order = analyze where- fin remn hs = return $ PHash $- if null remn- then hs- else Map.insert "auth_option" (PString (Text.unwords remn)) hs+ fin remn hs =+ return $+ PHash $+ if null remn+ then hs+ else Map.insert "auth_option" (PString (Text.unwords remn)) hs analyze = case remaining of- method : remn | typ == "local" ->- fin remn $ baseHash & at "auth_method" ?~ PString method- addr : msk : method : remn | Text.all isDigit msk ->- fin remn $ baseHash & at "address" ?~ PString (Text.unwords [addr,msk])- & at "auth_method" ?~ PString method- addr : method : remn ->- fin remn $ baseHash & at "address" ?~ PString addr- & at "auth_method" ?~ PString method- _ -> throwPosError $ "Unable to parse acl line" <+> squotes (ppline (Text.unwords acl))- baseHash = [ ("type", PString "local")- , ("database", PString db )- , ("user", PString usr)- , ("order", PString (sformat (FMT.left 3 '0' %. scifmt Sci.Fixed (Just 0)) order))- ]+ method : remn+ | typ == "local" ->+ fin remn $ baseHash & at "auth_method" ?~ PString method+ addr : msk : method : remn+ | Text.all isDigit msk ->+ fin remn $+ baseHash+ & at "address" ?~ PString (Text.unwords [addr, msk])+ & at "auth_method" ?~ PString method+ addr : method : remn ->+ fin remn $+ baseHash+ & at "address" ?~ PString addr+ & at "auth_method" ?~ PString method+ _ -> throwPosError $ "Unable to parse acl line" <+> squotes (ppline (Text.unwords acl))+ baseHash =+ [ ("type", PString "local"),+ ("database", PString db),+ ("user", PString usr),+ ("order", PString (sformat (FMT.left 3 '0' %. scifmt Sci.Fixed (Just 0)) order))+ ] aclToHash acl _ = throwPosError $ "Unable to parse acl line" <+> squotes (ppline (Text.unwords acl)) -- faked implementation, replace by the correct one if you need so.-mockDockerRunFlags :: MonadThrowPos m => [PValue] -> m PValue-mockDockerRunFlags arg@[PHash _]= (pure . PString . show . head) arg-mockDockerRunFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockDockerRunFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockDockerRunFlags arg@[PHash _] = (pure . PString . show . head) arg+mockDockerRunFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockDockerStackFlags :: MonadThrowPos m => [PValue] -> m PValue-mockDockerStackFlags arg@[PHash _]= (pure . PString . show . head) arg-mockDockerStackFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockDockerStackFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockDockerStackFlags arg@[PHash _] = (pure . PString . show . head) arg+mockDockerStackFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockDockerSecretsFlags :: MonadThrowPos m => [PValue] -> m PValue-mockDockerSecretsFlags arg@[PHash _]= (pure . PString . show . head) arg-mockDockerSecretsFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockDockerSecretsFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockDockerSecretsFlags arg@[PHash _] = (pure . PString . show . head) arg+mockDockerSecretsFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockDockerSwarmJoinFlags :: MonadThrowPos m => [PValue] -> m PValue-mockDockerSwarmJoinFlags arg@[PHash _]= (pure . PString . show . head) arg-mockDockerSwarmJoinFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockDockerSwarmJoinFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockDockerSwarmJoinFlags arg@[PHash _] = (pure . PString . show . head) arg+mockDockerSwarmJoinFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockDockerSwarmInitFlags :: MonadThrowPos m => [PValue] -> m PValue-mockDockerSwarmInitFlags arg@[PHash _]= (pure . PString . show . head) arg-mockDockerSwarmInitFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockDockerSwarmInitFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockDockerSwarmInitFlags arg@[PHash _] = (pure . PString . show . head) arg+mockDockerSwarmInitFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockKubernetesInitFlags :: MonadThrowPos m => [PValue] -> m PValue-mockKubernetesInitFlags arg@[PHash _]= (pure . PString . show . head) arg-mockKubernetesInitFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockKubernetesInitFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockKubernetesInitFlags arg@[PHash _] = (pure . PString . show . head) arg+mockKubernetesInitFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- faked implementation, replace by the correct one if you need so.-mockKubernetesJoinFlags :: MonadThrowPos m => [PValue] -> m PValue-mockKubernetesJoinFlags arg@[PHash _]= (pure . PString . show . head) arg-mockKubernetesJoinFlags arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+mockKubernetesJoinFlags :: (MonadThrowPos m) => [PValue] -> m PValue+mockKubernetesJoinFlags arg@[PHash _] = (pure . PString . show . head) arg+mockKubernetesJoinFlags arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg -- utils-scientificToInt :: MonadThrowPos m => Scientific -> m Int-scientificToInt s = maybe (throwPosError $ "Unable to convert" <+> pretty s <+> "into an int.")- pure- (Sci.toBoundedInteger s)+scientificToInt :: (MonadThrowPos m) => Scientific -> m Int+scientificToInt s =+ maybe+ (throwPosError $ "Unable to convert" <+> pretty s <+> "into an int.")+ pure+ (Sci.toBoundedInteger s) -- https://github.com/puppetlabs/puppetlabs-puppetdb/blob/master/lib/puppet/parser/functions/puppetdb_create_subsetting_resource_hash.rb-puppetdbCreateSubsettingResourceHash :: MonadThrowPos m => [PValue] -> m PValue+puppetdbCreateSubsettingResourceHash :: (MonadThrowPos m) => [PValue] -> m PValue puppetdbCreateSubsettingResourceHash [PHash s, PHash args] = do- let res_hash = [ (k, PHash h)- | (k,v) <- itoList s- , let h = [ ( "subsetting", PString k) , ("value", v)] `Map.union` args- ]+ let res_hash =+ [ (k, PHash h)+ | (k, v) <- itoList s,+ let h = [("subsetting", PString k), ("value", v)] `Map.union` args+ ] pure $ PHash (Map.fromList res_hash)-puppetdbCreateSubsettingResourceHash arg@_ = throwPosError $ "Expect 2 hashes as arguments but was" <+> pretty arg+puppetdbCreateSubsettingResourceHash arg = throwPosError $ "Expect 2 hashes as arguments but was" <+> pretty arg -- To be implemented if needed.-dockerSanitisedName :: MonadThrowPos m => [PValue] -> m PValue+dockerSanitisedName :: (MonadThrowPos m) => [PValue] -> m PValue dockerSanitisedName [PString s] = -- ruby implementation: regsubst($name, '[^0-9A-Za-z.\-_]', '-', 'G') pure $ PString s-dockerSanitisedName arg@_ = throwPosError $ "Expect an hash as argument but was" <+> pretty arg+dockerSanitisedName arg = throwPosError $ "Expect an hash as argument but was" <+> pretty arg
src/Puppet/Runner/Pure.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedLists #-}+ -- | Publicly exposed pure helpers to evaluate the 'InterpreterMonad' -- functions that can be found in "Puppet.Interpreter" and -- "Puppet.Interpreter.Resolve".@@ -7,43 +8,48 @@ -- -- >>> dummyEval (resolveExpression (Addition "1" "2")) -- Right (PString "3")-module Puppet.Runner.Pure (- dummyEval- , dummyFacts- , dummyInitialState- , pureEval- , pureEval'- , pureReader-) where--import XPrelude--import qualified Data.Either.Strict as S-import qualified Data.HashMap.Strict as HM--import Erb-import Facter-import Hiera.Server-import Puppet.Interpreter-import Puppet.Parser (Statement)-import Puppet.Runner.Erb-import PuppetDB (dummyPuppetDB)+module Puppet.Runner.Pure+ ( dummyEval,+ dummyFacts,+ dummyInitialState,+ pureEval,+ pureEval',+ pureReader,+ )+where +import qualified Data.Either.Strict as S+import qualified Data.HashMap.Strict as HM+import Erb+import Facter+import Hiera.Server+import Puppet.Interpreter+import Puppet.Parser (Statement)+import Puppet.Runner.Erb+import PuppetDB (dummyPuppetDB)+import XPrelude -- | Evaluates with a map of statements in a pure context. -- Unlike 'dummyEval', each hiera lookup is evaluated to return Nothing.-pureEval :: HM.HashMap (TopLevelType, Text) Statement -- ^ A top-level map- -> InterpreterMonad a -- ^ The action to evaluate- -> (Either PrettyError a, InterpreterState, InterpreterWriter)-pureEval stmap action =- pureEval' stmap dummyInitialState Nothing action+pureEval ::+ -- | A top-level map+ HM.HashMap (TopLevelType, Text) Statement ->+ -- | The action to evaluate+ InterpreterMonad a ->+ (Either PrettyError a, InterpreterState, InterpreterWriter)+pureEval stmap = pureEval' stmap dummyInitialState Nothing -- | More flexible version of 'pureEval'-pureEval' :: HM.HashMap (TopLevelType, Text) Statement -- ^ A top-level map- -> InterpreterState -- ^ the initial state- -> Maybe PValue -- ^ a value to be return by all hiera lookup- -> InterpreterMonad a -- ^ The action to evaluate- -> (Either PrettyError a, InterpreterState, InterpreterWriter)+pureEval' ::+ -- | A top-level map+ HM.HashMap (TopLevelType, Text) Statement ->+ -- | the initial state+ InterpreterState ->+ -- | a value to be return by all hiera lookup+ Maybe PValue ->+ -- | The action to evaluate+ InterpreterMonad a ->+ (Either PrettyError a, InterpreterState, InterpreterWriter) pureEval' stmap s0 hiera action = runIdentity (interpretMonad (pureReader stmap hiera) s0 action) @@ -57,9 +63,12 @@ -- | A pure 'InterpreterReader', that can only evaluate a subset of the -- templates, and that can include only the supplied top level statements.-pureReader :: HM.HashMap (TopLevelType, Text) Statement -- ^ A top-level statement map- -> Maybe PValue -- ^ What value a call to hiera should return- -> InterpreterReader Identity+pureReader ::+ -- | A top-level statement map+ HM.HashMap (TopLevelType, Text) Statement ->+ -- | What value a call to hiera should return+ Maybe PValue ->+ InterpreterReader Identity pureReader sttmap hiera = InterpreterReader baseNativeTypes@@ -80,13 +89,13 @@ pure_hiera :: HieraQueryFunc Identity pure_hiera _ _ _ = pure hiera hieradummy = HieraQueryLayers pure_hiera (\_ _ _ -> pure Nothing) mempty- getstatementdummy tlt n = return $ case HM.lookup (tlt,n) sttmap of- Just x -> S.Right x+ getstatementdummy tlt n = return $ case HM.lookup (tlt, n) sttmap of+ Just x -> S.Right x Nothing -> S.Left "Can't get statement" iomethods_purestubs :: IoMethods Identity iomethods_purestubs = IoMethods (return []) (const (return (Left "Can't read file"))) (\_ -> return ()) -dummyTemplate :: Monad m => TemplateSource -> InterpreterState -> InterpreterReader m -> m (S.Either PrettyError Text)+dummyTemplate :: (Monad m) => TemplateSource -> InterpreterState -> InterpreterReader m -> m (S.Either PrettyError Text) dummyTemplate (Filename _) _ _ = return (S.Left "Can't interpret files") dummyTemplate (Inline cnt) s _ = return $ case extractScope s of@@ -97,91 +106,101 @@ Right stmts -> case rubyEvaluate scope ctx stmts of Right x -> S.Right x- Left e -> S.Left (PrettyError e)+ Left e -> S.Left (PrettyError e) -- | A bunch of facts that can be used for pure evaluation. dummyFacts :: Facts-dummyFacts = HM.fromList- [ ("augeasversion", "0.10.0")- , ("bios_release_date", "07/06/2010")- , ("bios_vendor", "Dell Inc.")- , ("bios_version", "2.2.0")- , ("boardmanufacturer", "Dell Inc.")- , ("domain", "dummy.domain")- , ("facterversion", "1.7.5")- , ("filesystems", "ext2,ext3,ext4,vfat")- , ("fqdn", "dummy.dummy.domain")- , ("hardwareisa", "x86_64")- , ("hardwaremodel", "x86_64")- , ("hostname", "dummy")- , ("id", "root")- , ("interfaces", "eth0,lo")- , ("ipaddress", "172.17.42.1")- , ("ipaddress_eth0", "172.17.42.1")- , ("ipaddress_lo", "127.0.0.1")- , ("is_virtual", "false")- , ("kernel", "Linux")- , ("kernelmajversion", "3.8")- , ("kernelrelease", "3.8.0-37-generic")- , ("kernelversion", "3.8.0")- , ("lsbdistcodename", "precise")- , ("lsbdistdescription", "Ubuntu 12.04.4 LTS")- , ("lsbdistid", "Ubuntu")- , ("lsbdistrelease", "12.04")- , ("lsbmajdistrelease", "12")- , ("macaddress", "a5:cb:10:b0:9a:4b")- , ("macaddress_eth0", "72:53:10:c1:eb:70")- , ("manufacturer", "Dell Inc.")- , ("memoryfree", "12.57 GB")- , ("memoryfree_mb", "12869.89")- , ("memorysize", "15.63 GB")- , ("memorysize_mb", "16009.07")- , ("memorytotal", "15.63 GB")- , ("mtu_eth0", "1500")- , ("mtu_lo", "65536")- , ("netmask", "255.255.0.0")- , ("netmask_eth0", "255.255.255.0")- , ("netmask_lo", "255.0.0.0")- , ("network_eth0", "172.17.42.0")- , ("network_lo", "127.0.0.0")- , ("operatingsystem", "Ubuntu")- , ("operatingsystemrelease", "12.04")- , ("os", PHash [ ("architecture", "amd64")- , ("release", PHash [("major", "7")])- ])- , ("osfamily", "Debian")- , ("path", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin")- , ("processors", PHash [("models", PArray [ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- , "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"- ])- , ("count", "8")- , ("physicalprocessorcount", "1")- ])- , ("productname", "Vostro 430")- , ("ps", "ps -ef")- , ("puppetversion", "3.4.3")- , ("rubysitedir", "/usr/local/lib/site_ruby/1.8")- , ("rubyversion", "1.8.7")- , ("selinux", "false")- , ("serialnumber", "9L3FW4J")- , ("swapfree", "15.96 GB")- , ("swapfree_mb", "16340.00")- , ("swapsize", "15.96 GB")- , ("swapsize_mb", "16340.00")- , ("timezone", "CEST")- , ("type", "Desktop")- , ("uniqueid", "007f0101")- , ("uptime", "5:48 hours")- , ("uptime_days", "0")- , ("uptime_hours", "5")- , ("uptime_seconds", "20932")- , ("uuid", "97b75940-be55-11e3-b1b6-0800200c9a66")- , ("virtual", "physical")- ]+dummyFacts =+ HM.fromList+ [ ("augeasversion", "0.10.0"),+ ("bios_release_date", "07/06/2010"),+ ("bios_vendor", "Dell Inc."),+ ("bios_version", "2.2.0"),+ ("boardmanufacturer", "Dell Inc."),+ ("domain", "dummy.domain"),+ ("facterversion", "1.7.5"),+ ("filesystems", "ext2,ext3,ext4,vfat"),+ ("fqdn", "dummy.dummy.domain"),+ ("hardwareisa", "x86_64"),+ ("hardwaremodel", "x86_64"),+ ("hostname", "dummy"),+ ("id", "root"),+ ("interfaces", "eth0,lo"),+ ("ipaddress", "172.17.42.1"),+ ("ipaddress_eth0", "172.17.42.1"),+ ("ipaddress_lo", "127.0.0.1"),+ ("is_virtual", "false"),+ ("kernel", "Linux"),+ ("kernelmajversion", "3.8"),+ ("kernelrelease", "3.8.0-37-generic"),+ ("kernelversion", "3.8.0"),+ ("lsbdistcodename", "precise"),+ ("lsbdistdescription", "Ubuntu 12.04.4 LTS"),+ ("lsbdistid", "Ubuntu"),+ ("lsbdistrelease", "12.04"),+ ("lsbmajdistrelease", "12"),+ ("macaddress", "a5:cb:10:b0:9a:4b"),+ ("macaddress_eth0", "72:53:10:c1:eb:70"),+ ("manufacturer", "Dell Inc."),+ ("memoryfree", "12.57 GB"),+ ("memoryfree_mb", "12869.89"),+ ("memorysize", "15.63 GB"),+ ("memorysize_mb", "16009.07"),+ ("memorytotal", "15.63 GB"),+ ("mtu_eth0", "1500"),+ ("mtu_lo", "65536"),+ ("netmask", "255.255.0.0"),+ ("netmask_eth0", "255.255.255.0"),+ ("netmask_lo", "255.0.0.0"),+ ("network_eth0", "172.17.42.0"),+ ("network_lo", "127.0.0.0"),+ ("operatingsystem", "Ubuntu"),+ ("operatingsystemrelease", "12.04"),+ ( "os",+ PHash+ [ ("architecture", "amd64"),+ ("release", PHash [("major", "7")])+ ]+ ),+ ("osfamily", "Debian"),+ ("path", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"),+ ( "processors",+ PHash+ [ ( "models",+ PArray+ [ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz",+ "Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz"+ ]+ ),+ ("count", "8"),+ ("physicalprocessorcount", "1")+ ]+ ),+ ("productname", "Vostro 430"),+ ("ps", "ps -ef"),+ ("puppetversion", "3.4.3"),+ ("rubysitedir", "/usr/local/lib/site_ruby/1.8"),+ ("rubyversion", "1.8.7"),+ ("selinux", "false"),+ ("serialnumber", "9L3FW4J"),+ ("swapfree", "15.96 GB"),+ ("swapfree_mb", "16340.00"),+ ("swapsize", "15.96 GB"),+ ("swapsize_mb", "16340.00"),+ ("timezone", "CEST"),+ ("type", "Desktop"),+ ("uniqueid", "007f0101"),+ ("uptime", "5:48 hours"),+ ("uptime_days", "0"),+ ("uptime_hours", "5"),+ ("uptime_seconds", "20932"),+ ("uuid", "97b75940-be55-11e3-b1b6-0800200c9a66"),+ ("virtual", "physical")+ ]
src/Puppet/Runner/Stats.hs view
@@ -1,33 +1,37 @@-{-| A quickly done module that exports utility functions used to collect various-statistics. All statistics are stored in a MVar holding a HashMap.--This is not accurate in the presence of lazy evaluation. Nothing is forced.--}+-- | A quickly done module that exports utility functions used to collect various+-- statistics. All statistics are stored in a MVar holding a HashMap.+--+-- This is not accurate in the presence of lazy evaluation. Nothing is forced. module Puppet.Runner.Stats- ( measure- , newStats- , getStats- , StatsPoint(..)- , MStats- ) where--import XPrelude+ ( measure,+ newStats,+ getStats,+ StatsPoint (..),+ MStats,+ )+where -import Data.Time.Clock.POSIX (getPOSIXTime) import qualified Data.HashMap.Strict as Map+import Data.Time.Clock.POSIX (getPOSIXTime)+import XPrelude data StatsPoint = StatsPoint- { _statspointCount :: !Int -- ^ Total number of calls to a computation- , _statspointTotal :: !Double -- ^ Total time spent during this computation- , _statspointMin :: !Double -- ^ Minimum execution time- , _statspointMax :: !Double -- ^ Maximum execution time- } deriving (Show)+ { -- | Total number of calls to a computation+ _statspointCount :: !Int,+ -- | Total time spent during this computation+ _statspointTotal :: !Double,+ -- | Minimum execution time+ _statspointMin :: !Double,+ -- | Maximum execution time+ _statspointMax :: !Double+ }+ deriving (Show) -- | A table where keys are the names of the computations, and values are -- 'StatsPoint's. type StatsTable = HashMap Text StatsPoint -newtype MStats = MStats { unMStats :: MVar StatsTable }+newtype MStats = MStats {unMStats :: MVar StatsTable} -- | Returns the actual statistical values. getStats :: MStats -> IO StatsTable@@ -38,10 +42,14 @@ newStats = MStats `fmap` newMVar Map.empty -- | Wraps a computation, and measures related execution statistics.-measure :: MStats -- ^ Statistics container- -> Text -- ^ Action identifier- -> IO a -- ^ Computation- -> IO a+measure ::+ -- | Statistics container+ MStats ->+ -- | Action identifier+ Text ->+ -- | Computation+ IO a ->+ IO a measure (MStats mtable) statsname action = do (!tm, !out) <- time action !stats <- takeMVar mtable@@ -49,13 +57,9 @@ !nstats = case stats ^. at statsname of Nothing -> stats & at statsname ?~ StatsPoint 1 tm tm tm Just (StatsPoint sc st smi sma) ->- let !nmax = if tm > sma- then tm- else sma- !nmin = if tm < smi- then tm- else smi- in stats & at statsname ?~ StatsPoint (sc+1) (st+tm) nmin nmax+ let !nmax = max tm sma+ !nmin = min tm smi+ in stats & at statsname ?~ StatsPoint (sc + 1) (st + tm) nmin nmax putMVar mtable nstats return $! out
src/Puppet/Runner/Stdlib.hs view
@@ -1,162 +1,167 @@ {-# LANGUAGE PatternGuards #-}-{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-}-module Puppet.Runner.Stdlib (stdlibFunctions) where -import XPrelude hiding (sort)+module Puppet.Runner.Stdlib (stdlibFunctions) where -import qualified Data.Yaml as Yaml-import qualified Data.ByteString.Base16 as B16-import qualified Data.Char as Char-import qualified Data.HashMap.Strict as HM-import qualified Data.List as List-import qualified Data.List.Split as List (chunksOf)-import qualified Data.Scientific as Scientific-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import Data.Text.Lens (unpacked)-import qualified Data.Vector as V-import Data.Vector.Lens (toVectorOf)+import qualified Data.Aeson as Aeson+import qualified Data.ByteString.Base16 as B16+import qualified Data.ByteString.Lazy as LByteString+import qualified Data.Char as Char+import qualified Data.HashMap.Strict as HM+import qualified Data.List as List+import qualified Data.List.Split as List (chunksOf)+import qualified Data.Scientific as Scientific+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import Data.Text.Lens (unpacked)+import qualified Data.Vector as V+import Data.Vector.Lens (toVectorOf)+import qualified Data.Yaml as Yaml+import Puppet.Interpreter+import Puppet.Interpreter.Helpers+import qualified System.FilePath as FilePath import qualified Text.Regex.PCRE.ByteString.Utils as Regex-import qualified System.FilePath as FilePath--import Puppet.Interpreter-import Puppet.Interpreter.Helpers+import XPrelude hiding (sort) -- | Contains the implementation of the StdLib functions.-stdlibFunctions :: Container ( [PValue] -> InterpreterMonad PValue )-stdlibFunctions = HM.fromList [ singleArgument "abs" puppetAbs- , ("any2array", any2array)- , ("assert_private", assertPrivate)- , ("base64", base64)- -- basename- , singleArgument "bool2num" bool2num- -- bool2str- -- camelcase- , ("capitalize", stringArrayFunction (safeEmptyString (\t -> Text.cons (Char.toUpper (Text.head t)) (Text.tail t))))- -- ceiling- , ("chomp", stringArrayFunction (Text.dropWhileEnd (\c -> c == '\n' || c == '\r')))- , ("chop", stringArrayFunction (safeEmptyString Text.init))- -- clamp- , ("concat", puppetConcat)- -- convert_base- , ("count", puppetCount)- , ("deep_merge", deepMerge)- , ("defined_with_params", const (throwPosError "defined_with_params can't be implemented with language-puppet"))- , ("delete", delete)- , ("delete_at", deleteAt)- , singleArgument "delete_undef_values" deleteUndefValues- -- delete_values- -- difference- , singleArgument "dirname" dirname- -- dos2unix- , ("downcase", stringArrayFunction Text.toLower)- , singleArgument "empty" _empty- -- ensure_packages (in main interpreter module)- -- ensure_resource (in main interpreter module)- , singleArgument "fact" fact- , singleArgument "flatten" flatten- -- floor- -- fqdn_rand_string- -- fqdn_rotate- -- get_module_path- , ("getparam", const $ throwPosError "The getparam function is uncool and shall not be implemented in language-puppet")- , singleArgument "getvar" getvar- , ("grep", _grep)- , ("hash", hash)- -- has_interface_with- -- has_ip_address- -- has_ip_network- , ("has_key", hasKey)- -- intersection- -- is_absolute_path- , singleArgument "is_array" isArray- , singleArgument "is_bool" isBool- , singleArgument "is_domain_name" isDomainName- -- is_float- -- is_function_available- , singleArgument "is_hash" isHash- , singleArgument "is_integer" isInteger- -- is_ip_address- -- is_mac_address- -- is_numeric- , singleArgument "is_string" isString- , ("join", puppetJoin)- , ("join_keys_to_values", joinKeysToValues)- , singleArgument "keys" keys- , singleArgument "length" size- -- load_module_metadata- -- loadyaml- , ("lstrip", stringArrayFunction Text.stripStart)- -- max- , ("member", member)- , ("merge", merge)- -- min- -- num2bool- -- parsejson- -- parseyaml- , ("pick", pick)- , ("pick_default", pickDefault)- , ("prefix", prefix)- -- private- , ("pw_hash", pwHash)- -- range- -- reject- -- reverse- , ("rstrip", stringArrayFunction Text.stripEnd)- -- seeded_rand- -- shuffle- , singleArgument "size" size- , singleArgument "sort" sort- -- squeeze- , singleArgument "str2bool" str2Bool- -- strtosaltedshar512- -- strftime- , ("strip", stringArrayFunction Text.strip)- , ("suffix", suffix )- -- swapcase- -- time- -- to_bytes- -- try_get_value- -- type3x- -- type- -- union- , singleArgument "unique" unique- -- from puppetlabs-translate- , singleArgument "to_yaml" toYaml- , ("translate", translate)- -- unix2dos- , ("upcase", stringArrayFunction Text.toUpper)- -- uriescape- , ("validate_absolute_path", validateAbsolutePath)- , ("validate_array", validateArray)- -- validate_augeas- , ("validate_bool", validateBool)- -- validate_cmd- , ("validate_hash", validateHash)- , ("validate_integer", validateInteger)- -- validate_ip_address- -- validate_ipv4_address- -- validate_ipv6_address- , ("validate_numeric", validateNumeric)- , ("validate_re", validateRe)- -- validate_slength- , ("validate_string", validateString)- -- validate_x509_rsa_key_pair- -- values_at- , singleArgument "values" pvalues- -- zip- ]+stdlibFunctions :: Container ([PValue] -> InterpreterMonad PValue)+stdlibFunctions =+ HM.fromList+ [ singleArgument "abs" puppetAbs,+ ("any2array", any2array),+ ("assert_private", assertPrivate),+ ("base64", base64),+ -- basename+ singleArgument "bool2num" bool2num,+ -- bool2str+ -- camelcase+ ("capitalize", stringArrayFunction (safeEmptyString (\t -> Text.cons (Char.toUpper (Text.head t)) (Text.tail t)))),+ -- ceiling+ ("chomp", stringArrayFunction (Text.dropWhileEnd (\c -> c == '\n' || c == '\r'))),+ ("chop", stringArrayFunction (safeEmptyString Text.init)),+ -- clamp+ ("concat", puppetConcat),+ -- convert_base+ ("count", puppetCount),+ ("deep_merge", deepMerge),+ ("defined_with_params", const (throwPosError "defined_with_params can't be implemented with language-puppet")),+ ("delete", delete),+ ("delete_at", deleteAt),+ singleArgument "delete_undef_values" deleteUndefValues,+ -- delete_values+ -- difference+ singleArgument "dirname" dirname,+ -- dos2unix+ ("downcase", stringArrayFunction Text.toLower),+ singleArgument "empty" _empty,+ -- ensure_packages (in main interpreter module)+ -- ensure_resource (in main interpreter module)+ singleArgument "fact" fact,+ singleArgument "flatten" flatten,+ -- floor+ -- fqdn_rand_string+ -- fqdn_rotate+ -- get_module_path+ ("getparam", const $ throwPosError "The getparam function is uncool and shall not be implemented in language-puppet"),+ singleArgument "getvar" getvar,+ ("grep", _grep),+ ("hash", hash),+ -- has_interface_with+ -- has_ip_address+ -- has_ip_network+ ("has_key", hasKey),+ -- intersection+ -- is_absolute_path+ singleArgument "is_array" isArray,+ singleArgument "is_bool" isBool,+ singleArgument "is_domain_name" isDomainName,+ -- is_float+ -- is_function_available+ singleArgument "is_hash" isHash,+ singleArgument "is_integer" isInteger,+ -- is_ip_address+ -- is_mac_address+ -- is_numeric+ singleArgument "is_string" isString,+ ("join", puppetJoin),+ ("join_keys_to_values", joinKeysToValues),+ singleArgument "keys" keys,+ singleArgument "length" size,+ -- load_module_metadata+ -- loadyaml+ ("lstrip", stringArrayFunction Text.stripStart),+ -- max+ ("member", member),+ ("merge", merge),+ -- min+ -- num2bool+ -- parsejson+ -- parseyaml+ ("pick", pick),+ ("pick_default", pickDefault),+ ("prefix", prefix),+ -- private+ ("pw_hash", pwHash),+ -- range+ -- reject+ -- reverse+ ("rstrip", stringArrayFunction Text.stripEnd),+ -- seeded_rand+ -- shuffle+ singleArgument "size" size,+ singleArgument "sort" sort,+ -- squeeze+ singleArgument "str2bool" str2Bool,+ -- strtosaltedshar512+ -- strftime+ ("strip", stringArrayFunction Text.strip),+ ("suffix", suffix),+ -- swapcase+ -- time+ -- to_bytes+ -- try_get_value+ -- type3x+ -- type+ -- union+ singleArgument "unique" unique,+ -- from puppetlabs-translate+ singleArgument "to_yaml" toYaml,+ singleArgument "to_json" toJson,+ singleArgument "to_json_pretty" toJson,+ ("translate", translate),+ -- unix2dos+ ("upcase", stringArrayFunction Text.toUpper),+ -- uriescape+ ("validate_absolute_path", validateAbsolutePath),+ ("validate_array", validateArray),+ -- validate_augeas+ ("validate_bool", validateBool),+ -- validate_cmd+ ("validate_hash", validateHash),+ ("validate_integer", validateInteger),+ -- validate_ip_address+ -- validate_ipv4_address+ -- validate_ipv6_address+ ("validate_numeric", validateNumeric),+ ("validate_re", validateRe),+ -- validate_slength+ ("validate_string", validateString),+ -- validate_x509_rsa_key_pair+ -- values_at+ singleArgument "values" pvalues+ -- zip+ ] -singleArgument :: Text -> (PValue -> InterpreterMonad PValue) -> (Text, [PValue] -> InterpreterMonad PValue )+singleArgument :: Text -> (PValue -> InterpreterMonad PValue) -> (Text, [PValue] -> InterpreterMonad PValue) singleArgument fname ifunc = (fname, ofunc) where ofunc [x] = ifunc x- ofunc _ = throwPosError (ppline fname <> "(): Expects a single argument.")+ ofunc _ = throwPosError (ppline fname <> "(): Expects a single argument.") safeEmptyString :: (Text -> Text) -> Text -> Text safeEmptyString _ "" = ""-safeEmptyString f x = f x+safeEmptyString f x = f x stringArrayFunction :: (Text -> Text) -> [PValue] -> InterpreterMonad PValue stringArrayFunction f [PString s] = return (PString (f s))@@ -178,8 +183,8 @@ puppetAbs :: PValue -> InterpreterMonad PValue puppetAbs y = case y ^? _PValueNumber of- Just x -> return $ _PValueNumber # abs x- Nothing -> throwPosError ("abs(): Expects a number, not" <+> pretty y)+ Just x -> return $ _PValueNumber # abs x+ Nothing -> throwPosError ("abs(): Expects a number, not" <+> pretty y) suffix :: [PValue] -> InterpreterMonad PValue suffix = foofix "suffix" (flip (<>))@@ -196,33 +201,33 @@ foofix :: Doc -> (Text -> Text -> Text) -> [PValue] -> InterpreterMonad PValue foofix nm f args =- case args of- [PHash h] -> pure (PHash h)- [PArray r] -> pure (PArray r)- [_] -> throwPosError (nm <> ": expects the first argument to be an array or a hash")- [PHash h, PString s] -> pure (PHash . HM.fromList . map (_1 %~ f s) . HM.toList $ h)- [PArray r, PString s] -> pure (PArray (r & traverse . _PString %~ f s))- [PHash _, _] -> throwPosError (nm <> ": expects the second argument to be a string")- [PArray _, _] -> throwPosError (nm <> ": expects the second argument to be a string")- [_, _] -> throwPosError (nm <> ": expects the first argument to be an array or a hash")- _ -> throwPosError (nm <> ": expects two arguments")+ case args of+ [PHash h] -> pure (PHash h)+ [PArray r] -> pure (PArray r)+ [_] -> throwPosError (nm <> ": expects the first argument to be an array or a hash")+ [PHash h, PString s] -> pure (PHash . HM.fromList . map (_1 %~ f s) . HM.toList $ h)+ [PArray r, PString s] -> pure (PArray (r & traverse . _PString %~ f s))+ [PHash _, _] -> throwPosError (nm <> ": expects the second argument to be a string")+ [PArray _, _] -> throwPosError (nm <> ": expects the second argument to be a string")+ [_, _] -> throwPosError (nm <> ": expects the first argument to be an array or a hash")+ _ -> throwPosError (nm <> ": expects two arguments") assertPrivate :: [PValue] -> InterpreterMonad PValue assertPrivate args = case args of- [] -> go Nothing+ [] -> go Nothing [pv] -> resolvePValueString pv >>= go . Just- _ -> throwPosError "assert_private: expects no or a single string argument"+ _ -> throwPosError "assert_private: expects no or a single string argument" where go :: Maybe Text -> InterpreterMonad PValue go msg = do scp <- use curScope case scp of- funScope:callerScope:_ ->+ funScope : callerScope : _ -> let takeModule = Text.takeWhile (/= ':') . containerModName- in if takeModule funScope == takeModule callerScope- then return PUndef- else throwPosError $ maybe ("assert_private: failed: " <> pretty funScope <> " is private") ppline msg+ in if takeModule funScope == takeModule callerScope+ then return PUndef+ else throwPosError $ maybe ("assert_private: failed: " <> pretty funScope <> " is private") ppline msg _ -> return PUndef any2array :: [PValue] -> InterpreterMonad PValue@@ -230,19 +235,20 @@ any2array [PHash h] = return (PArray lst) where lst = V.fromList $ concatMap arraypair $ HM.toList h- arraypair (a,b) = [PString a, b]+ arraypair (a, b) = [PString a, b] any2array [x] = return (PArray (V.singleton x)) any2array x = return (PArray (V.fromList x)) base64 :: [PValue] -> InterpreterMonad PValue-base64 [pa,pb] = do+base64 [pa, pb] = do b <- encodeUtf8 <$> resolvePValueString pb- r <- resolvePValueString pa >>= \case- "encode" -> return (B16.encode b)- "decode" -> case B16.decode b of- (x, "") -> return x- _ -> error ("base64(): could not decode" <+> pretty pb) *> pure mempty- a -> throwPosError ("base64(): the first argument must be either 'encode' or 'decode', not" <+> ppline a)+ r <-+ resolvePValueString pa >>= \case+ "encode" -> return (B16.encode b)+ "decode" -> case B16.decode b of+ Right x -> return x+ Left msg -> mempty <$ error ("base64(): could not decode" <+> pretty pb <+> ppstring msg)+ a -> throwPosError ("base64(): the first argument must be either 'encode' or 'decode', not" <+> ppline a) pure $ PString (decodeUtf8 r) base64 _ = throwPosError "base64(): Expects 2 arguments" @@ -267,18 +273,19 @@ puppetConcat = return . PArray . V.concat . map toArr where toArr (PArray x) = x- toArr x = V.singleton x+ toArr x = V.singleton x puppetCount :: [PValue] -> InterpreterMonad PValue puppetCount [PArray x] = return (_PValueInteger # V.foldl' cnt 0 x) where cnt cur (PString "") = cur- cnt cur PUndef = cur- cnt cur _ = cur + 1+ cnt cur PUndef = cur+ cnt cur _ = cur + 1 puppetCount [PArray x, y] = return (_PValueInteger # V.foldl' cnt 0 x) where- cnt cur z | y == z = cur + 1- | otherwise = cur+ cnt cur z+ | y == z = cur + 1+ | otherwise = cur puppetCount _ = throwPosError "count(): expects 1 or 2 arguments" delete :: [PValue] -> InterpreterMonad PValue@@ -287,7 +294,7 @@ delete [PHash h, z] = do tz <- resolvePValueString z return $ PHash (h & at tz .~ Nothing)-delete [a,_] = throwPosError ("delete(): First argument must be an Array, String, or Hash. Given:" <+> pretty a)+delete [a, _] = throwPosError ("delete(): First argument must be an Array, String, or Hash. Given:" <+> pretty a) delete _ = throwPosError "delete(): expects 2 arguments" deleteAt :: [PValue] -> InterpreterMonad PValue@@ -296,18 +303,18 @@ let n = fromInteger gn lr = V.length r s1 = V.slice 0 n r- s2 = V.slice (n+1) (lr - n - 1) r- in if V.length r <= n+ s2 = V.slice (n + 1) (lr - n - 1) r+ in if V.length r <= n then throwPosError ("delete_at(): Out of bounds access detected, tried to remove index" <+> pretty z <+> "wheras the array only has" <+> pplines (show lr) <+> "elements") else return (PArray (s1 <> s2)) _ -> throwPosError ("delete_at(): The second argument must be an integer, not" <+> pretty z)-deleteAt [x,_] = throwPosError ("delete_at(): expects its first argument to be an array, not" <+> pretty x)+deleteAt [x, _] = throwPosError ("delete_at(): expects its first argument to be an array, not" <+> pretty x) deleteAt _ = throwPosError "delete_at(): expects 2 arguments" deleteUndefValues :: PValue -> InterpreterMonad PValue deleteUndefValues (PArray r) = return $ PArray $ V.filter (/= PUndef) r deleteUndefValues (PHash h) = return $ PHash $ HM.filter (/= PUndef) h-deleteUndefValues x = throwPosError ("delete_undef_values(): Expects an Array or a Hash, not" <+> pretty x)+deleteUndefValues x = throwPosError ("delete_undef_values(): Expects an Array or a Hash, not" <+> pretty x) _empty :: PValue -> InterpreterMonad PValue _empty = return . PBoolean . flip elem [PUndef, PString "", PString "undef", PArray V.empty, PHash HM.empty]@@ -324,7 +331,7 @@ where flatten' :: PValue -> V.Vector PValue flatten' (PArray x) = V.concatMap flatten' x- flatten' x = V.singleton x+ flatten' x = V.singleton x flatten x = throwPosError ("flatten(): Expects an Array, not" <+> pretty x) getvar :: PValue -> InterpreterMonad PValue@@ -334,19 +341,19 @@ _grep [PArray vls, rawre] = do regexp <- resolvePValueString rawre >>= compileRE rvls <- for vls $ \v -> do- r <- resolvePValueString v- ismatched <- matchRE regexp r- return (r, ismatched)+ r <- resolvePValueString v+ ismatched <- matchRE regexp r+ return (r, ismatched) return $ PArray $ V.map (PString . fst) (V.filter snd rvls)-_grep [x,_] = throwPosError ("grep(): The first argument must be an Array, not" <+> pretty x)+_grep [x, _] = throwPosError ("grep(): The first argument must be an Array, not" <+> pretty x) _grep _ = throwPosError "grep(): Expected two arguments." hash :: [PValue] -> InterpreterMonad PValue hash [PArray elems] = do let xs = mapMaybe assocPairs $ List.chunksOf 2 $ V.toList elems- assocPairs [a,b] = Just (a,b)- assocPairs _ = Nothing- PHash . HM.fromList <$> mapM (\(k,v) -> (,v) <$> resolvePValueString k) xs+ assocPairs [a, b] = Just (a, b)+ assocPairs _ = Nothing+ PHash . HM.fromList <$> mapM (\(k, v) -> (,v) <$> resolvePValueString k) xs hash _ = throwPosError "hash(): Expected and array." isArray :: PValue -> InterpreterMonad PValue@@ -355,15 +362,17 @@ isDomainName :: PValue -> InterpreterMonad PValue isDomainName s = do rs <- resolvePValueString s- let ndrs = if Text.last rs == '.'- then Text.init rs- else rs+ let ndrs =+ if Text.last rs == '.'+ then Text.init rs+ else rs prts = Text.splitOn "." ndrs- checkPart x = not (Text.null x)- && (Text.length x <= 63)- && (Text.head x /= '-')- && (Text.last x /= '-')- && Text.all (\y -> Char.isAlphaNum y || y == '-') x+ checkPart x =+ not (Text.null x)+ && (Text.length x <= 63)+ && (Text.head x /= '-')+ && (Text.last x /= '-')+ && Text.all (\y -> Char.isAlphaNum y || y == '-') x return $ PBoolean $ not (Text.null rs) && Text.length rs <= 255 && all checkPart prts isInteger :: PValue -> InterpreterMonad PValue@@ -374,42 +383,42 @@ isString :: PValue -> InterpreterMonad PValue isString pv = return $ PBoolean $ case (pv ^? _PString, pv ^? _PValueNumber) of- (_, Just _) -> False- (Just _, _) -> True- _ -> False+ (_, Just _) -> False+ (Just _, _) -> True+ _ -> False isBool :: PValue -> InterpreterMonad PValue isBool = return . PBoolean . has _PBoolean puppetJoin :: [PValue] -> InterpreterMonad PValue puppetJoin [PArray rr, PString interc] = do- rrt <- mapM resolvePValueString (V.toList rr)- return (PString (Text.intercalate interc rrt))-puppetJoin [_,_] = throwPosError "join(): expected an array of strings, and a string"+ rrt <- mapM resolvePValueString (V.toList rr)+ return (PString (Text.intercalate interc rrt))+puppetJoin [_, _] = throwPosError "join(): expected an array of strings, and a string" puppetJoin _ = throwPosError "join(): expected two arguments" joinKeysToValues :: [PValue] -> InterpreterMonad PValue joinKeysToValues [PHash h, separator] = do- ssep <- resolvePValueString separator- fmap (PArray . V.fromList) $ forM (itoList h) $ \(k,v) -> do- sv <- case v of- PUndef -> return ""- _ -> resolvePValueString v- return (PString (k <> ssep <> sv))+ ssep <- resolvePValueString separator+ fmap (PArray . V.fromList) $ forM (itoList h) $ \(k, v) -> do+ sv <- case v of+ PUndef -> return ""+ _ -> resolvePValueString v+ return (PString (k <> ssep <> sv)) joinKeysToValues _ = throwPosError "join_keys_to_values(): expects 2 arguments, an hash and a string" keys :: PValue -> InterpreterMonad PValue keys (PHash h) = return (PArray $ V.fromList $ map PString $ HM.keys h)-keys x = throwPosError ("keys(): Expects a Hash, not" <+> pretty x)+keys x = throwPosError ("keys(): Expects a Hash, not" <+> pretty x) member :: [PValue] -> InterpreterMonad PValue member [PArray v, x] = return $ PBoolean (x `V.elem` v)-member _ = throwPosError "member() expects 2 arguments"+member _ = throwPosError "member() expects 2 arguments" hasKey :: [PValue] -> InterpreterMonad PValue hasKey [PHash h, k] = do- k' <- resolvePValueString k- return (PBoolean (has (ix k') h))+ k' <- resolvePValueString k+ return (PBoolean (has (ix k') h)) hasKey [a, _] = throwPosError ("has_key(): expected a Hash, not" <+> pretty a) hasKey _ = throwPosError "has_key(): expected two arguments." @@ -417,24 +426,22 @@ merge xs | length xs < 2 = throwPosError "merge(): Expects at least two hashes" | otherwise =- let hashcontents = mapM (preview _PHash) xs- in- case hashcontents of- Nothing -> throwPosError "merge(): Expects hashes"- Just hashes -> return $ PHash (getDual $ foldMap Dual hashes)+ let hashcontents = mapM (preview _PHash) xs+ in case hashcontents of+ Nothing -> throwPosError "merge(): Expects hashes"+ Just hashes -> return $ PHash (getDual $ foldMap Dual hashes) deepMerge :: [PValue] -> InterpreterMonad PValue deepMerge xs | length xs < 2 = throwPosError "deep_merge(): Expects at least two hashes" | otherwise =- let hashcontents = mapM (preview _PHash) xs- in- case hashcontents of- Nothing -> throwPosError "deep_merge(): Expects hashes"- Just hashes -> pure $ PHash (List.foldr1 rec_merge hashes)+ let hashcontents = mapM (preview _PHash) xs+ in case hashcontents of+ Nothing -> throwPosError "deep_merge(): Expects hashes"+ Just hashes -> pure $ PHash (List.foldr1 rec_merge hashes) where rec_merge :: Container PValue -> Container PValue -> Container PValue- rec_merge a b = HM.unionWith f a b+ rec_merge = HM.unionWith f f :: PValue -> PValue -> PValue f (PHash a) (PHash b) = PHash $ rec_merge a b f _ h = h@@ -443,15 +450,15 @@ pick [] = throwPosError "pick(): must receive at least one non empty value" pick xs = case filter (`notElem` [PUndef, PString "", PString "undef"]) xs of- [] -> throwPosError "pick(): no value suitable to be picked"- (x:_) -> return x+ [] -> throwPosError "pick(): no value suitable to be picked"+ (x : _) -> return x pickDefault :: [PValue] -> InterpreterMonad PValue pickDefault [] = throwPosError "pick_default(): must receive at least one non empty value" pickDefault xs = case filter (`notElem` [PUndef, PString "", PString "undef"]) xs of- [] -> return (List.last xs)- (x:_) -> return x+ [] -> return (List.last xs)+ (x : _) -> return x size :: PValue -> InterpreterMonad PValue size (PHash h) = return (_PValueInteger # fromIntegral (HM.size h))@@ -471,9 +478,9 @@ sort :: PValue -> InterpreterMonad PValue sort (PArray s) = let lst = V.toList s- msort :: Ord a => Prism' PValue a -> Maybe PValue+ msort :: (Ord a) => Prism' PValue a -> Maybe PValue msort prsm = PArray . V.fromList . map (review prsm) . List.sort <$> mapM (preview prsm) lst- in case msort _PString <|> msort _PNumber of+ in case msort _PString <|> msort _PNumber of Just x -> return x _ -> throwPosError "sort(): only homogeneous arrays of numbers or strings are allowed" sort x = throwPosError ("sort(): Expect to sort an array, not" <+> pretty x)@@ -483,53 +490,55 @@ str2Bool a@(PBoolean _) = return a str2Bool a = do s <- resolvePValueString a- let b | s `elem` ["", "1", "t", "y", "true", "yes"] = Just True- | s `elem` [ "0", "f", "n", "false", "no"] = Just False+ let b+ | s `elem` ["", "1", "t", "y", "true", "yes"] = Just True+ | s `elem` ["0", "f", "n", "false", "no"] = Just False | otherwise = Nothing case b of- Just x -> return (PBoolean x)+ Just x -> return (PBoolean x) Nothing -> throwPosError "str2bool(): Unknown type of boolean given" validateAbsolutePath :: [PValue] -> InterpreterMonad PValue validateAbsolutePath [] = throwPosError "validateAbsolutePath(): wrong number of arguments, must be > 0" validateAbsolutePath a = mapM_ (resolvePValueString >=> validate) a >> return PUndef where- validate x | Text.head x == '/' = return ()- | otherwise = throwPosError (ppline x <+> "is not an absolute path")+ validate x+ | Text.head x == '/' = return ()+ | otherwise = throwPosError (ppline x <+> "is not an absolute path") validateArray :: [PValue] -> InterpreterMonad PValue validateArray [] = throwPosError "validate_array(): wrong number of arguments, must be > 0" validateArray x = mapM_ vb x >> return PUndef where vb (PArray _) = return ()- vb y = throwPosError (pretty y <+> "is not an array.")+ vb y = throwPosError (pretty y <+> "is not an array.") validateBool :: [PValue] -> InterpreterMonad PValue validateBool [] = throwPosError "validate_bool(): wrong number of arguments, must be > 0" validateBool x = mapM_ vb x >> return PUndef where vb (PBoolean _) = return ()- vb y = throwPosError (pretty y <+> "is not a boolean.")+ vb y = throwPosError (pretty y <+> "is not a boolean.") validateHash :: [PValue] -> InterpreterMonad PValue validateHash [] = throwPosError "validate_hash(): wrong number of arguments, must be > 0" validateHash x = mapM_ vb x >> return PUndef where vb (PHash _) = return ()- vb y = throwPosError (pretty y <+> "is not a hash.")+ vb y = throwPosError (pretty y <+> "is not a hash.") validateNumeric :: [PValue] -> InterpreterMonad PValue validateNumeric [] = throwPosError "validate_numeric: invalid arguments"-validateNumeric (arr:extra) = do+validateNumeric (arr : extra) = do (mn, mx) <- case extra of- [mx'] -> (Nothing,) . Just <$> resolvePValueNumber mx'- [PUndef, mi'] -> (,Nothing) . Just <$> resolvePValueNumber mi'- [mx',mi'] -> (,) <$> (Just <$> resolvePValueNumber mi') <*> (Just <$> resolvePValueNumber mx')- [] -> pure (Nothing, Nothing)- _ -> throwPosError "validate_numeric: invalid arguments"+ [mx'] -> (Nothing,) . Just <$> resolvePValueNumber mx'+ [PUndef, mi'] -> (,Nothing) . Just <$> resolvePValueNumber mi'+ [mx', mi'] -> (,) <$> (Just <$> resolvePValueNumber mi') <*> (Just <$> resolvePValueNumber mx')+ [] -> pure (Nothing, Nothing)+ _ -> throwPosError "validate_numeric: invalid arguments" numbers <- case arr of- PArray lst -> mapM resolvePValueNumber (V.toList lst)- _ -> pure <$> resolvePValueNumber arr+ PArray lst -> mapM resolvePValueNumber (V.toList lst)+ _ -> pure <$> resolvePValueNumber arr forM_ mn $ \mn' -> unless (all (>= mn') numbers) $ throwPosError "validate_numeric: failure" forM_ mx $ \mx' -> unless (all (<= mx') numbers) $ throwPosError "validate_numeric: failure" return PUndef@@ -548,7 +557,7 @@ validateString :: [PValue] -> InterpreterMonad PValue validateString [] = throwPosError "validate_string(): wrong number of arguments, must be > 0"-validateString x = mapM_ resolvePValueString x >> return PUndef+validateString x = mapM_ resolvePValueString x >> return PUndef validateInteger :: [PValue] -> InterpreterMonad PValue validateInteger [] = throwPosError "validate_integer(): wrong number of arguments, must be > 0"@@ -558,11 +567,11 @@ check n = unless (Scientific.isInteger n) $ throwPosError (msg n) vb (PNumber n) = check n vb (PString s) | Just n <- text2Scientific s = check n- vb a = throwPosError (msg a)+ vb a = throwPosError (msg a) pvalues :: PValue -> InterpreterMonad PValue pvalues (PHash h) = return $ PArray (toVectorOf traverse h)-pvalues x = throwPosError ("values(): expected a hash, not" <+> pretty x)+pvalues x = throwPosError ("values(): expected a hash, not" <+> pretty x) -- dummy translate method from puppetlabs-translate (used in puppetlabs-docker for instance) translate :: [PValue] -> InterpreterMonad PValue@@ -571,3 +580,6 @@ toYaml :: PValue -> InterpreterMonad PValue toYaml s = pure $ PString $ Text.decodeUtf8 (Yaml.encode s)++toJson :: PValue -> InterpreterMonad PValue+toJson s = pure $ PString $ Text.decodeUtf8 (LByteString.toStrict (Aeson.encode s))
src/PuppetDB.hs view
@@ -1,34 +1,31 @@ -- | Common data types for PuppetDB. module PuppetDB- ( dummyPuppetDB- , getDefaultDB- , pdbConnect- , loadTestDB- , generateWireCatalog- , puppetDBFacts- , module PuppetDB.Core- ) where--import XPrelude--import qualified Data.HashMap.Strict as Map-import Control.Arrow ((***))-import qualified Data.Text as Text-import Data.Vector.Lens-import Network.HTTP.Client-import System.Environment--import Facter-import Puppet.Language-import PuppetDB.Core-import PuppetDB.Remote-import PuppetDB.TestDB-+ ( dummyPuppetDB,+ getDefaultDB,+ pdbConnect,+ loadTestDB,+ generateWireCatalog,+ puppetDBFacts,+ module PuppetDB.Core,+ )+where +import Control.Arrow ((***))+import qualified Data.HashMap.Strict as Map+import qualified Data.Text as Text+import Data.Vector.Lens+import Facter+import Network.HTTP.Client+import Puppet.Language+import PuppetDB.Core+import PuppetDB.Remote+import PuppetDB.TestDB+import System.Environment+import XPrelude -- | Given a 'PDBType', will try return a sane default implementation. getDefaultDB :: PDBType -> IO (Either PrettyError (PuppetDBAPI IO))-getDefaultDB PDBDummy = return (Right dummyPuppetDB)+getDefaultDB PDBDummy = return (Right dummyPuppetDB) getDefaultDB PDBRemote = do let url = "http://localhost:8080" mgr <- newManager defaultManagerSettings@@ -38,9 +35,8 @@ Just h -> loadTestDB (h <> "/.testdb") Nothing -> fmap Right initTestDB - -- | A dummy implementation of 'PuppetDBAPI', that will return empty responses.-dummyPuppetDB :: Monad m => PuppetDBAPI m+dummyPuppetDB :: (Monad m) => PuppetDBAPI m dummyPuppetDB = PuppetDBAPI (return "dummy")@@ -58,56 +54,63 @@ generateWireCatalog :: NodeName -> FinalCatalog -> EdgeMap -> WireCatalog generateWireCatalog node cat edgemap = WireCatalog node "version" edges resources "uiid" where- edges = toVectorOf (folded . to (\li -> PuppetEdge (li ^. linksrc) (li ^. linkdst) (li ^. linkType))) (concatOf folded edgemap)+ edges = toVectorOf (folded . to (\li -> PuppetEdge (li ^. linksrc) (li ^. linkdst) (li ^. linkType))) (concatOf folded edgemap) resources = toVectorOf folded cat puppetDBFacts :: NodeName -> PuppetDBAPI IO -> IO (HashMap Text PValue) puppetDBFacts node pdbapi = runExceptT (getPDBFacts pdbapi (QEqual FCertname node)) >>= \case- Right facts@(_:_) -> return (Map.fromList (map (\f -> (f ^. factInfoName, f ^. factInfoVal)) facts))+ Right facts@(_ : _) -> return (Map.fromList (map (\f -> (f ^. factInfoName, f ^. factInfoVal)) facts)) _ -> do- rawFacts <- fmap concat (sequence [factNET, factRAM, factOS, fversion, factMountPoints, factOS, factUser, factUName, fenv, factProcessor])- let ofacts = genFacts $ map (Text.pack *** Text.pack) rawFacts- (hostname, ddomainname) = Text.break (== '.') node- domainname = if Text.null ddomainname- then ""- else Text.tail ddomainname- nfacts = genFacts [ ("fqdn", node)- , ("hostname", hostname)- , ("domain", domainname)- , ("rootrsa", "xxx")- , ("operatingsystem", "Ubuntu")- , ("puppetversion", "language-puppet")- , ("virtual", "xenu")- , ("clientcert", node)- , ("is_virtual", "true")- , ("concat_basedir", "/var/lib/puppet/concat")- ]- allfacts = nfacts `Map.union` ofacts- genFacts = Map.fromList- return (allfacts & traverse %~ PString & buildOSHash)+ rawFacts <- fmap concat (sequence [factNET, factRAM, factOS, fversion, factMountPoints, factOS, factUser, factUName, fenv, factProcessor])+ let ofacts = genFacts $ map (Text.pack *** Text.pack) rawFacts+ (hostname, ddomainname) = Text.break (== '.') node+ domainname =+ if Text.null ddomainname+ then ""+ else Text.tail ddomainname+ nfacts =+ genFacts+ [ ("fqdn", node),+ ("hostname", hostname),+ ("domain", domainname),+ ("rootrsa", "xxx"),+ ("operatingsystem", "Ubuntu"),+ ("puppetversion", "language-puppet"),+ ("virtual", "xenu"),+ ("clientcert", node),+ ("is_virtual", "true"),+ ("concat_basedir", "/var/lib/puppet/concat")+ ]+ allfacts = nfacts `Map.union` ofacts+ genFacts = Map.fromList+ return (allfacts & traverse %~ PString & buildOSHash) buildOSHash :: Facts -> Facts buildOSHash facts = case buildObject topLevel of- Nothing -> facts- Just os -> facts & at "os" ?~ os+ Nothing -> facts+ Just os -> facts & at "os" ?~ os where buildObject keys = let nobject = foldl' addKey mempty keys- in if nobject == mempty+ in if nobject == mempty then Nothing else Just (PHash nobject) g k = facts ^? ix k- topLevel = [ ("name", g "operatingsystem")- , ("family", g "osfamily")- , ("release", buildObject [("major", g "lsbdistrelease"), ("full", g "lsbdistrelease")])- , ("lsb", buildObject [ ("distcodename", g "lsbdistcodename")- , ("distid", g "lsbdistid")- , ("distdescription", g "lsbdistdescription")- , ("distrelease", g "lsbdistrelease")- , ("majdistrelease", g "lsbmajdistrelease")- ])- ]+ topLevel =+ [ ("name", g "operatingsystem"),+ ("family", g "osfamily"),+ ("release", buildObject [("major", g "lsbdistrelease"), ("full", g "lsbdistrelease")]),+ ( "lsb",+ buildObject+ [ ("distcodename", g "lsbdistcodename"),+ ("distid", g "lsbdistid"),+ ("distdescription", g "lsbdistdescription"),+ ("distrelease", g "lsbdistrelease"),+ ("majdistrelease", g "lsbmajdistrelease")+ ]+ )+ ] addKey hash (k, mv) = case mv of- Nothing -> hash- Just v -> hash & at k ?~ v+ Nothing -> hash+ Just v -> hash & at k ?~ v
src/PuppetDB/Core.hs view
@@ -1,69 +1,74 @@ {-# LANGUAGE TemplateHaskell #-}-module PuppetDB.Core -where--import XPrelude hiding (Read)+module PuppetDB.Core where -import Control.Lens-import Data.Aeson-import qualified Data.List as List-import Data.Maybe (fromJust)+import Control.Lens+import Data.Aeson+import qualified Data.List as List+import Data.Maybe (fromJust) import qualified Data.Maybe.Strict as S-import Data.Time.Clock-import GHC.Read (Read (..))-import Web.HttpApiData (ToHttpApiData (..))--import Facter-import Puppet.Language+import Data.Time.Clock+import Facter+import GHC.Read (Read (..))+import Puppet.Language+import Web.HttpApiData (ToHttpApiData (..))+import XPrelude hiding (Read) -- | The supported PuppetDB implementations. data PDBType- = PDBRemote -- ^ Your standard PuppetDB, queried through the HTTP interface.- | PDBDummy -- ^ A stupid stub, this is the default choice.- | PDBTest -- ^ A slow but handy PuppetDB implementation that is backed by a YAML file.+ = -- | Your standard PuppetDB, queried through the HTTP interface.+ PDBRemote+ | -- | A stupid stub, this is the default choice.+ PDBDummy+ | -- | A slow but handy PuppetDB implementation that is backed by a YAML file.+ PDBTest deriving (Eq) instance Read PDBType where- readsPrec _ r | isJust reml = [(PDBRemote, fromJust reml)]- | isJust rems = [(PDBRemote, fromJust rems)]- | isJust duml = [(PDBDummy, fromJust duml)]- | isJust dums = [(PDBDummy, fromJust dums)]- | isJust tstl = [(PDBTest, fromJust tstl)]- | isJust tsts = [(PDBTest, fromJust tsts)]- | otherwise = []+ readsPrec _ r+ | isJust reml = [(PDBRemote, fromJust reml)]+ | isJust rems = [(PDBRemote, fromJust rems)]+ | isJust duml = [(PDBDummy, fromJust duml)]+ | isJust dums = [(PDBDummy, fromJust dums)]+ | isJust tstl = [(PDBTest, fromJust tstl)]+ | isJust tsts = [(PDBTest, fromJust tsts)]+ | otherwise = [] where reml = List.stripPrefix "PDBRemote" r- rems = List.stripPrefix "remote" r- duml = List.stripPrefix "PDBDummy" r- dums = List.stripPrefix "dummy" r- tstl = List.stripPrefix "PDBTest" r- tsts = List.stripPrefix "test" r+ rems = List.stripPrefix "remote" r+ duml = List.stripPrefix "PDBDummy" r+ dums = List.stripPrefix "dummy" r+ tstl = List.stripPrefix "PDBTest" r+ tsts = List.stripPrefix "test" r data NodeInfo = NodeInfo- { _nodeInfoName :: !NodeName- , _nodeInfoDeactivated :: !Bool- , _nodeInfoCatalogT :: !(S.Maybe UTCTime)- , _nodeInfoFactsT :: !(S.Maybe UTCTime)- , _nodeInfoReportT :: !(S.Maybe UTCTime)- }+ { _nodeInfoName :: !NodeName,+ _nodeInfoDeactivated :: !Bool,+ _nodeInfoCatalogT :: !(S.Maybe UTCTime),+ _nodeInfoFactsT :: !(S.Maybe UTCTime),+ _nodeInfoReportT :: !(S.Maybe UTCTime)+ } makeClassy ''NodeInfo instance ToJSON NodeInfo where- toJSON p = object [ ("name" , toJSON (p ^. nodeInfoName))- , ("deactivated" , toJSON (p ^. nodeInfoDeactivated))- , ("catalog_timestamp", toJSON (p ^. nodeInfoCatalogT))- , ("facts_timestamp" , toJSON (p ^. nodeInfoFactsT))- , ("report_timestamp" , toJSON (p ^. nodeInfoReportT))- ]+ toJSON p =+ object+ [ ("name", toJSON (p ^. nodeInfoName)),+ ("deactivated", toJSON (p ^. nodeInfoDeactivated)),+ ("catalog_timestamp", toJSON (p ^. nodeInfoCatalogT)),+ ("facts_timestamp", toJSON (p ^. nodeInfoFactsT)),+ ("report_timestamp", toJSON (p ^. nodeInfoReportT))+ ] instance FromJSON NodeInfo where- parseJSON (Object v) = NodeInfo <$> v .: "name"- <*> v .:? "deactivated" .!= False- <*> v .: "catalog_timestamp"- <*> v .: "facts_timestamp"- <*> v .: "report_timestamp"+ parseJSON (Object v) =+ NodeInfo+ <$> v .: "name"+ <*> v .:? "deactivated" .!= False+ <*> v .: "catalog_timestamp"+ <*> v .: "facts_timestamp"+ <*> v .: "report_timestamp" parseJSON _ = fail "invalide node info" -- | Pretty straightforward way to define the various PuppetDB queries@@ -79,35 +84,35 @@ | QNot (Query a) | QEmpty -instance ToJSON a => ToJSON (Query a) where- toJSON (QOr qs) = toJSON ("or" : map toJSON qs)- toJSON (QAnd qs) = toJSON ("and" : map toJSON qs)- toJSON (QNot q) = toJSON [ "not" , toJSON q ]- toJSON (QEqual flds val) = toJSON [ "=", toJSON flds, toJSON val ]- toJSON (QMatch flds val) = toJSON [ "~", toJSON flds, toJSON val ]- toJSON (QL flds val) = toJSON [ "<", toJSON flds, toJSON val ]- toJSON (QG flds val) = toJSON [ ">", toJSON flds, toJSON val ]- toJSON (QLE flds val) = toJSON [ "<=", toJSON flds, toJSON val ]- toJSON (QGE flds val) = toJSON [ ">=", toJSON flds, toJSON val ]- toJSON QEmpty = Null+instance (ToJSON a) => ToJSON (Query a) where+ toJSON (QOr qs) = toJSON ("or" : map toJSON qs)+ toJSON (QAnd qs) = toJSON ("and" : map toJSON qs)+ toJSON (QNot q) = toJSON ["not", toJSON q]+ toJSON (QEqual flds val) = toJSON ["=", toJSON flds, toJSON val]+ toJSON (QMatch flds val) = toJSON ["~", toJSON flds, toJSON val]+ toJSON (QL flds val) = toJSON ["<", toJSON flds, toJSON val]+ toJSON (QG flds val) = toJSON [">", toJSON flds, toJSON val]+ toJSON (QLE flds val) = toJSON ["<=", toJSON flds, toJSON val]+ toJSON (QGE flds val) = toJSON [">=", toJSON flds, toJSON val]+ toJSON QEmpty = Null -instance ToJSON a => ToHttpApiData (Query a) where+instance (ToJSON a) => ToHttpApiData (Query a) where toHeader = Control.Lens.view strict . encode toUrlPiece = decodeUtf8 . toHeader -instance FromJSON a => FromJSON (Query a) where+instance (FromJSON a) => FromJSON (Query a) where parseJSON Null = pure QEmpty parseJSON (Array elems) = case toList elems of- ("or":xs) -> QOr <$> mapM parseJSON xs- ("and":xs) -> QAnd <$> mapM parseJSON xs- ["not",x] -> QNot <$> parseJSON x- [ "=", flds, val ] -> QEqual <$> parseJSON flds <*> parseJSON val- [ "~", flds, val ] -> QEqual <$> parseJSON flds <*> parseJSON val- [ ">", flds, val ] -> QG <$> parseJSON flds <*> parseJSON val- [ "<", flds, val ] -> QL <$> parseJSON flds <*> parseJSON val- [">=", flds, val ] -> QGE <$> parseJSON flds <*> parseJSON val- ["<=", flds, val ] -> QLE <$> parseJSON flds <*> parseJSON val- x -> fail ("unknown query" ++ show x)+ ("or" : xs) -> QOr <$> mapM parseJSON xs+ ("and" : xs) -> QAnd <$> mapM parseJSON xs+ ["not", x] -> QNot <$> parseJSON x+ ["=", flds, val] -> QEqual <$> parseJSON flds <*> parseJSON val+ ["~", flds, val] -> QEqual <$> parseJSON flds <*> parseJSON val+ [">", flds, val] -> QG <$> parseJSON flds <*> parseJSON val+ ["<", flds, val] -> QL <$> parseJSON flds <*> parseJSON val+ [">=", flds, val] -> QGE <$> parseJSON flds <*> parseJSON val+ ["<=", flds, val] -> QLE <$> parseJSON flds <*> parseJSON val+ x -> fail ("unknown query" ++ show x) parseJSON _ = fail "Expected an array" -- | Fields for the fact endpoint@@ -117,27 +122,27 @@ | FCertname instance ToJSON FactField where- toJSON FName = "name"- toJSON FValue = "value"+ toJSON FName = "name"+ toJSON FValue = "value" toJSON FCertname = "certname" instance FromJSON FactField where- parseJSON "name" = pure FName- parseJSON "value" = pure FValue+ parseJSON "name" = pure FName+ parseJSON "value" = pure FValue parseJSON "certname" = pure FCertname- parseJSON _ = fail "Can't parse fact field"+ parseJSON _ = fail "Can't parse fact field" -- | Fields for the node endpoint data NodeField = NName | NFact Text instance ToJSON NodeField where- toJSON NName = "name"- toJSON (NFact t) = toJSON [ "fact", t ]+ toJSON NName = "name"+ toJSON (NFact t) = toJSON ["fact", t] instance FromJSON NodeField where parseJSON (Array xs) = case toList xs of- ["fact", x] -> NFact <$> parseJSON x- _ -> fail "Invalid field syntax"+ ["fact", x] -> NFact <$> parseJSON x+ _ -> fail "Invalid field syntax" parseJSON (String "name") = pure NName parseJSON _ = fail "invalid field" @@ -153,37 +158,43 @@ | RLine instance ToJSON ResourceField where- toJSON RTag = "tag"- toJSON RCertname = "certname"+ toJSON RTag = "tag"+ toJSON RCertname = "certname" toJSON (RParameter t) = toJSON ["parameter", t]- toJSON RType = "type"- toJSON RTitle = "title"- toJSON RExported = "exported"- toJSON RFile = "file"- toJSON RLine = "line"+ toJSON RType = "type"+ toJSON RTitle = "title"+ toJSON RExported = "exported"+ toJSON RFile = "file"+ toJSON RLine = "line" instance FromJSON ResourceField where parseJSON (Array xs) = case toList xs of ["parameter", x] -> RParameter <$> parseJSON x- _ -> fail "Invalid field syntax"- parseJSON (String "tag" ) = pure RTag+ _ -> fail "Invalid field syntax"+ parseJSON (String "tag") = pure RTag parseJSON (String "certname") = pure RCertname- parseJSON (String "type" ) = pure RType- parseJSON (String "title" ) = pure RTitle+ parseJSON (String "type") = pure RType+ parseJSON (String "title") = pure RTitle parseJSON (String "exported") = pure RExported- parseJSON (String "file" ) = pure RFile- parseJSON (String "line" ) = pure RLine+ parseJSON (String "file") = pure RFile+ parseJSON (String "line") = pure RLine parseJSON _ = fail "invalid field" data PuppetDBAPI m = PuppetDBAPI- { pdbInformation :: m Doc- , replaceCatalog :: WireCatalog -> ExceptT PrettyError m () -- ^ <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#replace-catalog-version-3>- , replaceFacts :: [(NodeName, Facts)] -> ExceptT PrettyError m () -- ^ <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#replace-facts-version-1>- , deactivateNode :: NodeName -> ExceptT PrettyError m () -- ^ <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#deactivate-node-version-1>- , getPDBFacts :: Query FactField -> ExceptT PrettyError m [FactInfo] -- ^ <http://docs.puppetlabs.com/puppetdb/1.5/api/query/v3/facts.html#get-v3facts>- , getResources :: Query ResourceField -> ExceptT PrettyError m [Resource] -- ^ <http://docs.puppetlabs.com/puppetdb/1.5/api/query/v3/resources.html#get-v3resources>- , getNodes :: Query NodeField -> ExceptT PrettyError m [NodeInfo]- , commitDB :: ExceptT PrettyError m () -- ^ This is only here to tell the test PuppetDB to save its content to disk.- , getResourcesOfNode :: NodeName -> Query ResourceField -> ExceptT PrettyError m [Resource]+ { pdbInformation :: m Doc,+ -- | <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#replace-catalog-version-3>+ replaceCatalog :: WireCatalog -> ExceptT PrettyError m (),+ -- | <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#replace-facts-version-1>+ replaceFacts :: [(NodeName, Facts)] -> ExceptT PrettyError m (),+ -- | <http://docs.puppetlabs.com/puppetdb/1.5/api/commands.html#deactivate-node-version-1>+ deactivateNode :: NodeName -> ExceptT PrettyError m (),+ -- | <http://docs.puppetlabs.com/puppetdb/1.5/api/query/v3/facts.html#get-v3facts>+ getPDBFacts :: Query FactField -> ExceptT PrettyError m [FactInfo],+ -- | <http://docs.puppetlabs.com/puppetdb/1.5/api/query/v3/resources.html#get-v3resources>+ getResources :: Query ResourceField -> ExceptT PrettyError m [Resource],+ getNodes :: Query NodeField -> ExceptT PrettyError m [NodeInfo],+ -- | This is only here to tell the test PuppetDB to save its content to disk.+ commitDB :: ExceptT PrettyError m (),+ getResourcesOfNode :: NodeName -> Query ResourceField -> ExceptT PrettyError m [Resource] }
src/PuppetDB/Remote.hs view
@@ -1,25 +1,25 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-} {-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE TypeOperators #-}-module PuppetDB.Remote (pdbConnect) where--import XPrelude+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} -import Network.HTTP.Client (Manager)-import Servant.API-import Servant.Client+module PuppetDB.Remote (pdbConnect) where -import Facter-import Puppet.Language-import PuppetDB.Core+import Facter+import Network.HTTP.Client (Manager)+import Puppet.Language+import PuppetDB.Core+import Servant.API+import Servant.Client+import XPrelude -type PDBAPIv3 = "nodes" :> QueryParam "query" (Query NodeField) :> Get '[JSON] [NodeInfo]- :<|> "nodes" :> Capture "resourcename" Text :> "resources" :> QueryParam "query" (Query ResourceField) :> Get '[JSON] [Resource]- :<|> "facts" :> QueryParam "query" (Query FactField) :> Get '[JSON] [FactInfo]- :<|> "resources" :> QueryParam "query" (Query ResourceField) :> Get '[JSON] [Resource]+type PDBAPIv3 =+ "nodes" :> QueryParam "query" (Query NodeField) :> Get '[JSON] [NodeInfo]+ :<|> "nodes" :> Capture "resourcename" Text :> "resources" :> QueryParam "query" (Query ResourceField) :> Get '[JSON] [Resource]+ :<|> "facts" :> QueryParam "query" (Query FactField) :> Get '[JSON] [FactInfo]+ :<|> "resources" :> QueryParam "query" (Query ResourceField) :> Get '[JSON] [Resource] type PDBAPI = "v3" :> PDBAPIv3 @@ -40,22 +40,24 @@ pdbConnect mgr url = do url' <- parseBaseUrl url let env = mkClientEnv mgr url'- pure $ Right $ PuppetDBAPI- (return (ppline $ fromString url))- (const (throwError "operation not supported"))- (const (throwError "operation not supported"))- (const (throwError "operation not supported"))- (\q -> prettyError $ runClientM (sgetFacts (Just q)) env)- (\q -> prettyError $ runClientM (sgetResources (Just q)) env)- (\q -> prettyError $ runClientM (sgetNodes (Just q)) env)- (throwError "operation not supported")- (\node q -> prettyError $ runClientM (sgetNodeResources node (Just q)) env)+ pure $+ Right $+ PuppetDBAPI+ (return (ppline $ fromString url))+ (const (throwError "operation not supported"))+ (const (throwError "operation not supported"))+ (const (throwError "operation not supported"))+ (\q -> prettyError $ runClientM (sgetFacts (Just q)) env)+ (\q -> prettyError $ runClientM (sgetResources (Just q)) env)+ (\q -> prettyError $ runClientM (sgetNodes (Just q)) env)+ (throwError "operation not supported")+ (\node q -> prettyError $ runClientM (sgetNodeResources node (Just q)) env) where- sgetNodes :: Maybe (Query NodeField) -> ClientM [NodeInfo]- sgetNodeResources :: Text -> Maybe (Query ResourceField) -> ClientM [Resource]- sgetFacts :: Maybe (Query FactField) -> ClientM [FactInfo]- sgetResources :: Maybe (Query ResourceField) -> ClientM [Resource]- (sgetNodes :<|> sgetNodeResources :<|> sgetFacts :<|> sgetResources) = client api+ sgetNodes :: Maybe (Query NodeField) -> ClientM [NodeInfo]+ sgetNodeResources :: Text -> Maybe (Query ResourceField) -> ClientM [Resource]+ sgetFacts :: Maybe (Query FactField) -> ClientM [FactInfo]+ sgetResources :: Maybe (Query ResourceField) -> ClientM [Resource]+ (sgetNodes :<|> sgetNodeResources :<|> sgetFacts :<|> sgetResources) = client api - prettyError :: IO (Either ClientError b) -> ExceptT PrettyError IO b- prettyError = ExceptT . fmap (_Left %~ PrettyError . pplines . show)+ prettyError :: IO (Either ClientError b) -> ExceptT PrettyError IO b+ prettyError = ExceptT . fmap (_Left %~ PrettyError . pplines . show)
src/PuppetDB/TestDB.hs view
@@ -1,35 +1,33 @@ {-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell #-} -- | A stub implementation of PuppetDB, backed by a YAML file. module PuppetDB.TestDB- ( loadTestDB- , initTestDB-) where--import XPrelude--import Control.Concurrent.STM-import Data.Aeson-import qualified Data.CaseInsensitive as CaseInsensitive-import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as HS-import qualified Data.Maybe.Strict as S-import qualified Data.Text as Text-import qualified Data.Vector as V-import Data.Yaml (ParseException (..), YamlException (..), YamlMark(..))-import qualified Data.Yaml as Yaml-import Text.Megaparsec.Pos+ ( loadTestDB,+ initTestDB,+ )+where -import Facter-import Puppet.Language-import PuppetDB.Core+import Control.Concurrent.STM+import Data.Aeson hiding (AesonException)+import qualified Data.CaseInsensitive as CaseInsensitive+import qualified Data.HashMap.Strict as HM+import qualified Data.HashSet as HS+import qualified Data.Maybe.Strict as S+import qualified Data.Text as Text+import qualified Data.Vector as V+import Data.Yaml (ParseException (..), YamlException (..), YamlMark (..))+import qualified Data.Yaml as Yaml+import Facter+import Puppet.Language+import PuppetDB.Core+import Text.Megaparsec.Pos+import XPrelude data DBContent = DBContent- { _dbcontentResources :: Container WireCatalog- , _dbcontentFacts :: Container Facts- , _dbcontentBackingFile :: Maybe FilePath+ { _dbcontentResources :: Container WireCatalog,+ _dbcontentFacts :: Container Facts,+ _dbcontentBackingFile :: Maybe FilePath } makeLensesWith abbreviatedFields ''DBContent@@ -38,7 +36,7 @@ instance FromJSON DBContent where parseJSON (Object v) = DBContent <$> v .: "resources" <*> v .: "facts" <*> pure Nothing- parseJSON _ = mempty+ parseJSON _ = mempty instance ToJSON DBContent where toJSON (DBContent r f _) = object [("resources", toJSON r), ("facts", toJSON f)]@@ -49,16 +47,17 @@ Yaml.decodeFileEither fp >>= \case Left (OtherParseException rr) -> return (Left (PrettyError (pplines (show rr)))) Left (InvalidYaml Nothing) -> baseError "Unknown error"- Left (InvalidYaml (Just (YamlException s))) -> if take 21 s == "Yaml file not found: "- then newFile- else baseError (ppstring s)+ Left (InvalidYaml (Just (YamlException s))) ->+ if take 21 s == "Yaml file not found: "+ then newFile+ else baseError (ppstring s) Left (InvalidYaml (Just (YamlParseException pb ctx (YamlMark _ l c)))) -> baseError $ red (ppstring pb <+> ppstring ctx) <+> "at line" <+> pretty l <> ", column" <+> pretty c Left (AesonException e) -> baseError (fromString e) Left _ -> newFile- Right x -> fmap Right (genDBAPI (x & backingFile ?~ fp ))+ Right x -> fmap Right (genDBAPI (x & backingFile ?~ fp)) where baseError r = return $ Left $ PrettyError $ "Could not parse" <+> pptext fp <> ":" <+> r- newFile = Right <$> genDBAPI (newDB & backingFile ?~ fp )+ newFile = Right <$> genDBAPI (newDB & backingFile ?~ fp) -- | Starts a new PuppetDB, without any backing file. initTestDB :: IO (PuppetDBAPI IO)@@ -70,16 +69,17 @@ genDBAPI :: DBContent -> IO (PuppetDBAPI IO) genDBAPI db = do d <- newTVarIO db- return $! PuppetDBAPI (dbapiInfo d)- (replCat d)- (replFacts d)- (deactivate d)- (getFcts d)- (getRes d)- (getNds d)- (commit d)- (getResNode d)-+ return $!+ PuppetDBAPI+ (dbapiInfo d)+ (replCat d)+ (replFacts d)+ (deactivate d)+ (getFcts d)+ (getRes d)+ (getNds d)+ (commit d)+ (getResNode d) data Extracted = EText Text@@ -91,101 +91,105 @@ resolveQuery f (QEqual a t) = \v -> case f a v of EText tt -> CaseInsensitive.mk tt == CaseInsensitive.mk t- ESet ss -> ss ^. contains t- _ -> False-resolveQuery f (QNot q) = not . resolveQuery f q-resolveQuery f (QG a i) = ncompare (>) f a i-resolveQuery f (QL a i) = ncompare (<) f a i+ ESet ss -> ss ^. contains t+ _ -> False+resolveQuery f (QNot q) = not . resolveQuery f q+resolveQuery f (QG a i) = ncompare (>) f a i+resolveQuery f (QL a i) = ncompare (<) f a i resolveQuery f (QGE a i) = ncompare (>=) f a i resolveQuery f (QLE a i) = ncompare (<=) f a i resolveQuery _ (QMatch _ _) = const False resolveQuery f (QAnd qs) = \v -> all (\q -> resolveQuery f q v) qs-resolveQuery f (QOr qs) = \v -> any (\q -> resolveQuery f q v) qs+resolveQuery f (QOr qs) = \v -> any (\q -> resolveQuery f q v) qs dbapiInfo :: DB -> IO Doc dbapiInfo db = do c <- readTVarIO db case c ^. backingFile of Nothing -> return "TestDB"- Just v -> return ("TestDB" <+> ppstring v)+ Just v -> return ("TestDB" <+> ppstring v) -ncompare :: (Integer -> Integer -> Bool) -> (a -> b -> Extracted) -> a -> Integer -> b -> Bool+ncompare :: (Integer -> Integer -> Bool) -> (a -> b -> Extracted) -> a -> Integer -> b -> Bool ncompare operation f a i v = case f a v of EText tt -> case PString tt ^? _PValueInteger of- Just ii -> operation i ii- _ -> False+ Just ii -> operation i ii+ _ -> False _ -> False replCat :: DB -> WireCatalog -> ExceptT PrettyError IO () replCat db wc = liftIO $ atomically $ modifyTVar db (resources . at (wc ^. wireCatalogNodename) ?~ wc) replFacts :: DB -> [(NodeName, Facts)] -> ExceptT PrettyError IO ()-replFacts db lst = liftIO $ atomically $ modifyTVar db $- facts %~ (\r -> foldl' (\curr (n,f) -> curr & at n ?~ f) r lst)+replFacts db lst =+ liftIO $+ atomically $+ modifyTVar db $+ facts %~ (\r -> foldl' (\curr (n, f) -> curr & at n ?~ f) r lst) deactivate :: DB -> NodeName -> ExceptT PrettyError IO ()-deactivate db n = liftIO $ atomically $ modifyTVar db $- (resources . at n .~ Nothing) . (facts . at n .~ Nothing)+deactivate db n =+ liftIO $+ atomically $+ modifyTVar db $+ (resources . at n .~ Nothing) . (facts . at n .~ Nothing) getFcts :: DB -> Query FactField -> ExceptT PrettyError IO [FactInfo] getFcts db f = fmap (filter (resolveQuery factQuery f) . toFactInfo) (liftIO $ readTVarIO db)- where- toFactInfo :: DBContent -> [FactInfo]- toFactInfo = concatMap gf . HM.toList . _dbcontentFacts- where- gf (k,n) = do- (fn,fv) <- HM.toList n- return $ FactInfo k fn fv- factQuery :: FactField -> FactInfo -> Extracted- factQuery t = EText . view l- where- l = case t of- FName -> factInfoName- FValue -> factInfoVal . _PString- FCertname -> factInfoNodename+ where+ toFactInfo :: DBContent -> [FactInfo]+ toFactInfo = concatMap gf . HM.toList . _dbcontentFacts+ where+ gf (k, n) = do+ (fn, fv) <- HM.toList n+ return $ FactInfo k fn fv+ factQuery :: FactField -> FactInfo -> Extracted+ factQuery t = EText . view l+ where+ l = case t of+ FName -> factInfoName+ FValue -> factInfoVal . _PString+ FCertname -> factInfoNodename resourceQuery :: ResourceField -> Resource -> Extracted resourceQuery RTag r = r ^. rtags . to ESet resourceQuery RCertname r = r ^. rnode . to EText-resourceQuery (RParameter p) r = case r ^? rattributes . ix p . _PString of- Just s -> EText s- Nothing -> ENil+resourceQuery (RParameter p) r = maybe ENil EText (r ^? rattributes . ix p . _PString) resourceQuery RType r = r ^. rid . itype . to EText resourceQuery RTitle r = r ^. rid . iname . to EText-resourceQuery RExported r = if r ^. rvirtuality == Exported- then EText "true"- else EText "false"+resourceQuery RExported r =+ if r ^. rvirtuality == Exported+ then EText "true"+ else EText "false" resourceQuery RFile r = r ^. rpos . _1 . to sourceName . to Text.pack . to EText resourceQuery RLine r = r ^. rpos . _1 . to sourceLine . to show . to Text.pack . to EText getRes :: DB -> Query ResourceField -> ExceptT PrettyError IO [Resource] getRes db f = fmap (filter (resolveQuery resourceQuery f) . toResources) (liftIO $ readTVarIO db)- where- toResources :: DBContent -> [Resource]- toResources = concatMap (V.toList . view wireCatalogResources) . HM.elems . view resources+ where+ toResources :: DBContent -> [Resource]+ toResources = concatMap (V.toList . view wireCatalogResources) . HM.elems . view resources getResNode :: DB -> NodeName -> Query ResourceField -> ExceptT PrettyError IO [Resource] getResNode db nn f = do- c <- liftIO $ readTVarIO db- case c ^. resources . at nn of- Just cnt -> return $ filter (resolveQuery resourceQuery f) $ V.toList $ cnt ^. wireCatalogResources- Nothing -> throwError "Unknown node"+ c <- liftIO $ readTVarIO db+ case c ^. resources . at nn of+ Just cnt -> return $ filter (resolveQuery resourceQuery f) $ V.toList $ cnt ^. wireCatalogResources+ Nothing -> throwError "Unknown node" commit :: DB -> ExceptT PrettyError IO () commit db = do- dbc <- liftIO $ atomically $ readTVar db- case dbc ^. backingFile of- Nothing -> throwError "No backing file defined"- Just bf -> liftIO (Yaml.encodeFile bf dbc `catches` [ ])+ dbc <- liftIO $ readTVarIO db+ case dbc ^. backingFile of+ Nothing -> throwError "No backing file defined"+ Just bf -> liftIO (Yaml.encodeFile bf dbc `catches` []) getNds :: DB -> Query NodeField -> ExceptT PrettyError IO [NodeInfo] getNds db QEmpty = fmap toNodeInfo (liftIO $ readTVarIO db)- where- toNodeInfo :: DBContent -> [NodeInfo]- toNodeInfo = fmap g . HM.keys . _dbcontentFacts- where- g :: NodeName -> NodeInfo- g = \n -> NodeInfo n False S.Nothing S.Nothing S.Nothing-+ where+ toNodeInfo :: DBContent -> [NodeInfo]+ toNodeInfo = fmap g . HM.keys . _dbcontentFacts+ where+ g :: NodeName -> NodeInfo+ g n = NodeInfo n False S.Nothing S.Nothing S.Nothing getNds _ _ = throwError "getNds with query not implemented"
src/XPrelude.hs view
@@ -1,11 +1,11 @@-{-|-General specific prelude for language-puppet.-Customization of the <https://hackage.haskell.org/package/protolude Protolude> with extra specific utilities.--}+-- |+-- General specific prelude for language-puppet.+-- Customization of the <https://hackage.haskell.org/package/protolude Protolude> with extra specific utilities. module XPrelude- ( module XPrelude.Extra- , module XPrelude.PP- ) where+ ( module XPrelude.Extra,+ module XPrelude.PP,+ )+where import XPrelude.Extra import XPrelude.PP hiding (width, (</>))
src/XPrelude/Extra.hs view
@@ -1,72 +1,101 @@-{-# OPTIONS_HADDOCK ignore-exports #-} {-# LANGUAGE RankNTypes #-}-module XPrelude.Extra (- module Exports- , String- , Container- , unwrapError- , dropInitialColons- , strictifyEither- , scientific2text- , text2Scientific- , ifromList, ikeys, ifromListWith, iunionWith, iinsertWith- -- * Logger- , loggerName- , logDebug- , logDebugStr- , logInfo- , logInfoStr- , logWarning- , logWarningStr- , logError- , logErrorStr- , logCritical- , logCriticalStr- -- * Lenses for json- , key- , _String- , avalues- , nth-) where+{-# OPTIONS_HADDOCK ignore-exports #-} -import Protolude as Exports hiding (Down, Infix, Prefix, Selector,- State, StateT, Strict, break, check,- evalState, evalStateT, execState,- execStateT, from, hash, list,- moduleName, runState, runStateT,- sourceColumn, sourceLine, to, typeOf,- uncons, unsnoc, withState, (%), (<&>),- (<.>))+module XPrelude.Extra+ ( module Exports,+ String,+ Container,+ unwrapError,+ dropInitialColons,+ strictifyEither,+ scientific2text,+ text2Scientific,+ ifromList,+ ikeys,+ ifromListWith,+ iunionWith,+ iinsertWith, -import Control.Exception.Lens as Exports (catching)-import Control.Lens as Exports hiding (Strict, argument, noneOf, op,- (<.>))-import Control.Monad as Exports (fail)-import Control.Monad.Trans.Except as Exports (catchE, except, throwE)-import Control.Monad.Trans.Maybe as Exports (runMaybeT)-import Data.Aeson as Exports (FromJSON, ToJSON, fromJSON, toJSON)-import Data.Aeson (Value (..))-import Data.HashMap.Strict as Exports (HashMap)-import Data.HashSet as Exports (HashSet)-import Data.Scientific as Exports (Scientific)-import Data.Set as Exports (Set)-import Data.String as Exports (IsString (..))-import Data.Tuple.Strict as Exports (Pair (..))-import Data.Vector as Exports (Vector)-import Text.Regex.PCRE.ByteString.Utils as Exports (Regex)+ -- * Logger+ loggerName,+ logDebug,+ logDebugStr,+ logInfo,+ logInfoStr,+ logWarning,+ logWarningStr,+ logError,+ logErrorStr,+ logCritical,+ logCriticalStr, -import Data.Attoparsec.Text (parseOnly, rational)-import qualified Data.Either.Strict as S-import qualified Data.HashMap.Strict as Map-import qualified Data.HashMap.Strict as HM-import qualified Data.HashSet as HS-import qualified Data.Scientific as Scientific-import Data.String (String)-import qualified Data.Text as Text-import qualified Data.Vector as V-import qualified System.Log.Logger as Log-import XPrelude.PP+ -- * Lenses for json+ avalues,+ nth,+ )+where +import Control.Exception.Lens as Exports (catching)+import Control.Lens as Exports hiding+ ( Strict,+ argument,+ noneOf,+ op,+ (<.>),+ )+import Control.Monad as Exports (fail)+import Control.Monad.Trans.Except as Exports (except)+import Control.Monad.Trans.Maybe as Exports (runMaybeT)+import Data.Aeson (Value (..))+import Data.Aeson as Exports (FromJSON, ToJSON, fromJSON, toJSON)+import Data.Aeson.Lens as Exports (key, _Array, _Object, _String)+import Data.Attoparsec.Text (parseOnly, rational)+import qualified Data.Either.Strict as S+import Data.HashMap.Strict as Exports (HashMap)+import qualified Data.HashMap.Strict as Map+import Data.HashSet as Exports (HashSet)+import qualified Data.HashSet as HS+import Data.Scientific as Exports (Scientific)+import qualified Data.Scientific as Scientific+import Data.String (String)+import Data.String as Exports (IsString (..))+import qualified Data.Text as Text+import Data.Tuple.Strict as Exports (Pair (..))+import Data.Vector as Exports (Vector)+import Protolude as Exports hiding+ ( Down,+ Infix,+ Prefix,+ Selector,+ State,+ StateT,+ break,+ check,+ evalState,+ evalStateT,+ execState,+ execStateT,+ from,+ hash,+ list,+ moduleName,+ runState,+ runStateT,+ sourceColumn,+ sourceLine,+ to,+ typeOf,+ uncons,+ unsnoc,+ withState,+ (%),+ (<&>),+ (<.>),+ )+import qualified System.Log.Logger as Log+import Text.Regex.PCRE.ByteString.Utils as Exports (Regex)+import XPrelude.PP+ type Container = Map.HashMap Text text2Scientific :: Text -> Maybe Scientific@@ -75,37 +104,37 @@ scientific2text :: Scientific -> Text scientific2text n = case Scientific.floatingOrInteger n of- Left r -> show (r :: Double)+ Left r -> show (r :: Double) Right i -> show (i :: Integer) strictifyEither :: Either a b -> S.Either a b-strictifyEither (Left x) = S.Left x+strictifyEither (Left x) = S.Left x strictifyEither (Right x) = S.Right x -- | Helper for hashmap, in case we want another kind of map. ifromList :: (Monoid m, At m, Foldable f) => f (Index m, IxValue m) -> m-{-# INLINABLE ifromList #-}-ifromList = foldl' (\curm (k,v) -> curm & at k ?~ v) mempty+{-# INLINEABLE ifromList #-}+ifromList = foldl' (\curm (k, v) -> curm & at k ?~ v) mempty -- | Return all the keys of a map in a set. ikeys :: (Eq k, Hashable k) => HashMap k v -> HS.HashSet k-{-# INLINABLE ikeys #-}+{-# INLINEABLE ikeys #-} ikeys = HS.fromList . Map.keys ifromListWith :: (Monoid m, At m, Foldable f) => (IxValue m -> IxValue m -> IxValue m) -> f (Index m, IxValue m) -> m-{-# INLINABLE ifromListWith #-}-ifromListWith f = foldl' (\curmap (k,v) -> iinsertWith f k v curmap) mempty+{-# INLINEABLE ifromListWith #-}+ifromListWith f = foldl' (\curmap (k, v) -> iinsertWith f k v curmap) mempty -iinsertWith :: At m => (IxValue m -> IxValue m -> IxValue m) -> Index m -> IxValue m -> m -> m-{-# INLINABLE iinsertWith #-}+iinsertWith :: (At m) => (IxValue m -> IxValue m -> IxValue m) -> Index m -> IxValue m -> m -> m+{-# INLINEABLE iinsertWith #-} iinsertWith f k v m = m & at k %~ mightreplace where- mightreplace Nothing = Just v+ mightreplace Nothing = Just v mightreplace (Just x) = Just (f v x) iunionWith :: (Hashable k, Eq k) => (v -> v -> v) -> HashMap k v -> HashMap k v -> HashMap k v-{-# INLINABLE iunionWith #-}+{-# INLINEABLE iunionWith #-} iunionWith = Map.unionWith -- | Remove the '::' token from a text if any.@@ -148,28 +177,9 @@ -- | In case of a Left error, print and exit immediately. unwrapError :: Doc -> Either PrettyError a -> IO a unwrapError desc = either exit pure- where- exit = \err -> putDoc (display err) >> exitFailure- display err = red desc <> ":" <+> getError err--key :: Text -> Traversal' Value Value-key k f v =- case v of- Object o ->- fmap Object (HM.alterF (traverse f) k o)- _ -> pure v--_String :: Prism' Value Text-_String = prism String $- \v -> case v of- String x -> Right x- _ -> Left v--_Array :: Prism' Value (V.Vector Value)-_Array = prism Array $- \v -> case v of- Array x -> Right x- _ -> Left v+ where+ exit err = putDoc (display err) >> exitFailure+ display err = red desc <> ":" <+> getError err avalues :: IndexedTraversal' Int Value Value avalues = _Array . traversed
src/XPrelude/PP.hs view
@@ -1,33 +1,43 @@-{-# OPTIONS_HADDOCK ignore-exports #-}-{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE CPP #-}-module XPrelude.PP (- module Exports- , PrettyError (..)- , ppline- , pplines- , pptext- , ppstring-)-where--import Protolude+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-# OPTIONS_HADDOCK ignore-exports #-} -import Data.Scientific-import Data.Semigroup as Sem-import Data.String-import qualified Data.Text as Text-import Text.PrettyPrint.ANSI.Leijen as Exports hiding (bool, cat,- char, double, empty,- float, group, int,- integer, rational,- string, text, (<$>))+module XPrelude.PP+ ( module Exports,+ PrettyError (..),+ ppline,+ pplines,+ pptext,+ ppstring,+ )+where -import Text.PrettyPrint.ANSI.Leijen (string, text)+import Data.Scientific+import Data.Semigroup as Sem+import Data.String+import qualified Data.Text as Text+import Protolude+import Text.PrettyPrint.ANSI.Leijen (string, text)+import Text.PrettyPrint.ANSI.Leijen as Exports hiding+ ( bool,+ cat,+ char,+ double,+ empty,+ float,+ group,+ int,+ integer,+ rational,+ string,+ text,+ (<$>),+ ) newtype PrettyError = PrettyError { getError :: Doc- } deriving Show+ }+ deriving (Show) instance Sem.Semigroup PrettyError where a <> b = PrettyError $ align (vsep [getError a, getError b])@@ -46,7 +56,6 @@ instance Pretty Scientific where pretty = text . show- -- | pretty print multiple lines of text. pplines :: Text -> Doc
tests/ErbSpec.hs view
@@ -1,75 +1,96 @@-module ErbSpec(spec) where--import XPrelude--import Test.Hspec+module ErbSpec (spec) where -import Erb-import Puppet.Runner+import Erb+import GHC.Base (error)+import Puppet.Runner+import Test.Hspec+import XPrelude parsingtests :: [(String, [RubyStatement])] parsingtests =- [ ("port = 5432", [ Puts (Value (Literal "port = 5432" ))])- , ("mode = host=<%= @var %>", [ Puts (Value (Literal "mode = host="))- , Puts (Object (Value (Literal "@var")))- , Puts (Value (Literal ""))])- , ("mode = host=<%= var %>", [ Puts (Value (Literal "mode = host="))- , Puts (Object (Value (Literal "var")))- , Puts (Value (Literal ""))])- , ("<%= @os['architecture'] %>", [ Puts (Value (Literal ""))- , Puts (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "architecture")))- , Puts (Value (Literal ""))])- , ("<%= @os['release']['major'] %>", [ Puts (Value (Literal ""))- , Puts (LookupOperation (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "release"))) (Value (Literal "major")))- , Puts (Value (Literal ""))])- , ("<%= @processors['models'] %>", [ Puts (Value (Literal ""))- , Puts (LookupOperation (Object (Value (Literal "@processors"))) (Value (Literal "models")))- , Puts (Value (Literal ""))])- , ("<%= scope.lookupvar('::fqdn') %>", [ Puts (Value (Literal ""))- , Puts (ScopeObject (Value (Literal "::fqdn")))- , Puts(Value (Literal ""))])- , ("<%= scope.lookupvar(\"::fqdn\") %>", [ Puts (Value (Literal ""))- , Puts (ScopeObject (Value (Literal "::fqdn")))- , Puts(Value (Literal ""))])+ [ ("port = 5432", [Puts (Value (Literal "port = 5432"))]),+ ( "mode = host=<%= @var %>",+ [ Puts (Value (Literal "mode = host=")),+ Puts (Object (Value (Literal "@var"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "mode = host=<%= var %>",+ [ Puts (Value (Literal "mode = host=")),+ Puts (Object (Value (Literal "var"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "<%= @os['architecture'] %>",+ [ Puts (Value (Literal "")),+ Puts (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "architecture"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "<%= @os['release']['major'] %>",+ [ Puts (Value (Literal "")),+ Puts (LookupOperation (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "release"))) (Value (Literal "major"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "<%= @processors['models'] %>",+ [ Puts (Value (Literal "")),+ Puts (LookupOperation (Object (Value (Literal "@processors"))) (Value (Literal "models"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "<%= scope.lookupvar('::fqdn') %>",+ [ Puts (Value (Literal "")),+ Puts (ScopeObject (Value (Literal "::fqdn"))),+ Puts (Value (Literal ""))+ ]+ ),+ ( "<%= scope.lookupvar(\"::fqdn\") %>",+ [ Puts (Value (Literal "")),+ Puts (ScopeObject (Value (Literal "::fqdn"))),+ Puts (Value (Literal ""))+ ]+ ) ] resolvetests :: [([RubyStatement], Text)] resolvetests =- [ ([ Puts (Object (Value (Literal "@hostname")))]- , "dummy"- )- , ([ Puts (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "architecture")))]- , "amd64"- )- , ([ Puts (LookupOperation (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "release"))) (Value (Literal "major")))]- , "7"- )- , ([ Puts (LookupOperation (Object (Value (Literal "@processors"))) (Value (Literal "models")))]- , expectedmodels- )- , ([ Puts (ScopeObject (Value (Literal "::fqdn")))]- , "dummy.dummy.domain"+ [ ( [Puts (Object (Value (Literal "@hostname")))],+ "dummy"+ ),+ ( [Puts (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "architecture")))],+ "amd64"+ ),+ ( [Puts (LookupOperation (LookupOperation (Object (Value (Literal "@os"))) (Value (Literal "release"))) (Value (Literal "major")))],+ "7"+ ),+ ( [Puts (LookupOperation (Object (Value (Literal "@processors"))) (Value (Literal "models")))],+ expectedmodels+ ),+ ( [Puts (ScopeObject (Value (Literal "::fqdn")))],+ "dummy.dummy.domain" ) ]- where- expectedmodels = "[\"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\"]"+ where+ expectedmodels = "[\"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\", \"Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz\"]" parsingspec = for_ parsingtests $ \(s, e) ->- let item = it ("should parse " <> s) in- case parseErbString s of- Left err -> item $ expectationFailure (show err)- Right r -> item $ r `shouldBe` e+ let item = it ("should parse " <> s)+ in case parseErbString s of+ Left err -> item $ expectationFailure (show err)+ Right r -> item $ r `shouldBe` e resolvespec = let state0 = initialState dummyFacts mempty- Just (scope_name, scope) = extractScope state0- in- for_ resolvetests $ \(s, e) ->- let item = it ("should resolve " <> show s) in- case rubyEvaluate scope scope_name s of- Left err -> item $ expectationFailure (show err)- Right r -> item $ r `shouldBe` e+ (scope_name, scope) = case extractScope state0 of+ Just p -> p+ Nothing -> error "should not happen"+ in for_ resolvetests $ \(s, e) ->+ let item = it ("should resolve " <> show s)+ in case rubyEvaluate scope scope_name s of+ Left err -> item $ expectationFailure (show err)+ Right r -> item $ r `shouldBe` e spec = describe "Erb" $ do parsingspec
tests/Helpers.hs view
@@ -1,64 +1,63 @@ {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedLists #-}--module Helpers ( module Exports- , checkExprsSuccess- , checkExprsError- , pureCatalog- , getResource- , getAttribute- , renderToString- , withStdlibFunction- ) where+{-# LANGUAGE OverloadedLists #-} -import XPrelude as Exports+module Helpers+ ( module Exports,+ checkExprsSuccess,+ checkExprsError,+ pureCatalog,+ getResource,+ getAttribute,+ renderToString,+ withStdlibFunction,+ )+where -import Control.Monad.Fail as Exports (MonadFail) import qualified Data.HashMap.Strict as HM-import qualified Data.Maybe.Strict as S-import qualified Data.Vector as Vector-import Test.Hspec as Exports--import Puppet.Interpreter as Exports-import Puppet.Parser as Exports-import Puppet.Runner as Exports hiding (getCatalog)+import qualified Data.Maybe.Strict as S+import qualified Data.Vector as Vector+import Puppet.Interpreter as Exports+import Puppet.Parser as Exports+import Puppet.Runner as Exports hiding (getCatalog)+import Test.Hspec as Exports+import XPrelude as Exports -- | Given a raw text input to be parsed, compute the manifest in a pure setting. -- The 'InterpreterWriter' might be useful for debugging purpose.-pureCatalog :: Text -> Either String (FinalCatalog, InterpreterWriter)-pureCatalog = runExcept . fmap (\s -> (s^._1,s^._6)) . compileCatalog+pureCatalog :: Text -> Either String (FinalCatalog, InterpreterWriter)+pureCatalog = runExcept . fmap (\s -> (s ^. _1, s ^. _6)) . compileCatalog where- compileCatalog :: Text -> Except String (FinalCatalog, EdgeMap, FinalCatalog, [Resource], InterpreterState, InterpreterWriter)- compileCatalog input = do- statements <- either (throwError . show) pure (runPuppetParser mempty input)- let nodename = "pure"- top_node = [((TopNode, nodename), NodeDeclaration (NodeDecl (NodeName nodename) statements S.Nothing (initialPPos mempty)))]- (res, finalState, logs) = pureEval top_node (computeCatalog nodename)- (catalog, em, exported, defResources) <- either (throwError . show) pure res- pure (catalog, em, exported, defResources, finalState, logs)+ compileCatalog :: Text -> Except String (FinalCatalog, EdgeMap, FinalCatalog, [Resource], InterpreterState, InterpreterWriter)+ compileCatalog input = do+ statements <- either (throwError . show) pure (runPuppetParser mempty input)+ let nodename = "pure"+ top_node = [((TopNode, nodename), NodeDeclaration (NodeDecl (NodeName nodename) statements S.Nothing (initialPPos mempty)))]+ (res, finalState, logs) = pureEval top_node (computeCatalog nodename)+ (catalog, em, exported, defResources) <- either (throwError . show) pure res+ pure (catalog, em, exported, defResources, finalState, logs) getResource :: (MonadFail m) => RIdentifier -> FinalCatalog -> m Resource getResource resid catalog = maybe (fail ("Unknown resource " <> renderToString resid)) pure (HM.lookup resid catalog) -getAttribute :: MonadFail m => Text -> Resource -> m PValue+getAttribute :: (MonadFail m) => Text -> Resource -> m PValue getAttribute att res = case res ^? rattributes . ix att of Nothing -> fail ("Unknown attribute: " <> toS att)- Just x -> return x+ Just x -> return x -withStdlibFunction :: Text -> ( ([PValue] -> InterpreterMonad PValue) -> Spec ) -> Spec+withStdlibFunction :: Text -> (([PValue] -> InterpreterMonad PValue) -> Spec) -> Spec withStdlibFunction fname testsuite = case stdlibFunctions ^? ix fname of- Just f -> testsuite f+ Just f -> testsuite f Nothing -> panic ("Don't know this function: " <> fname) -checkExprsSuccess :: Text -> [Expression] -> Text -> Expectation+checkExprsSuccess :: Text -> [Expression] -> Text -> Expectation checkExprsSuccess fname args res = case evalExprs fname args of- Left rr -> expectationFailure (show rr)+ Left rr -> expectationFailure (show rr) Right res' -> res' `shouldBe` res -checkExprsError :: Text -> [Expression] -> String -> Expectation+checkExprsError :: Text -> [Expression] -> String -> Expectation checkExprsError fname args msg = case evalExprs fname args of Left rr -> show rr `shouldContain` msg@@ -68,7 +67,7 @@ evalExprs fname = dummyEval . resolveValue . UFunctionCall fname . Vector.fromList >=> \case PString s -> return s- v -> Left ("Expected a string, not " <> PrettyError (pretty v))+ v -> Left ("Expected a string, not " <> PrettyError (pretty v)) -renderToString :: Pretty a => a -> String+renderToString :: (Pretty a) => a -> String renderToString d = displayS (renderCompact (pretty d)) ""
tests/HieraSpec.hs view
@@ -1,14 +1,12 @@-module HieraSpec(spec) where--import XPrelude+module HieraSpec (spec) where import qualified Data.HashMap.Strict as HM-import qualified Data.Vector as Vector-import qualified System.Log.Logger as Log-import Test.Hspec--import Hiera.Server-import Puppet.Language+import qualified Data.Vector as Vector+import Hiera.Server+import Puppet.Language+import qualified System.Log.Logger as Log+import Test.Hspec+import XPrelude checkOutput :: (Eq a, Show rr, Show a) => a -> Either rr a -> Expectation checkOutput v (Right x) = x `shouldBe` v@@ -16,7 +14,7 @@ checkFail :: (Show a) => Either rr a -> Expectation checkFail (Right v) = expectationFailure ("Should have failed, but returned: " ++ show v)-checkFail _ = return ()+checkFail _ = return () fqdn :: Text fqdn = "node.com"@@ -34,19 +32,25 @@ configInterpolate = "./tests/hiera/interpolate/config/hiera.yaml" vars :: HM.HashMap Text PValue-vars = HM.fromList [ ("::environment", "production")- , ("::fqdn" , PString fqdn)- ]+vars =+ HM.fromList+ [ ("::environment", "production"),+ ("::fqdn", PString fqdn)+ ] users :: HM.HashMap Text PValue-users = HM.fromList [ ("pete", PHash (HM.singleton "uid" (PNumber 2000)))- , ("tom" , PHash (HM.singleton "uid" (PNumber 2001)))- ]+users =+ HM.fromList+ [ ("pete", PHash (HM.singleton "uid" (PNumber 2000))),+ ("tom", PHash (HM.singleton "uid" (PNumber 2001)))+ ] pusers :: HM.HashMap Text PValue-pusers = HM.fromList [ ("bob", PHash (HM.singleton "uid" (PNumber 100)))- , ("tom" , PHash (HM.singleton "uid" (PNumber 12)))- ]+pusers =+ HM.fromList+ [ ("bob", PHash (HM.singleton "uid" (PNumber 100))),+ ("tom", PHash (HM.singleton "uid" (PNumber 12)))+ ] hash :: [(Text, PValue)] -> PValue hash = PHash . HM.fromList@@ -65,11 +69,11 @@ interpolationSpec miscSpec describe "v5 lookup hierarchy" $- it "should override some values" $ do+ it "should override some values" $ do q5 vars "http_port" QFirst >>= checkOutput (Just (PNumber 9090)) q5 vars "global" QFirst >>= checkOutput (Just "glob") describe "v5 ~" $- it "should read '~' as a Null/Nothing value" $+ it "should read '~' as a Null/Nothing value" $ q5 vars "optional_value" QFirst >>= checkOutput (Just PUndef) describe "v3 lookup with no context variables" $ do it "should return nothing when called with an empty string" $@@ -83,7 +87,7 @@ it "should return common data" $ q3 mempty "http_port" QFirst >>= checkOutput (Just (PNumber 8080)) it "should return arrays" $- q3 mempty "ntp_servers" QFirst >>= checkOutput (Just (array ["0.ntp.puppetlabs.com","1.ntp.puppetlabs.com"]))+ q3 mempty "ntp_servers" QFirst >>= checkOutput (Just (array ["0.ntp.puppetlabs.com", "1.ntp.puppetlabs.com"])) it "should return hashes" $ q3 mempty "users" QFirst >>= checkOutput (Just (PHash users)) describe "v3 lookup hierarchy" $ do@@ -98,7 +102,7 @@ it "resolves in strings" $ q3 vars "interp1" QFirst >>= checkOutput (Just (PString ("**" <> fqdn <> "**"))) it "resolves in objects" $- q3 vars "testnode" QFirst >>= checkOutput (Just (hash [("1",PString ("**" <> fqdn <> "**")),("2",PString "nothing special")]))+ q3 vars "testnode" QFirst >>= checkOutput (Just (hash [("1", PString ("**" <> fqdn <> "**")), ("2", PString "nothing special")])) it "resolves in arrays" $ q3 vars "arraytest" QFirst >>= checkOutput (Just (array [PString "a", PString fqdn, PString "c"])) it "resolves aliases" $@@ -109,7 +113,7 @@ q3 vars "aliased_lookup" QFirst >>= checkOutput (Just (PNumber 100)) describe "v3 other merge modes" $ do it "catenates arrays" $- q3 vars "ntp_servers" QUnique >>= checkOutput (Just (array ["2.ntp.puppetlabs.com","3.ntp.puppetlabs.com","0.ntp.puppetlabs.com","1.ntp.puppetlabs.com"]))+ q3 vars "ntp_servers" QUnique >>= checkOutput (Just (array ["2.ntp.puppetlabs.com", "3.ntp.puppetlabs.com", "0.ntp.puppetlabs.com", "1.ntp.puppetlabs.com"])) it "puts single values in arrays" $ q3 vars "http_port" QUnique >>= checkOutput (Just (array [PNumber 9090, PNumber 8080])) it "merges hashes" $@@ -142,7 +146,6 @@ describe "when not finding value for interpolated key" $ it "should resolve the interpolation to an empty string" $ q mempty "niltest" QFirst >>= checkFail -- puppet behavior: checkOutput (Just "Missing key ##. Key with nil ##")- describe "when there are empty interpolations %{} in data" $ do it "should should produce an empty string for the interpolation" $ do pendingWith "Support empty interpolation"@@ -190,21 +193,27 @@ it "split dq" $ varSplitter "\"ab\"" `shouldBe` HieraVar ("ab" :| []) it "split dq 2" $ varSplitter "\"ab.cd\"" `shouldBe` HieraVar ("ab.cd" :| []) it "split dq 2 mixed" $ varSplitter "\"ab.cd\".ef" `shouldBe` HieraVar ("ab.cd" :| ["ef"])- it "split dq 2 mixed 4" $ varSplitter "\"ab.cd\".ef.\"lol.cat\".bar" `shouldBe`- HieraVar ("ab.cd" :| ["ef", "lol.cat", "bar"])+ it "split dq 2 mixed 4" $+ varSplitter "\"ab.cd\".ef.\"lol.cat\".bar"+ `shouldBe` HieraVar ("ab.cd" :| ["ef", "lol.cat", "bar"]) it "split sq" $ varSplitter "'ab'" `shouldBe` HieraVar ("ab" :| []) it "split sq 2" $ varSplitter "'ab.cd'" `shouldBe` HieraVar ("ab.cd" :| []) it "split sq 2 mixed" $ varSplitter "'ab.cd'.ef" `shouldBe` HieraVar ("ab.cd" :| ["ef"])- it "split sq 2 mixed 4" $ varSplitter "'ab.cd'.ef.'lol.cat'.bar" `shouldBe`- HieraVar ("ab.cd" :| ["ef", "lol.cat", "bar"])- it "split all mixed" $ varSplitter "'a.b'.\"c.d\".e.f" `shouldBe`- HieraVar ("a.b" :| ["c.d", "e", "f"])- it "function f()" $ varSplitter "f()" `shouldBe`- HieraFunction ("f" :| []) []- it "function f('a')" $ varSplitter "f('a')" `shouldBe`- HieraFunction ("f" :| []) ["a"]- it "function f('a', 'b', 'c')" $ varSplitter "f('a', 'b', 'c')" `shouldBe`- HieraFunction ("f" :| []) ["a", "b", "c"]+ it "split sq 2 mixed 4" $+ varSplitter "'ab.cd'.ef.'lol.cat'.bar"+ `shouldBe` HieraVar ("ab.cd" :| ["ef", "lol.cat", "bar"])+ it "split all mixed" $+ varSplitter "'a.b'.\"c.d\".e.f"+ `shouldBe` HieraVar ("a.b" :| ["c.d", "e", "f"])+ it "function f()" $+ varSplitter "f()"+ `shouldBe` HieraFunction ("f" :| []) []+ it "function f('a')" $+ varSplitter "f('a')"+ `shouldBe` HieraFunction ("f" :| []) ["a"]+ it "function f('a', 'b', 'c')" $+ varSplitter "f('a', 'b', 'c')"+ `shouldBe` HieraFunction ("f" :| []) ["a", "b", "c"] describe "when using dotted keys" $ do it "should find an entry using a quoted interpolation" $
tests/Interpreter/ClassSpec.hs view
@@ -2,31 +2,29 @@ module Interpreter.ClassSpec (spec) where -import Test.Hspec--import Control.Lens-import qualified Data.Text as Text--import Helpers+import Control.Lens+import qualified Data.Text as Text+import Helpers+import Test.Hspec spec = do describe "Class" $ do it "should work when using several include statements" $- pureCatalog (Text.unlines ["class foo {}", "include foo", "include foo"]) `shouldSatisfy` (has _Right)+ pureCatalog (Text.unlines ["class foo {}", "include foo", "include foo"]) `shouldSatisfy` has _Right it "should work when using class before include" $- pureCatalog (Text.unlines ["class foo {}", "class { 'foo': }", "include foo"]) `shouldSatisfy` (has _Right)+ pureCatalog (Text.unlines ["class foo {}", "class { 'foo': }", "include foo"]) `shouldSatisfy` has _Right it "should fail when using include before class" $- pureCatalog (Text.unlines ["class foo {}", "include foo", "class { 'foo': }" ]) `shouldSatisfy` (has _Left)+ pureCatalog (Text.unlines ["class foo {}", "include foo", "class { 'foo': }"]) `shouldSatisfy` has _Left it "should fail if the class is not defined" $- pureCatalog (Text.unlines ["include foo"]) `shouldSatisfy` (has _Left)+ pureCatalog (Text.unlines ["include foo"]) `shouldSatisfy` has _Left describe "Parameters" $ do it "should fail when declaring a class with an unknown params" $- pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': param1 => 1 }"]) `shouldSatisfy` (has _Left)+ pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': param1 => 1 }"]) `shouldSatisfy` has _Left it "should succeed when declaring a class with a correct param" $- pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': param0 => 1 }"]) `shouldSatisfy` (has _Right)- it "should fail when the type of the attribute is wrong" $ do- pureCatalog (Text.unlines ["class foo (String $param0){}", "class {'foo': param0 => 1 }"]) `shouldSatisfy` (has _Left)- it "should fail when declaring with a missing param" $ do- pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': }"]) `shouldSatisfy` (has _Left)- it "should succeed with missing param and an optional type" $ do- pureCatalog (Text.unlines ["class foo (Optional[String] $param0){}", "class {'foo': }"]) `shouldSatisfy` (has _Right)+ pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': param0 => 1 }"]) `shouldSatisfy` has _Right+ it "should fail when the type of the attribute is wrong" $+ pureCatalog (Text.unlines ["class foo (String $param0){}", "class {'foo': param0 => 1 }"]) `shouldSatisfy` has _Left+ it "should fail when declaring with a missing param" $+ pureCatalog (Text.unlines ["class foo ($param0){}", "class {'foo': }"]) `shouldSatisfy` has _Left+ it "should succeed with missing param and an optional type" $+ pureCatalog (Text.unlines ["class foo (Optional[String] $param0){}", "class {'foo': }"]) `shouldSatisfy` has _Right
tests/Interpreter/CollectorSpec.hs view
@@ -1,26 +1,25 @@-{-# LANGUAGE OverloadedLists #-}--- | Directly ported from puppet specs-module Interpreter.CollectorSpec (spec) where+{-# LANGUAGE OverloadedLists #-} -import Test.Hspec+-- | Directly ported from puppet specs+module Interpreter.CollectorSpec (spec) where -import Control.Lens+import Control.Lens import qualified Data.Text as Text--import Helpers-+import Helpers+import Test.Hspec shouldNotify :: [Text] -> [PValue] -> Expectation shouldNotify content expectedMessages = do- catalog <- case pureCatalog (Text.unlines content) of- Left rr -> fail rr- Right (x,_) -> pure x- let messages = itoList catalog ^.. folded . filtered (\rp -> rp ^. _1 . itype == "notify") . _2 . rattributes . ix "message"- messages `shouldMatchList` expectedMessages+ catalog <- case pureCatalog (Text.unlines content) of+ Left rr -> fail rr+ Right (x, _) -> pure x+ let messages = itoList catalog ^.. folded . filtered (\rp -> rp ^. _1 . itype == "notify") . _2 . rattributes . ix "message"+ messages `shouldMatchList` expectedMessages shouldFail :: [Text] -> Expectation-shouldFail content = let catalog = pureCatalog (Text.unlines content)- in catalog `shouldSatisfy` has _Left+shouldFail content =+ let catalog = pureCatalog (Text.unlines content)+ in catalog `shouldSatisfy` has _Left spec = describe "Collectors" $ do@@ -29,130 +28,154 @@ spec0 :: Spec spec0 = do- it "matches everything when no query given" $- [ "@notify { 'testing': message => 'the message' }"- , "@notify { 'other': message => 'the other message' }"- , "Notify <| |>"- ] `shouldNotify` ["the other message", "the message"]- it "matches regular resources" $- [ "notify { \"testing\": message => \"the message\" }"- , "notify { \"other\": message => \"the other message\" }"- , "Notify <| |> { message => \"changed\" }"- ] `shouldNotify` ["changed", "changed"]- it "matches on tags" $- [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }"- , "@notify { \"other\": tag => [\"two\"], message => \"unwanted\" }"- , "Notify <| tag == one |>"- ] `shouldNotify` ["wanted"]- it "matches on title" $- [ "@notify { \"testing\": message => \"the message\" }"- , "Notify <| title == \"testing\" |>"- ] `shouldNotify` ["the message"]- it "matches on other parameters" $- [ "@notify { \"testing\": message => \"the message\" }"- , "@notify { \"other testing\": message => \"the wrong message\" }"- , "Notify <| message == \"the message\" |>"- ] `shouldNotify` ["the message"]- it "matches against elements of an array valued parameter" $- [ "@notify { \"testing\": message => [\"the\", \"message\"] }"- , "@notify { \"other testing\": message => [\"not\", \"here\"] }"- , "Notify <| message == \"message\" |>"- ] `shouldNotify` [PArray ["the", "message"]]- it "matches with bare word" $- [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }"- , "Notify <| tag == one |>"- ] `shouldNotify` ["wanted"]- it "matches with single quoted string" $- [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }"- , "Notify <| tag == 'one' |>"- ] `shouldNotify` ["wanted"]- it "matches with double quoted string" $- [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }"- , "Notify <| tag == \"one\" |>"- ] `shouldNotify` ["wanted"]- it "matches with double quoted string with interpolated expression" $- [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }"- , "$x = 'one'"- , "Notify <| tag == \"$x\" |>"- ] `shouldNotify` ["wanted"]- it "matches with resource references" $ do- pending- shouldNotify [ "@notify { \"foobar\": }"- , "@notify { \"testing\": require => Notify[\"foobar\"], message => \"wanted\" }"- , "Notify <| require == Notify[\"foobar\"] |>"- ]- ["wanted"]- it "allows criteria to be combined with 'and'" $- [ "@notify { \"testing\": message => \"the message\" }"- , "@notify { \"other\": message => \"the message\" }"- , "Notify <| title == \"testing\" and message == \"the message\" |>"- ] `shouldNotify` ["the message"]- it "allows criteria to be combined with 'or'" $- [ "@notify { \"testing\": message => \"the message\" }"- , "@notify { \"other\": message => \"other message\" }"- , "@notify { \"yet another\": message => \"different message\" }"- , "Notify <| title == \"testing\" or message == \"other message\" |>"- ] `shouldNotify` ["the message", "other message"]- it "allows criteria to be grouped with parens" $- [ "@notify { \"testing\": message => \"different message\", withpath => true }"- , "@notify { \"other\": message => \"the message\" }"- , "@notify { \"yet another\": message => \"the message\", withpath => true }"- , "Notify <| (title == \"testing\" or message == \"the message\") and withpath == true |>"- ] `shouldNotify` ["the message", "different message"]- it "does not do anything if nothing matches" $- [ "@notify { \"testing\": message => \"different message\" }"- , "Notify <| title == \"does not exist\" |>"- ] `shouldNotify` []- it "excludes items with inequalities" $- [ "@notify { \"testing\": message => \"good message\" }"- , "@notify { \"the wrong one\": message => \"bad message\" }"- , "Notify <| title != \"the wrong one\" |>"- ] `shouldNotify` ["good message"]- it "does not exclude resources with unequal arrays" $- [ "@notify { \"testing\": message => \"message\" }"- , "@notify { \"the wrong one\": message => [\"not this message\", \"or this one\"] }"- , "Notify <| message != \"not this message\" |>"- ] `shouldNotify` ["message", PArray ["not this message", "or this one"]]- it "does not exclude tags with inequalities" $- [ "@notify { \"testing\": tag => [\"wanted\"], message => \"wanted message\" }"- , "@notify { \"other\": tag => [\"why\"], message => \"the way it works\" }"- , "Notify <| tag != \"why\" |>"- ] `shouldNotify` ["wanted message", "the way it works"]- it "does not collect classes" $ shouldFail $- [ "class theclass {"- , " @notify { \"testing\": message => \"good message\" }"- , "}"- , "Class <| |>"- ]- it "does not collect resources that don't exist" $ do- pending- shouldFail $- [ "class theclass {"- , " @notify { \"testing\": message => \"good message\" }"- , "}"- , "SomeResource <| |>"- ]- it "modifies an existing array" $- [ "@notify { \"testing\": message => [\"original message\"] }"- , "Notify <| |> {"- , " message +> \"extra message\""- , "}"] `shouldNotify` [PArray ["original message", "extra message"]]- it "converts a scalar to an array" $- [ "@notify { \"testing\": message => \"original message\" }"- , "Notify <| |> {"- , " message +> \"extra message\""- , "}"] `shouldNotify` [PArray ["original message", "extra message"]]- it "collects and overrides virtual resources multiple times using multiple collects" $- [ "@notify { \"testing\": message => \"original\" }"- , "Notify <| |> { message => 'overridden1' }"- , "Notify <| |> { message => 'overridden2' }"- ] `shouldNotify` ["overridden2"]- it "collects and overrides non virtual resources multiple times using multiple collects" $- [ "notify { \"testing\": message => \"original\" }"- , "Notify <| |> { message => 'overridden1' }"- , "Notify <| |> { message => 'overridden2' }"- ] `shouldNotify` ["overridden2"]-+ it "matches everything when no query given" $+ [ "@notify { 'testing': message => 'the message' }",+ "@notify { 'other': message => 'the other message' }",+ "Notify <| |>"+ ]+ `shouldNotify` ["the other message", "the message"]+ it "matches regular resources" $+ [ "notify { \"testing\": message => \"the message\" }",+ "notify { \"other\": message => \"the other message\" }",+ "Notify <| |> { message => \"changed\" }"+ ]+ `shouldNotify` ["changed", "changed"]+ it "matches on tags" $+ [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }",+ "@notify { \"other\": tag => [\"two\"], message => \"unwanted\" }",+ "Notify <| tag == one |>"+ ]+ `shouldNotify` ["wanted"]+ it "matches on title" $+ [ "@notify { \"testing\": message => \"the message\" }",+ "Notify <| title == \"testing\" |>"+ ]+ `shouldNotify` ["the message"]+ it "matches on other parameters" $+ [ "@notify { \"testing\": message => \"the message\" }",+ "@notify { \"other testing\": message => \"the wrong message\" }",+ "Notify <| message == \"the message\" |>"+ ]+ `shouldNotify` ["the message"]+ it "matches against elements of an array valued parameter" $+ [ "@notify { \"testing\": message => [\"the\", \"message\"] }",+ "@notify { \"other testing\": message => [\"not\", \"here\"] }",+ "Notify <| message == \"message\" |>"+ ]+ `shouldNotify` [PArray ["the", "message"]]+ it "matches with bare word" $+ [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }",+ "Notify <| tag == one |>"+ ]+ `shouldNotify` ["wanted"]+ it "matches with single quoted string" $+ [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }",+ "Notify <| tag == 'one' |>"+ ]+ `shouldNotify` ["wanted"]+ it "matches with double quoted string" $+ [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }",+ "Notify <| tag == \"one\" |>"+ ]+ `shouldNotify` ["wanted"]+ it "matches with double quoted string with interpolated expression" $+ [ "@notify { \"testing\": tag => [\"one\"], message => \"wanted\" }",+ "$x = 'one'",+ "Notify <| tag == \"$x\" |>"+ ]+ `shouldNotify` ["wanted"]+ it "matches with resource references" $ do+ pending+ shouldNotify+ [ "@notify { \"foobar\": }",+ "@notify { \"testing\": require => Notify[\"foobar\"], message => \"wanted\" }",+ "Notify <| require == Notify[\"foobar\"] |>"+ ]+ ["wanted"]+ it "allows criteria to be combined with 'and'" $+ [ "@notify { \"testing\": message => \"the message\" }",+ "@notify { \"other\": message => \"the message\" }",+ "Notify <| title == \"testing\" and message == \"the message\" |>"+ ]+ `shouldNotify` ["the message"]+ it "allows criteria to be combined with 'or'" $+ [ "@notify { \"testing\": message => \"the message\" }",+ "@notify { \"other\": message => \"other message\" }",+ "@notify { \"yet another\": message => \"different message\" }",+ "Notify <| title == \"testing\" or message == \"other message\" |>"+ ]+ `shouldNotify` ["the message", "other message"]+ it "allows criteria to be grouped with parens" $+ [ "@notify { \"testing\": message => \"different message\", withpath => true }",+ "@notify { \"other\": message => \"the message\" }",+ "@notify { \"yet another\": message => \"the message\", withpath => true }",+ "Notify <| (title == \"testing\" or message == \"the message\") and withpath == true |>"+ ]+ `shouldNotify` ["the message", "different message"]+ it "does not do anything if nothing matches" $+ [ "@notify { \"testing\": message => \"different message\" }",+ "Notify <| title == \"does not exist\" |>"+ ]+ `shouldNotify` []+ it "excludes items with inequalities" $+ [ "@notify { \"testing\": message => \"good message\" }",+ "@notify { \"the wrong one\": message => \"bad message\" }",+ "Notify <| title != \"the wrong one\" |>"+ ]+ `shouldNotify` ["good message"]+ it "does not exclude resources with unequal arrays" $+ [ "@notify { \"testing\": message => \"message\" }",+ "@notify { \"the wrong one\": message => [\"not this message\", \"or this one\"] }",+ "Notify <| message != \"not this message\" |>"+ ]+ `shouldNotify` ["message", PArray ["not this message", "or this one"]]+ it "does not exclude tags with inequalities" $+ [ "@notify { \"testing\": tag => [\"wanted\"], message => \"wanted message\" }",+ "@notify { \"other\": tag => [\"why\"], message => \"the way it works\" }",+ "Notify <| tag != \"why\" |>"+ ]+ `shouldNotify` ["wanted message", "the way it works"]+ it "does not collect classes" $+ shouldFail+ [ "class theclass {",+ " @notify { \"testing\": message => \"good message\" }",+ "}",+ "Class <| |>"+ ]+ it "does not collect resources that don't exist" $ do+ pending+ shouldFail+ [ "class theclass {",+ " @notify { \"testing\": message => \"good message\" }",+ "}",+ "SomeResource <| |>"+ ]+ it "modifies an existing array" $+ [ "@notify { \"testing\": message => [\"original message\"] }",+ "Notify <| |> {",+ " message +> \"extra message\"",+ "}"+ ]+ `shouldNotify` [PArray ["original message", "extra message"]]+ it "converts a scalar to an array" $+ [ "@notify { \"testing\": message => \"original message\" }",+ "Notify <| |> {",+ " message +> \"extra message\"",+ "}"+ ]+ `shouldNotify` [PArray ["original message", "extra message"]]+ it "collects and overrides virtual resources multiple times using multiple collects" $+ [ "@notify { \"testing\": message => \"original\" }",+ "Notify <| |> { message => 'overridden1' }",+ "Notify <| |> { message => 'overridden2' }"+ ]+ `shouldNotify` ["overridden2"]+ it "collects and overrides non virtual resources multiple times using multiple collects" $+ [ "notify { \"testing\": message => \"original\" }",+ "Notify <| |> { message => 'overridden1' }",+ "Notify <| |> { message => 'overridden2' }"+ ]+ `shouldNotify` ["overridden2"] spec1 :: Spec spec1 = do@@ -162,21 +185,22 @@ getResAttr (computeWith "=>") ^. at "uid" `shouldBe` Just (PNumber 1000) describe "AppendArrow in AttributeDecl" $ it "should add 'docker' to the 'groups' attribute of the user resource" $ do- getResAttr (computeWith "+>") ^. at "groups" `shouldBe` Just (PArray $ ["ci", "docker"])+ getResAttr (computeWith "+>") ^. at "groups" `shouldBe` Just (PArray ["ci", "docker"]) describe "AssignArrow in AttributeDecl" $ it "should override the 'groups' attributes from the user resource" $- getResAttr (computeWith "=>") ^. at "groups" `shouldBe` Just (PArray $ ["docker"])+ getResAttr (computeWith "=>") ^. at "groups" `shouldBe` Just (PArray ["docker"]) where getResAttr :: Either String (FinalCatalog, InterpreterWriter) -> Container PValue- getResAttr s = s ^. _Right . _1 . at (RIdentifier "user" "jenkins")._Just.rattributes+ getResAttr s = s ^. _Right . _1 . at (RIdentifier "user" "jenkins") . _Just . rattributes arrowOperationInput :: Text -> Text arrowOperationInput arr =- Text.unlines [ "user { 'jenkins':"- , " groups => 'ci'"- , "}"- , "User <| title == 'jenkins' |> {"- , "groups " <> arr <> " 'docker',"- , "uid => 1000}"- , "}"- ]+ Text.unlines+ [ "user { 'jenkins':",+ " groups => 'ci'",+ "}",+ "User <| title == 'jenkins' |> {",+ "groups " <> arr <> " 'docker',",+ "uid => 1000}",+ "}"+ ]
tests/Interpreter/EvalSpec.hs view
@@ -1,44 +1,43 @@ module Interpreter.EvalSpec (spec) where --import Test.Hspec-import Text.Megaparsec (errorBundlePretty, eof, parse)--import Puppet.Interpreter-import Puppet.Parser.Internal-import Puppet.Runner-import XPrelude+import Puppet.Interpreter+import Puppet.Parser.Internal+import Puppet.Runner+import Test.Hspec+import Text.Megaparsec (eof, errorBundlePretty, parse)+import XPrelude -evaluations = [ "4 + 2 == 6"- , "[1,2][1] == 2"- , "[1,[1,2]][1][0] == 1"- , "[1,2,3] + [4,5,6] == [1,2,3,4,5,6]"- , "{a => 1} + {b => 2} == {a=>1, b=>2 }"- , "[1,2,3] << 10 == [1,2,3,10]"- , "[1,2,3] << [4,5] == [1,2,3,[4,5]]"- , "4 / 2.0 == 2"- , "$kernel == 'Linux'"- , "$facts['os']['architecture'] == 'amd64'"- -- string interpolation- , "\"$kernel\" == 'Linux'"- , "\"${kernel} box\" == 'Linux box'"- , "\"${os['architecture']}\" == 'amd64'"- , "\"${os['release']['major']}\" == '7'"- , "\"${facts['kernel']}\" == 'Linux'"- , "\"${facts['os']['architecture']}\" == 'amd64'"- --- , "$settings::confdir == '/etc/puppet'"- , "regsubst('127', '([0-9]+)', '<\\1>', 'G') == '<127>'"- , "regsubst(['1','2','3'], '([0-9]+)', '<\\1>', 'G') == ['<1>','<2>','<3>']"- , "versioncmp('2.1','2.2') == -1"- , "inline_template('a','b') == 'ab'"- ]+evaluations =+ [ "4 + 2 == 6",+ "[1,2][1] == 2",+ "[1,[1,2]][1][0] == 1",+ "[1,2,3] + [4,5,6] == [1,2,3,4,5,6]",+ "{a => 1} + {b => 2} == {a=>1, b=>2 }",+ "[1,2,3] << 10 == [1,2,3,10]",+ "[1,2,3] << [4,5] == [1,2,3,[4,5]]",+ "4 / 2.0 == 2",+ "$kernel == 'Linux'",+ "$facts['os']['architecture'] == 'amd64'",+ -- string interpolation+ "\"$kernel\" == 'Linux'",+ "\"${kernel} box\" == 'Linux box'",+ "\"${os['architecture']}\" == 'amd64'",+ "\"${os['release']['major']}\" == '7'",+ "\"${facts['kernel']}\" == 'Linux'",+ "\"${facts['os']['architecture']}\" == 'amd64'",+ --+ "$settings::confdir == '/etc/puppet'",+ "regsubst('127', '([0-9]+)', '<\\1>', 'G') == '<127>'",+ "regsubst(['1','2','3'], '([0-9]+)', '<\\1>', 'G') == ['<1>','<2>','<3>']",+ "versioncmp('2.1','2.2') == -1",+ "inline_template('a','b') == 'ab'"+ ] testEvaluation t =- let item = it ("should evaluate " <> t) in- case check (toS t) of- Left ctx -> context ctx $ item False- Right b -> item b+ let item = it ("should evaluate " <> t)+ in case check (toS t) of+ Left ctx -> context ctx $ item False+ Right b -> item b check :: Text -> Either String Bool check t =@@ -46,7 +45,7 @@ Left err -> Left $ "Parsing error. Are you sure the input is correct ?\n" <> errorBundlePretty err Right e -> case dummyEval (resolveExpression e) of Right (PBoolean True) -> Right True- _ -> Right False+ _ -> Right False spec = do describe "Evaluation of expressions" $ mapM_ testEvaluation evaluations
tests/Interpreter/EvaluateStatementSpec.hs view
@@ -1,25 +1,25 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.EvaluateStatementSpec where -import Helpers import qualified Data.Text as Text+import Helpers main :: IO () main = hspec spec shouldNotify :: [Text] -> PValue -> Expectation shouldNotify s expected = do- catalog <- case pureCatalog (Text.unlines s) of- Left rr -> fail rr- Right (x,_) -> pure x- let msg = catalog ^? at (RIdentifier "notify" "test")._Just.rattributes. ix "message"- msg `shouldBe` (Just expected)-+ catalog <- case pureCatalog (Text.unlines s) of+ Left rr -> fail rr+ Right (x, _) -> pure x+ let msg = catalog ^? at (RIdentifier "notify" "test") . _Just . rattributes . ix "message"+ msg `shouldBe` Just expected spec :: Spec spec = do describe "evaluate statement" $ do it "should evaluate simple variable assignment" $- [ "$a = 0" , "notify { 'test': message => \"a is ${a}\"}"] `shouldNotify` "a is 0"+ ["$a = 0", "notify { 'test': message => \"a is ${a}\"}"] `shouldNotify` "a is 0" it "should evaluate chained variables assignment" $- [ "$a = $b = 0" , "notify { 'test': message => \"b is ${b}\"}"] `shouldNotify` "b is 0"+ ["$a = $b = 0", "notify { 'test': message => \"b is ${b}\"}"] `shouldNotify` "b is 0"
tests/Interpreter/Function/AssertPrivateSpec.hs view
@@ -1,27 +1,32 @@-{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.AssertPrivateSpec where -import Helpers+import Helpers main :: IO () main = hspec spec -evalWithScope :: ([PValue] -> InterpreterMonad PValue)- -> Text -- ^ caller scope- -> Text -- ^ module scope- -> [Expression] -- ^ function args- -> Either String PValue+evalWithScope ::+ ([PValue] -> InterpreterMonad PValue) ->+ -- | caller scope+ Text ->+ -- | module scope+ Text ->+ -- | function args+ [Expression] ->+ Either String PValue evalWithScope apFunc callerScope moduleScope = (_Left %~ show) . view _1 . ctxEval . (mapM resolveExpression >=> apFunc) where- ctxEval = pureEval' mempty state0 Nothing- state0 = dummyInitialState & curScope .~ [ContClass moduleScope, ContClass callerScope]+ ctxEval = pureEval' mempty state0 Nothing+ state0 = dummyInitialState & curScope .~ [ContClass moduleScope, ContClass callerScope] spec :: Spec spec = withStdlibFunction "assert_private" $ \apFunc -> do- let errorWith a b = case a of- Right x -> fail ("Should have failed, got this instead: " ++ show x)- Left rr -> rr `shouldContain` b- it "should work when called from inside module" (evalWithScope apFunc "bar" "bar" [] `shouldBe` Right PUndef)- it "should fail with the default message" (evalWithScope apFunc "bar" "baz" [] `errorWith` "is private")- it "should fail with an explicit failure message" (evalWithScope apFunc "bar" "baz" ["lalala"] `errorWith` "lalala")+ let errorWith a b = case a of+ Right x -> fail ("Should have failed, got this instead: " ++ show x)+ Left rr -> rr `shouldContain` b+ it "should work when called from inside module" (evalWithScope apFunc "bar" "bar" [] `shouldBe` Right PUndef)+ it "should fail with the default message" (evalWithScope apFunc "bar" "baz" [] `errorWith` "is private")+ it "should fail with an explicit failure message" (evalWithScope apFunc "bar" "baz" ["lalala"] `errorWith` "lalala")
tests/Interpreter/Function/DeleteAtSpec.hs view
@@ -2,29 +2,29 @@ module Interpreter.Function.DeleteAtSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec spec :: Spec spec = withStdlibFunction "delete_at" $ \deleteAtFunc -> do- let evalArgs' = dummyEval . deleteAtFunc- narray = PArray . fmap PNumber- check a b res = case evalArgs' [narray a, PNumber b] of- Left rr -> expectationFailure (show rr)- Right res' -> res' `shouldBe` narray res- checkError args ins = case evalArgs' args of- Left rr -> show rr `shouldContain` ins- Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)- it "should error with invalid arguments" $ do- checkError [] "expects 2 arguments"- checkError [PNumber 1] "expects 2 arguments"- checkError ["foo", "bar"] "expects its first argument to be an array"- checkError [ narray [0,1,2], PNumber 3 ] "Out of bounds access"- it "should work otherwise" $ do- check [0,1,2] 1 [0,2]- it "should work for negative positions" $ do- pending- check [0,1,2] (-1) [0,1]- check [0,1,2] (-4) [0,1,2]+ let evalArgs' = dummyEval . deleteAtFunc+ narray = PArray . fmap PNumber+ check a b res = case evalArgs' [narray a, PNumber b] of+ Left rr -> expectationFailure (show rr)+ Right res' -> res' `shouldBe` narray res+ checkError args ins = case evalArgs' args of+ Left rr -> show rr `shouldContain` ins+ Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)+ it "should error with invalid arguments" $ do+ checkError [] "expects 2 arguments"+ checkError [PNumber 1] "expects 2 arguments"+ checkError ["foo", "bar"] "expects its first argument to be an array"+ checkError [narray [0, 1, 2], PNumber 3] "Out of bounds access"+ it "should work otherwise" $ do+ check [0, 1, 2] 1 [0, 2]+ it "should work for negative positions" $ do+ pending+ check [0, 1, 2] (-1) [0, 1]+ check [0, 1, 2] (-4) [0, 1, 2]
tests/Interpreter/Function/EachSpec.hs view
@@ -1,60 +1,67 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.EachSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec spec :: Spec spec = do- let getCatalog x = case pureCatalog x of- Left rr -> fail rr- Right (y,_) -> pure y- describe "should be callable as" $ do- let checkEnsure f v c =- getResource (RIdentifier "file" f) c >>= getAttribute "ensure" >>= \a -> a `shouldBe` v- checks :: [(Text, PValue)] -> FinalCatalog -> IO ()- checks lst c = mapM_ (\(f,v) -> checkEnsure f v c) lst- it "each on an array selecting each value" $- getCatalog "$a = [1,2,3]\n $a.each |$v| {\n file { \"/file_$v\": ensure => present } \n } "- >>= checks [ ("/file_1", "present")- , ("/file_2", "present")- , ("/file_3", "present")- ]- it "each on an array selecting each value - function call style" $- getCatalog "$a = [1,2,3]\n each ($a) |$index, $v| {\n file { \"/file_$v\": ensure => present }\n }"- >>= checks [ ("/file_1", "present")- , ("/file_2", "present")- , ("/file_3", "present")- ]- it "each on an array with index" $- getCatalog "$a = [present, absent, present]\n $a.each |$k,$v| {\n file { \"/file_$k\": ensure => $v }\n }"- >>= checks [ ("/file_0", "present")- , ("/file_1", "absent")- , ("/file_2", "present")- ]- it "each on a hash selecting entries" $- getCatalog "$a = {'a'=>'present','b'=>'absent','c'=>'present'}\n $a.each |$e| {\n $num = $e[0]\n file { \"/file_${num}\": ensure => $e[1] }\n }"- >>= checks [ ("/file_a", "present")- , ("/file_b", "absent")- , ("/file_c", "present")- ]- it "each on a hash selecting key and value" $- getCatalog "$a = {'a'=>present,'b'=>absent,'c'=>present}\n $a.each |$k, $v| {\n file { \"/file_$k\": ensure => $v }\n }"- >>= checks [ ("/file_a", "present")- , ("/file_b", "absent")- , ("/file_c", "present")- ]- it "each on a hash selecting key and value (using captures-last parameter)" $ do- pending- getCatalog "$a = {'a'=>present,'b'=>absent,'c'=>present}\n $a.each |*$kv| {\n file { \"/file_${kv[0]}\": ensure => $kv[1] }\n }"- >>= checks [ ("/file_a", "present")- , ("/file_b", "absent")- , ("/file_c", "present")- ]- describe "should produce receiver" $- it "each checking produced value using single expression" $ do- pending- c <- getCatalog "$a = [1, 3, 2]\n $b = $a.each |$x| { \"unwanted\" }\n $u = $b[1]\n file { \"/file_${u}\":\n ensure => present\n }"- getResource (RIdentifier "file" "/file_3") c >>= getAttribute "ensure" >>= \a -> a `shouldBe` "present"+ let getCatalog x = case pureCatalog x of+ Left rr -> fail rr+ Right (y, _) -> pure y+ describe "should be callable as" $ do+ let checkEnsure f v c =+ getResource (RIdentifier "file" f) c >>= getAttribute "ensure" >>= \a -> a `shouldBe` v+ checks :: [(Text, PValue)] -> FinalCatalog -> IO ()+ checks lst c = mapM_ (\(f, v) -> checkEnsure f v c) lst+ it "each on an array selecting each value" $+ getCatalog "$a = [1,2,3]\n $a.each |$v| {\n file { \"/file_$v\": ensure => present } \n } "+ >>= checks+ [ ("/file_1", "present"),+ ("/file_2", "present"),+ ("/file_3", "present")+ ]+ it "each on an array selecting each value - function call style" $+ getCatalog "$a = [1,2,3]\n each ($a) |$index, $v| {\n file { \"/file_$v\": ensure => present }\n }"+ >>= checks+ [ ("/file_1", "present"),+ ("/file_2", "present"),+ ("/file_3", "present")+ ]+ it "each on an array with index" $+ getCatalog "$a = [present, absent, present]\n $a.each |$k,$v| {\n file { \"/file_$k\": ensure => $v }\n }"+ >>= checks+ [ ("/file_0", "present"),+ ("/file_1", "absent"),+ ("/file_2", "present")+ ]+ it "each on a hash selecting entries" $+ getCatalog "$a = {'a'=>'present','b'=>'absent','c'=>'present'}\n $a.each |$e| {\n $num = $e[0]\n file { \"/file_${num}\": ensure => $e[1] }\n }"+ >>= checks+ [ ("/file_a", "present"),+ ("/file_b", "absent"),+ ("/file_c", "present")+ ]+ it "each on a hash selecting key and value" $+ getCatalog "$a = {'a'=>present,'b'=>absent,'c'=>present}\n $a.each |$k, $v| {\n file { \"/file_$k\": ensure => $v }\n }"+ >>= checks+ [ ("/file_a", "present"),+ ("/file_b", "absent"),+ ("/file_c", "present")+ ]+ it "each on a hash selecting key and value (using captures-last parameter)" $ do+ pending+ getCatalog "$a = {'a'=>present,'b'=>absent,'c'=>present}\n $a.each |*$kv| {\n file { \"/file_${kv[0]}\": ensure => $kv[1] }\n }"+ >>= checks+ [ ("/file_a", "present"),+ ("/file_b", "absent"),+ ("/file_c", "present")+ ]+ describe "should produce receiver" $+ it "each checking produced value using single expression" $ do+ pending+ c <- getCatalog "$a = [1, 3, 2]\n $b = $a.each |$x| { \"unwanted\" }\n $u = $b[1]\n file { \"/file_${u}\":\n ensure => present\n }"+ getResource (RIdentifier "file" "/file_3") c >>= getAttribute "ensure" >>= \a -> a `shouldBe` "present"
tests/Interpreter/Function/JoinKeysToValuesSpec.hs view
@@ -1,26 +1,24 @@-{-# LANGUAGE OverloadedLists #-}-module Interpreter.Function.JoinKeysToValuesSpec (spec) where+{-# LANGUAGE OverloadedLists #-} -import Test.Hspec+module Interpreter.Function.JoinKeysToValuesSpec (spec) where import qualified Data.Foldable as F-import Data.Monoid--import Puppet.Interpreter--import Helpers+import Data.Monoid+import Helpers+import Puppet.Interpreter+import Test.Hspec spec :: Spec spec = withStdlibFunction "join_keys_to_values" $ \jkvFunc -> it "Should work as expected" $ do- let eval h s = case dummyEval (jkvFunc [PHash h, PString s]) of- Left rr -> Left (renderToString (getError rr))- Right (PArray vals) -> Right (F.toList vals)- Right v -> Left ("Expected an array, not: " <> renderToString v)- eval [] "" `shouldBe` Right []- eval [] ":" `shouldBe` Right []- eval [("key","value")] "" `shouldBe` Right ["keyvalue"]- eval [("key","value")] ":" `shouldBe` Right ["key:value"]- eval [("key",PUndef)] ":" `shouldBe` Right ["key:"]- case eval [("key1","value1"),("key2","value2")] ":" of- Left rr -> fail rr- Right lst -> lst `shouldMatchList` ["key1:value1", "key2:value2"]+ let eval h s = case dummyEval (jkvFunc [PHash h, PString s]) of+ Left rr -> Left (renderToString (getError rr))+ Right (PArray vals) -> Right (F.toList vals)+ Right v -> Left ("Expected an array, not: " <> renderToString v)+ eval [] "" `shouldBe` Right []+ eval [] ":" `shouldBe` Right []+ eval [("key", "value")] "" `shouldBe` Right ["keyvalue"]+ eval [("key", "value")] ":" `shouldBe` Right ["key:value"]+ eval [("key", PUndef)] ":" `shouldBe` Right ["key:"]+ case eval [("key1", "value1"), ("key2", "value2")] ":" of+ Left rr -> fail rr+ Right lst -> lst `shouldMatchList` ["key1:value1", "key2:value2"]
tests/Interpreter/Function/LookupSpec.hs view
@@ -1,18 +1,20 @@ module Interpreter.Function.LookupSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec fname :: Text fname = "lookup"+ expectedErrMsg :: String expectedErrMsg = "Wrong set of arguments" -- See dummyFacts defined in Pure.hs testkey :: Expression testkey = "foo"+ expectedValue :: Text expectedValue = "dummy" @@ -28,10 +30,10 @@ spec :: Spec spec = do- it "should fail with no argument" (checkError [] expectedErrMsg)- it "should succeed with one argument" (checkSuccess [testkey] expectedValue)- it "should succeed with 4 arguments" (checkSuccess [testkey, stringDatatype, "unique", "default"] expectedValue)- it "should fail with an unknown merge strategy" (checkError [testkey, stringDatatype, "joe", "default"] "Unknown merge strategy")- it "should succeed with two arguments, the second one being a datatype" (checkSuccess [testkey, stringDatatype] expectedValue)- it "should fail when the type mismatched" (checkError [testkey, boolDatatype] "Datatype mismatched")- it "should fail with two arguments both strings" (checkError [testkey, "default"] expectedErrMsg)+ it "should fail with no argument" (checkError [] expectedErrMsg)+ it "should succeed with one argument" (checkSuccess [testkey] expectedValue)+ it "should succeed with 4 arguments" (checkSuccess [testkey, stringDatatype, "unique", "default"] expectedValue)+ it "should fail with an unknown merge strategy" (checkError [testkey, stringDatatype, "joe", "default"] "Unknown merge strategy")+ it "should succeed with two arguments, the second one being a datatype" (checkSuccess [testkey, stringDatatype] expectedValue)+ it "should fail when the type mismatched" (checkError [testkey, boolDatatype] "Datatype mismatched")+ it "should fail with two arguments both strings" (checkError [testkey, "default"] expectedErrMsg)
tests/Interpreter/Function/MergeSpec.hs view
@@ -1,11 +1,9 @@ {-# LANGUAGE OverloadedLists #-}-module Interpreter.Function.MergeSpec (spec, main) where --import Helpers+module Interpreter.Function.MergeSpec (spec, main) where import qualified Data.HashMap.Strict as HM-+import Helpers main :: IO () main = hspec spec@@ -15,68 +13,77 @@ spec1 evalArgs :: InterpreterMonad PValue -> Either PrettyError (HM.HashMap Text PValue)-evalArgs = dummyEval- >=> \pv -> case pv of- PHash s -> return s- _ -> Left ("Expected a string, not " <> PrettyError (pretty pv))+evalArgs =+ dummyEval+ >=> \pv -> case pv of+ PHash s -> return s+ _ -> Left ("Expected a string, not " <> PrettyError (pretty pv)) spec0 :: Spec spec0 = do withStdlibFunction "merge" $ \mergeFunc -> do let evalArgs' = evalArgs . mergeFunc let check args res = case evalArgs' (map PHash args) of- Left rr -> expectationFailure (show rr)- Right res' -> res' `shouldBe` res+ Left rr -> expectationFailure (show rr)+ Right res' -> res' `shouldBe` res checkError args ins = case evalArgs' args of- Left rr -> show rr `shouldContain` ins- Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)+ Left rr -> show rr `shouldContain` ins+ Right r -> expectationFailure ("Should have errored, received this instead: " <> show r) it "should error with invalid arguments" $ do- checkError [] "Expects at least two hashes"- checkError [PNumber 1] "Expects at least two hashes"- checkError [PBoolean True] "Expects at least two hashes"- checkError ["foo"] "Expects at least two hashes"+ checkError [] "Expects at least two hashes"+ checkError [PNumber 1] "Expects at least two hashes"+ checkError [PBoolean True] "Expects at least two hashes"+ checkError ["foo"] "Expects at least two hashes" it "should handle empty hashes" $ do- check [[],[]] []- check [[],[],[]] []+ check [[], []] []+ check [[], [], []] [] it "should merge hashes" $ do- check [ [("key", "value")], [] ] [("key","value")]- check [ [], [("key", "value")] ] [("key","value")]- check [ [("key1", "value1")], [("key2", "value2")], [("key3", "value3")] ] [("key1", "value1"), ("key2", "value2"), ("key3", "value3")]- check [ [("key", "value1")], [("key", "value2")] ] [("key","value2")]+ check [[("key", "value")], []] [("key", "value")]+ check [[], [("key", "value")]] [("key", "value")]+ check [[("key1", "value1")], [("key2", "value2")], [("key3", "value3")]] [("key1", "value1"), ("key2", "value2"), ("key3", "value3")]+ check [[("key", "value1")], [("key", "value2")]] [("key", "value2")] spec1 :: Spec spec1 =- describe "deep_merge" $- withStdlibFunction "deep_merge" $ \mergeFunc -> do- let evalArgs' = evalArgs . mergeFunc- let check args res = case evalArgs' (map PHash args) of- Left rr -> expectationFailure (show rr)- Right res' -> res' `shouldBe` res- checkError args ins = case evalArgs' args of- Left rr -> show rr `shouldContain` ins- Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)+ describe "deep_merge" $+ withStdlibFunction "deep_merge" $ \mergeFunc -> do+ let evalArgs' = evalArgs . mergeFunc+ let check args res = case evalArgs' (map PHash args) of+ Left rr -> expectationFailure (show rr)+ Right res' -> res' `shouldBe` res+ checkError args ins = case evalArgs' args of+ Left rr -> show rr `shouldContain` ins+ Right r -> expectationFailure ("Should have errored, received this instead: " <> show r) - it "should error with invalid arguments" $ do+ it "should error with invalid arguments" $ do checkError [] "Expects at least two hashes" checkError [PNumber 1] "Expects at least two hashes" checkError [PBoolean True] "Expects at least two hashes" checkError ["foo"] "Expects at least two hashes"- it "should handle empty hashes" $ do- check [[],[]] []- check [[],[],[]] []- it "should deeply merge hashes" $ do- check [ [("key", "value")], [] ] [("key","value")]- check [ [], [("key", "value")] ] [("key","value")]- check [ [("key1", "value1")]- , [("key2", "value2")]- , [("key3", "value3")]- ] [("key1", "value1"), ("key2", "value2"), ("key3", "value3")]- check [ [("key", "value1")]- , [("key", "value2")]- ] [("key","value2")]- check [ [("key", PHash [("key00", "value00")])]- , [("key", PHash [("key01", "value01")])]- ] [("key", PHash [("key01", PString "value01"), ("key00", PString "value00")])]- check [ [("key", PHash [("key01", "value00")])]- , [("key", PHash [("key01", "value01")])]- ] [("key", PHash [("key01", PString "value01")])]+ it "should handle empty hashes" $ do+ check [[], []] []+ check [[], [], []] []+ it "should deeply merge hashes" $ do+ check [[("key", "value")], []] [("key", "value")]+ check [[], [("key", "value")]] [("key", "value")]+ check+ [ [("key1", "value1")],+ [("key2", "value2")],+ [("key3", "value3")]+ ]+ [("key1", "value1"), ("key2", "value2"), ("key3", "value3")]+ check+ [ [("key", "value1")],+ [("key", "value2")]+ ]+ [("key", "value2")]+ check+ [ [("key", PHash [("key00", "value00")])],+ [("key", PHash [("key01", "value01")])]+ ]+ [("key", PHash [("key01", PString "value01"), ("key00", PString "value00")])]+ check+ [ [("key", PHash [("key01", "value00")])],+ [("key", PHash [("key01", "value01")])]+ ]+ [("key", PHash [("key01", PString "value01")])]
tests/Interpreter/Function/PrefixSpec.hs view
@@ -1,9 +1,9 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.PrefixSpec (spec, main) where import qualified Data.Text as T--import Helpers+import Helpers main :: IO () main = hspec spec@@ -13,27 +13,26 @@ spec :: Spec spec = withStdlibFunction fname $ \tester -> do- let checkError input expectederror =- case dummyEval (tester input) of- Left rr -> show (getError rr) `shouldStartWith` (T.unpack fname ++ ": " ++ expectederror)- Right _ -> expectationFailure "should have failed"- checkSuccess input expected =- case dummyEval (tester input) of- Left rr -> expectationFailure (show rr)- Right r -> r `shouldBe` expected- it "should fail with no argument" (checkError [] "expects two arguments")- it "should fail if the first argument isn't an array or hash" (checkError ["lol"] "expects the first argument to be an array or a hash")- it "should fail if the second argument isn't a string" $ do- checkError [PArray [], PNumber 1] "expects the second argument to be a string"- checkError [PArray [], PArray []] "expects the second argument to be a string"- it "should work with arrays" $ do- checkSuccess [ PArray []] (PArray [])- checkSuccess [ PArray [], ""] (PArray [])- checkSuccess [ PArray ["one"], "pre" ] (PArray ["preone"])- checkSuccess [ PArray ["one","two","three"], "pre" ] (PArray ["preone","pretwo","prethree"])- it "should work with hashes" $ do- checkSuccess [(PHash mempty)] (PHash mempty)- checkSuccess [(PHash mempty), ""] (PHash mempty)- checkSuccess [(PHash [("one", PNumber 5)] ), "pre" ] (PHash [("preone", PNumber 5)])- checkSuccess [(PHash [("one", PNumber 5), ("two", "lol"), ("three", PNumber 7)]), "pre" ] (PHash [("preone", PNumber 5), ("pretwo", "lol"), ("prethree", PNumber 7)])-+ let checkError input expectederror =+ case dummyEval (tester input) of+ Left rr -> show (getError rr) `shouldStartWith` (T.unpack fname ++ ": " ++ expectederror)+ Right _ -> expectationFailure "should have failed"+ checkSuccess input expected =+ case dummyEval (tester input) of+ Left rr -> expectationFailure (show rr)+ Right r -> r `shouldBe` expected+ it "should fail with no argument" (checkError [] "expects two arguments")+ it "should fail if the first argument isn't an array or hash" (checkError ["lol"] "expects the first argument to be an array or a hash")+ it "should fail if the second argument isn't a string" $ do+ checkError [PArray [], PNumber 1] "expects the second argument to be a string"+ checkError [PArray [], PArray []] "expects the second argument to be a string"+ it "should work with arrays" $ do+ checkSuccess [PArray []] (PArray [])+ checkSuccess [PArray [], ""] (PArray [])+ checkSuccess [PArray ["one"], "pre"] (PArray ["preone"])+ checkSuccess [PArray ["one", "two", "three"], "pre"] (PArray ["preone", "pretwo", "prethree"])+ it "should work with hashes" $ do+ checkSuccess [PHash mempty] (PHash mempty)+ checkSuccess [PHash mempty, ""] (PHash mempty)+ checkSuccess [PHash [("one", PNumber 5)], "pre"] (PHash [("preone", PNumber 5)])+ checkSuccess [PHash [("one", PNumber 5), ("two", "lol"), ("three", PNumber 7)], "pre"] (PHash [("preone", PNumber 5), ("pretwo", "lol"), ("prethree", PNumber 7)])
tests/Interpreter/Function/ShellquoteSpec.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.ShellquoteSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec@@ -11,31 +12,40 @@ spec :: Spec spec = do- it "should handle no arguments" (check [] "")- it "should handle array arguments" $- check ["foo", ["bar@example.com", "localhost:/dev/null"], "xyzzy+-4711,23"]- "foo bar@example.com localhost:/dev/null xyzzy+-4711,23"- it "should quote unsafe characters" $- check ["/etc/passwd ", "(ls)", "*", "[?]", "'&'"]- "\"/etc/passwd \" \"(ls)\" \"*\" \"[?]\" \"'&'\""- it "should deal with double quotes" $- check ["\"foo\"bar\""]- "'\"foo\"bar\"'"- it "should cope with dollar signs" $- check ["$PATH", "foo$bar", "\"x$\""]- "'$PATH' 'foo$bar' '\"x$\"'"- it "should deal with apostrophes (single quotes)" $- check ["'foo'bar'", "`$'EDITOR'`"]- "\"'foo'bar'\" \"\\`\\$'EDITOR'\\`\""- it "should cope with grave accents (backquotes)" $- check ["`echo *`", "`ls \"$MAILPATH\"`"]- "'`echo *`' '`ls \"$MAILPATH\"`'"- it "should deal with both single and double quotes" $- check ["'foo\"bar\"xyzzy'", "\"foo'bar'xyzzy\""]- "\"'foo\\\"bar\\\"xyzzy'\" \"\\\"foo'bar'xyzzy\\\"\""- it "should handle multiple quotes *and* dollars and backquotes" $- check ["'foo\"$x`bar`\"xyzzy'"]- "\"'foo\\\"\\$x\\`bar\\`\\\"xyzzy'\""- it "should handle linefeeds" $- check ["foo \n bar"]- "\"foo \n bar\""+ it "should handle no arguments" (check [] "")+ it "should handle array arguments" $+ check+ ["foo", ["bar@example.com", "localhost:/dev/null"], "xyzzy+-4711,23"]+ "foo bar@example.com localhost:/dev/null xyzzy+-4711,23"+ it "should quote unsafe characters" $+ check+ ["/etc/passwd ", "(ls)", "*", "[?]", "'&'"]+ "\"/etc/passwd \" \"(ls)\" \"*\" \"[?]\" \"'&'\""+ it "should deal with double quotes" $+ check+ ["\"foo\"bar\""]+ "'\"foo\"bar\"'"+ it "should cope with dollar signs" $+ check+ ["$PATH", "foo$bar", "\"x$\""]+ "'$PATH' 'foo$bar' '\"x$\"'"+ it "should deal with apostrophes (single quotes)" $+ check+ ["'foo'bar'", "`$'EDITOR'`"]+ "\"'foo'bar'\" \"\\`\\$'EDITOR'\\`\""+ it "should cope with grave accents (backquotes)" $+ check+ ["`echo *`", "`ls \"$MAILPATH\"`"]+ "'`echo *`' '`ls \"$MAILPATH\"`'"+ it "should deal with both single and double quotes" $+ check+ ["'foo\"bar\"xyzzy'", "\"foo'bar'xyzzy\""]+ "\"'foo\\\"bar\\\"xyzzy'\" \"\\\"foo'bar'xyzzy\\\"\""+ it "should handle multiple quotes *and* dollars and backquotes" $+ check+ ["'foo\"$x`bar`\"xyzzy'"]+ "\"'foo\\\"\\$x\\`bar\\`\\\"xyzzy'\""+ it "should handle linefeeds" $+ check+ ["foo \n bar"]+ "\"foo \n bar\""
tests/Interpreter/Function/SizeSpec.hs view
@@ -1,42 +1,44 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.SizeSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec evalArgs :: InterpreterMonad PValue -> Either PrettyError Scientific-evalArgs = dummyEval- >=> \pv -> case pv of- PNumber s -> return s- _ -> Left ("Expected a string, not " <> PrettyError (pretty pv))+evalArgs =+ dummyEval+ >=> \pv -> case pv of+ PNumber s -> return s+ _ -> Left ("Expected a string, not " <> PrettyError (pretty pv)) spec :: Spec spec = withStdlibFunction "size" $ \sizeFunc -> do- let evalArgs' = evalArgs . sizeFunc- let check args res = case evalArgs' args of- Left rr -> expectationFailure (show rr)- Right res' -> res' `shouldBe` res- checkError args ins = case evalArgs' args of- Left rr -> show rr `shouldContain` ins- Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)- it "should error with no arguments" (checkError [] "a single argument")- it "should error with numerical arguments" (checkError [PNumber 1] "size(): Expects ")- it "should error with boolean arguments" (checkError [PBoolean True] "size(): Expects ")- -- Not conformant:- -- it "should error with numerical arguments" (checkError ["1"] "size(): Expects ")- it "should handle arrays" $ do- check [PArray []] 0- check [PArray ["a"]] 1- check [PArray ["one","two","three"]] 3- check [PArray ["one","two","three","four"]] 4- it "should handle hashes" $ do- check [PHash []] 0- check [PHash [("1","2")]] 1- check [PHash [("1","2"),("3","4")]] 2- it "should handle strings" $ do- check [""] 0- check ["a"] 1- check ["ab"] 2- check ["abcd"] 4+ let evalArgs' = evalArgs . sizeFunc+ let check args res = case evalArgs' args of+ Left rr -> expectationFailure (show rr)+ Right res' -> res' `shouldBe` res+ checkError args ins = case evalArgs' args of+ Left rr -> show rr `shouldContain` ins+ Right r -> expectationFailure ("Should have errored, received this instead: " <> show r)+ it "should error with no arguments" (checkError [] "a single argument")+ it "should error with numerical arguments" (checkError [PNumber 1] "size(): Expects ")+ it "should error with boolean arguments" (checkError [PBoolean True] "size(): Expects ")+ -- Not conformant:+ -- it "should error with numerical arguments" (checkError ["1"] "size(): Expects ")+ it "should handle arrays" $ do+ check [PArray []] 0+ check [PArray ["a"]] 1+ check [PArray ["one", "two", "three"]] 3+ check [PArray ["one", "two", "three", "four"]] 4+ it "should handle hashes" $ do+ check [PHash []] 0+ check [PHash [("1", "2")]] 1+ check [PHash [("1", "2"), ("3", "4")]] 2+ it "should handle strings" $ do+ check [""] 0+ check ["a"] 1+ check ["ab"] 2+ check ["abcd"] 4
tests/Interpreter/Function/SprintfSpec.hs view
@@ -1,6 +1,6 @@ module Interpreter.Function.SprintfSpec (spec, main) where -import Helpers+import Helpers main :: IO () main = hspec spec@@ -15,27 +15,27 @@ spec :: Spec spec = do- it "should fail with no argument" (checkError [] "Expects a string as its first argument")- it "should succeed with one argument" (checkSuccess ["hello"] "hello") -- puppet sprintf accepts one arg- it "should work with multiple arguments" (checkSuccess ["hello %s %s", "world", "!"] "hello world !")- it "should work with one string argument" (checkSuccess ["hello %s", "world"] "hello world" )- it "should work with one int argument" (checkSuccess ["hello %d", 10] "hello 10" )- it "should fail if arg is not provided" (checkError ["hello %s"] "not enough arguments")- it "should fail when a wrong format instruction is used" (checkError ["hello %d", "world"] "Don't know how to convert this to a number" )- it "should fail when a wrong format instruction is used" (checkError ["hello %f", "world"] "Don't know how to convert this to a number" )- it "should work with one int argument" (checkSuccess ["hello %f", 1.0] "hello 1.0" )- it "should work with one int argument" (checkSuccess ["hello %.1f", 1.23] "hello 1.2" )- it "should pad with zeroes" (checkSuccess ["hello %03d", 10] "hello 010")- it "should pad with spaces" (checkSuccess ["hello % 3d", 10] "hello 10")- it "should format integers" (checkSuccess ["%+05d", 23] "+0023")- it "should format floats" (checkSuccess ["%+.2f", 2.7182818284590451] "+2.72")- it "should format large floats" (pendingWith "Minor formatting difference" >> checkSuccess ["%+.2e", 27182818284590451] "+2.72e+16")- it "should work with " (checkSuccess ["%5d" , 5] " 5")- it "should work with 0" (checkSuccess ["%05d" , 5] "00005")- it "should work with - " (checkSuccess ["%-5d" , 5] "5 ")- it "should work with -0" (checkSuccess ["%-05d" , 5] "5 ")- it "should work with + " (checkSuccess ["%+5d" , 5] " +5")- it "should work with + 0" (checkSuccess ["%+05d" , 5] "+0005")- it "should work with +- " (checkSuccess ["%+-5d" , 5] "+5 ")- it "should work with +-0" (checkSuccess ["%+-05d", 5] "+5 ")- it "should perform more complex formatting" (pendingWith "# is not yet supported" >> checkSuccess [ "<%.8s:%#5o %#8X (%-8s)>", "overlongstring", 23, 48879, "foo" ] "<overlong: 027 0XBEEF (foo )>")+ it "should fail with no argument" (checkError [] "Expects a string as its first argument")+ it "should succeed with one argument" (checkSuccess ["hello"] "hello") -- puppet sprintf accepts one arg+ it "should work with multiple arguments" (checkSuccess ["hello %s %s", "world", "!"] "hello world !")+ it "should work with one string argument" (checkSuccess ["hello %s", "world"] "hello world")+ it "should work with one int argument" (checkSuccess ["hello %d", 10] "hello 10")+ it "should fail if arg is not provided" (checkError ["hello %s"] "not enough arguments")+ it "should fail when a wrong format instruction is used" (checkError ["hello %d", "world"] "Don't know how to convert this to a number")+ it "should fail when a wrong format instruction is used" (checkError ["hello %f", "world"] "Don't know how to convert this to a number")+ it "should work with one int argument" (checkSuccess ["hello %f", 1.0] "hello 1.0")+ it "should work with one int argument" (checkSuccess ["hello %.1f", 1.23] "hello 1.2")+ it "should pad with zeroes" (checkSuccess ["hello %03d", 10] "hello 010")+ it "should pad with spaces" (checkSuccess ["hello % 3d", 10] "hello 10")+ it "should format integers" (checkSuccess ["%+05d", 23] "+0023")+ it "should format floats" (checkSuccess ["%+.2f", 2.7182818284590451] "+2.72")+ it "should format large floats" (pendingWith "Minor formatting difference" >> checkSuccess ["%+.2e", 27182818284590451] "+2.72e+16")+ it "should work with " (checkSuccess ["%5d", 5] " 5")+ it "should work with 0" (checkSuccess ["%05d", 5] "00005")+ it "should work with - " (checkSuccess ["%-5d", 5] "5 ")+ it "should work with -0" (checkSuccess ["%-05d", 5] "5 ")+ it "should work with + " (checkSuccess ["%+5d", 5] " +5")+ it "should work with + 0" (checkSuccess ["%+05d", 5] "+0005")+ it "should work with +- " (checkSuccess ["%+-5d", 5] "+5 ")+ it "should work with +-0" (checkSuccess ["%+-05d", 5] "+5 ")+ it "should perform more complex formatting" (pendingWith "# is not yet supported" >> checkSuccess ["<%.8s:%#5o %#8X (%-8s)>", "overlongstring", 23, 48879, "foo"] "<overlong: 027 0XBEEF (foo )>")
tests/Interpreter/Function/SuffixSpec.hs view
@@ -1,9 +1,9 @@ {-# LANGUAGE OverloadedLists #-}+ module Interpreter.Function.SuffixSpec (spec, main) where import qualified Data.Text as T--import Helpers+import Helpers main :: IO () main = hspec spec@@ -13,27 +13,26 @@ spec :: Spec spec = withStdlibFunction fname $ \tester -> do- let checkError input expectederror =- case dummyEval (tester input) of- Left rr -> show (getError rr) `shouldStartWith` (T.unpack fname ++ ": " ++ expectederror)- Right _ -> expectationFailure "should have failed"- checkSuccess input expected =- case dummyEval (tester input) of- Left rr -> expectationFailure (show rr)- Right r -> r `shouldBe` expected- it "should fail with no argument" (checkError [] "expects two arguments")- it "should fail if the first argument isn't an array or hash" (checkError ["lol"] "expects the first argument to be an array or a hash")- it "should fail if the second argument isn't a string" $ do- checkError [PArray [], PNumber 1] "expects the second argument to be a string"- checkError [PArray [], PArray []] "expects the second argument to be a string"- it "should work with arrays" $ do- checkSuccess [ PArray []] (PArray [])- checkSuccess [ PArray [], ""] (PArray [])- checkSuccess [ PArray ["one"], "post" ] (PArray ["onepost"])- checkSuccess [ PArray ["one","two","three"], "post" ] (PArray ["onepost","twopost","threepost"])- it "should work with hashes" $ do- checkSuccess [(PHash mempty)] (PHash mempty)- checkSuccess [(PHash mempty), ""] (PHash mempty)- checkSuccess [(PHash [("one", PNumber 5)] ), "post" ] (PHash [("onepost", PNumber 5)])- checkSuccess [(PHash [("one", PNumber 5), ("two", "lol"), ("three", PNumber 7)]), "post" ] (PHash [("onepost", PNumber 5), ("twopost", "lol"), ("threepost", PNumber 7)])-+ let checkError input expectederror =+ case dummyEval (tester input) of+ Left rr -> show (getError rr) `shouldStartWith` (T.unpack fname ++ ": " ++ expectederror)+ Right _ -> expectationFailure "should have failed"+ checkSuccess input expected =+ case dummyEval (tester input) of+ Left rr -> expectationFailure (show rr)+ Right r -> r `shouldBe` expected+ it "should fail with no argument" (checkError [] "expects two arguments")+ it "should fail if the first argument isn't an array or hash" (checkError ["lol"] "expects the first argument to be an array or a hash")+ it "should fail if the second argument isn't a string" $ do+ checkError [PArray [], PNumber 1] "expects the second argument to be a string"+ checkError [PArray [], PArray []] "expects the second argument to be a string"+ it "should work with arrays" $ do+ checkSuccess [PArray []] (PArray [])+ checkSuccess [PArray [], ""] (PArray [])+ checkSuccess [PArray ["one"], "post"] (PArray ["onepost"])+ checkSuccess [PArray ["one", "two", "three"], "post"] (PArray ["onepost", "twopost", "threepost"])+ it "should work with hashes" $ do+ checkSuccess [PHash mempty] (PHash mempty)+ checkSuccess [PHash mempty, ""] (PHash mempty)+ checkSuccess [PHash [("one", PNumber 5)], "post"] (PHash [("onepost", PNumber 5)])+ checkSuccess [PHash [("one", PNumber 5), ("two", "lol"), ("three", PNumber 7)], "post"] (PHash [("onepost", PNumber 5), ("twopost", "lol"), ("threepost", PNumber 7)])
tests/Interpreter/Function/WithSpec.hs view
@@ -1,7 +1,6 @@-{-# LANGUAGE OverloadedLists #-} module Interpreter.Function.WithSpec where -import Helpers+import Helpers main :: IO () main = hspec spec@@ -9,30 +8,30 @@ spec :: Spec spec = do let getCatalog x = case pureCatalog x of- Left rr -> fail rr- Right (y,_) -> pure y+ Left rr -> fail rr+ Right (y, _) -> pure y describe "should run as" $ do it "should be callable with an argument" $ getCatalog "with ( 12 ) |$x| { file {'/f': content => $x; } }"- >>= getResource (RIdentifier "file" "/f")- >>= getAttribute "content"- >>= (`shouldBe` "12")+ >>= getResource (RIdentifier "file" "/f")+ >>= getAttribute "content"+ >>= (`shouldBe` "12") it "should be callable with two arguments" $ getCatalog "with ( '/tmp/lal', 12 ) |$f, $x| { file {$f: content => $x; } }"- >>= getResource (RIdentifier "file" "/tmp/lal")- >>= getAttribute "content"- >>= (`shouldBe` "12")+ >>= getResource (RIdentifier "file" "/tmp/lal")+ >>= getAttribute "content"+ >>= (`shouldBe` "12") it "should separate scopes scope" $ do getCatalog "$x='lol' \n with ( 12 ) |$x| { file {'/f': content => $x; } } \n file {'/g': content => $x; }"- >>= getResource (RIdentifier "file" "/f")- >>= getAttribute "content"- >>= (`shouldBe` "12")+ >>= getResource (RIdentifier "file" "/f")+ >>= getAttribute "content"+ >>= (`shouldBe` "12") getCatalog "$x='lol' \n with ( 12 ) |$x| { file {'/f': content => $x; } } \n file {'/g': content => $x; }"- >>= getResource (RIdentifier "file" "/g")- >>= getAttribute "content"- >>= (`shouldBe` "lol")+ >>= getResource (RIdentifier "file" "/g")+ >>= getAttribute "content"+ >>= (`shouldBe` "lol") it "should work in value mode" $ getCatalog "$x= with ('a', 'b' ) |$x, $y| { \"${x} and ${y}\" } \n file {'/g': content => $x; }"- >>= getResource (RIdentifier "file" "/g")- >>= getAttribute "content"- >>= (`shouldBe` "a and b")+ >>= getResource (RIdentifier "file" "/g")+ >>= getAttribute "content"+ >>= (`shouldBe` "a and b")
tests/Interpreter/IfSpec.hs view
@@ -1,30 +1,35 @@-{-# LANGUAGE OverloadedLists #-}--- | Directly ported from puppet specs-module Interpreter.IfSpec (spec) where+{-# LANGUAGE OverloadedLists #-} -import Test.Hspec+-- | Directly ported from puppet specs+module Interpreter.IfSpec (spec) where -import Control.Lens+import Control.Lens import qualified Data.Text as Text--import Helpers+import Helpers+import Test.Hspec shouldFail :: [Text] -> Expectation-shouldFail content = let cat = pureCatalog (Text.unlines content)- in cat `shouldSatisfy` has _Left+shouldFail content =+ let cat = pureCatalog (Text.unlines content)+ in cat `shouldSatisfy` has _Left shouldNotFail :: [Text] -> Expectation-shouldNotFail content = let cat = pureCatalog (Text.unlines content)- in cat `shouldSatisfy` has _Right+shouldNotFail content =+ let cat = pureCatalog (Text.unlines content)+ in cat `shouldSatisfy` has _Right spec :: Spec spec = do describe "If" $ do- it "doesn't enter false conditions" $ shouldNotFail- [ "if (false) { fail ':(' }" ]- it "enters true conditions" $ shouldFail- [ "if (true) { fail ':(' }" ]- it "enters empty string conditions" $ shouldFail- [ "if '' { fail ':(' }" ]- it "not (unknown variable) is true" $ shouldFail- [ "if (!$::unknown123) { fail ':(' }" ]+ it "doesn't enter false conditions" $+ shouldNotFail+ ["if (false) { fail ':(' }"]+ it "enters true conditions" $+ shouldFail+ ["if (true) { fail ':(' }"]+ it "enters empty string conditions" $+ shouldFail+ ["if '' { fail ':(' }"]+ it "not (unknown variable) is true" $+ shouldFail+ ["if (!$::unknown123) { fail ':(' }"]
tests/Parser/DT.hs view
@@ -1,13 +1,12 @@ module Parser.DT (spec) where -import Helpers--import Test.Hspec.Megaparsec-import Text.Megaparsec (parse)+import GHC.Base (error)+import Helpers+import Puppet.Parser.Internal+import Test.Hspec.Megaparsec+import Text.Megaparsec (parse) import qualified Text.Regex.PCRE.ByteString.Utils as Regex -import Puppet.Parser.Internal- spec :: Spec spec = do let parsed s r = it ("accepts " <> toS s) $ parse datatype "?" s `shouldParse` r@@ -19,7 +18,9 @@ "String[5]" `parsed` UDTString (Just 5) Nothing "String[5,8]" `parsed` UDTString (Just 5) (Just 8) "Regexp" `parsed` UDTRegexp Nothing- let Right foore = Regex.compile' Regex.compBlank Regex.execBlank "foo"+ let foore = case Regex.compile' Regex.compBlank Regex.execBlank "foo" of+ Right ok -> ok+ Left rr -> error (show rr) "Regexp[/foo/]" `parsed` UDTRegexp (Just (CompRegex "foo" foore)) it "accepts variables" $ pendingWith "to be fixed" *> parse datatype "?" "String[$var]" `shouldParse` UDTString (Just 5) Nothing describe "Stdlib::" $ do
tests/Parser/ExprSpec.hs view
@@ -1,55 +1,67 @@ {-# LANGUAGE OverloadedLists #-}-module Parser.ExprSpec (spec) where -import Test.Hspec-import Test.Hspec.Megaparsec-import Text.Megaparsec+module Parser.ExprSpec (spec) where -import Puppet.Parser-import Puppet.Parser.Internal-import XPrelude+import Puppet.Parser+import Puppet.Parser.Internal+import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Megaparsec+import XPrelude expressions :: [(Text, Expression)] expressions =- [ ("5 + 3 * 2", 5 + 3 * 2)- , ("5+2 == 7", Equal (5 + 2) 7)- , ("include(foo::bar)", Terminal (UFunctionCall "include" ["foo::bar"] ))- , ("fail(('foo'))", Terminal (UFunctionCall "fail" ["foo"] ))- , ("test(foo,bar)", Terminal (UFunctionCall "test" ["foo", "bar"] ))- , ("extlib::test()", Terminal (UFunctionCall "extlib::test" [] ))- , ("extlib::test(fail('foo'))", Terminal (UFunctionCall "extlib::test" [Terminal (UFunctionCall "fail" [Terminal (UString "foo")])]))- , ("test(extlib::test())", Terminal (UFunctionCall "test" [Terminal (UFunctionCall "extlib::test" [])] ))- , ("test ( foo , bar )", Terminal (UFunctionCall "test" ["foo", "bar"] ))- , ("$y ? {\- \ undef => 'undef',\- \ default => 'default',\- \ }", ConditionalValue (Terminal (UVariableReference "y"))- [ SelectorValue UUndef :!: Terminal (UString "undef")- , SelectorDefault :!: Terminal (UString "default")])- , ("$x", Terminal (UVariableReference "x"))- , ("x($y)", Terminal (UFunctionCall "x" [ Terminal (UVariableReference "y") ]))- , ("\"$\"", Terminal (UInterpolable [Terminal (UString "$")]))- , ("\"${x}\"", Terminal (UInterpolable [Terminal (UVariableReference "x")]))- , ("$x[ 3 ]", Lookup (Terminal (UVariableReference "x")) (Terminal (UNumber 3)))- , ("\"${ os[ 'architecture' ]}\"", Terminal (UInterpolable [Lookup (Terminal (UVariableReference "os")) (Terminal (UString "architecture"))]))- , ("\"${facts['os']['architecture']}\"", Terminal (UInterpolable [Lookup (Lookup (Terminal (UVariableReference "facts")) (Terminal (UString "os"))) (Terminal (UString "architecture"))]))- , ("\"${x[$y]}\"", Terminal (UInterpolable [Lookup (Terminal (UVariableReference "x")) (Terminal (UVariableReference "y")) ]))- , ("\"${x($y)}\"", Terminal (UInterpolable [ Terminal (UFunctionCall "x" [ Terminal (UVariableReference "y") ] ) ] ))- , ("\"${x($y)}$'\"", Terminal (UInterpolable [ Terminal (UFunctionCall "x" [ Terminal (UVariableReference "y") ])- , Terminal (UString "$"),Terminal (UString "'")]))- ]+ [ ("5 + 3 * 2", 5 + 3 * 2),+ ("5+2 == 7", Equal (5 + 2) 7),+ ("include(foo::bar)", Terminal (UFunctionCall "include" ["foo::bar"])),+ ("fail(('foo'))", Terminal (UFunctionCall "fail" ["foo"])),+ ("test(foo,bar)", Terminal (UFunctionCall "test" ["foo", "bar"])),+ ("extlib::test()", Terminal (UFunctionCall "extlib::test" [])),+ ("extlib::test(fail('foo'))", Terminal (UFunctionCall "extlib::test" [Terminal (UFunctionCall "fail" [Terminal (UString "foo")])])),+ ("test(extlib::test())", Terminal (UFunctionCall "test" [Terminal (UFunctionCall "extlib::test" [])])),+ ("test ( foo , bar )", Terminal (UFunctionCall "test" ["foo", "bar"])),+ ( "$y ? {\+ \ undef => 'undef',\+ \ default => 'default',\+ \ }",+ ConditionalValue+ (Terminal (UVariableReference "y"))+ [ SelectorValue UUndef :!: Terminal (UString "undef"),+ SelectorDefault :!: Terminal (UString "default")+ ]+ ),+ ("$x", Terminal (UVariableReference "x")),+ ("x($y)", Terminal (UFunctionCall "x" [Terminal (UVariableReference "y")])),+ ("\"$\"", Terminal (UInterpolable [Terminal (UString "$")])),+ ("\"${x}\"", Terminal (UInterpolable [Terminal (UVariableReference "x")])),+ ("$x[ 3 ]", Lookup (Terminal (UVariableReference "x")) (Terminal (UNumber 3))),+ ("\"${ os[ 'architecture' ]}\"", Terminal (UInterpolable [Lookup (Terminal (UVariableReference "os")) (Terminal (UString "architecture"))])),+ ("\"${facts['os']['architecture']}\"", Terminal (UInterpolable [Lookup (Lookup (Terminal (UVariableReference "facts")) (Terminal (UString "os"))) (Terminal (UString "architecture"))])),+ ("\"${x[$y]}\"", Terminal (UInterpolable [Lookup (Terminal (UVariableReference "x")) (Terminal (UVariableReference "y"))])),+ ("\"${x($y)}\"", Terminal (UInterpolable [Terminal (UFunctionCall "x" [Terminal (UVariableReference "y")])])),+ ( "\"${x($y)}$'\"",+ Terminal+ ( UInterpolable+ [ Terminal (UFunctionCall "x" [Terminal (UVariableReference "y")]),+ Terminal (UString "$"),+ Terminal (UString "'")+ ]+ )+ )+ ] invalid :: [Text]-invalid = [ "$os['name]"- -- pending- -- , "$os ['name']"- -- interpolation- , "\"${os['name]}\""- , "\"${os[name}\""- , "\"${os[name]\""- ]+invalid =+ [ "$os['name]",+ -- pending+ -- , "$os ['name']"+ -- interpolation+ "\"${os['name]}\"",+ "\"${os[name}\"",+ "\"${os[name]\""+ ] -testExpression (t,e) = it ("should parse " <> toS t) $ parse (expression <* eof) "" t `shouldParse` e+testExpression (t, e) = it ("should parse " <> toS t) $ parse (expression <* eof) "" t `shouldParse` e testInvalid s = it ("rejects " <> toS s) $ shouldFailOn (parse (expression <* eof) "") s
tests/Parser/LexerSpec.hs view
@@ -1,30 +1,27 @@-module Parser.LexerSpec(spec) where--import XPrelude--import System.FilePath.Glob-import Test.Hspec-import Text.Megaparsec (eof, parse)-import Test.Hspec.Megaparsec+module Parser.LexerSpec (spec) where -import Puppet.Parser-import Puppet.Parser.Internal+import Puppet.Parser+import Puppet.Parser.Internal+import System.FilePath.Glob+import Test.Hspec+import Test.Hspec.Megaparsec+import Text.Megaparsec (eof, parse)+import XPrelude validFiles = do files <- runIO $ globDir1 (compile "*.pp") "tests/Parser/lexer" mapM_ test files- where test fp = do- r <- runIO $ fmap check (readFile fp)+ r <- runIO $ fmap check (readFile fp) it ("should parse " <> fp) r- check i =+ check i = parse (puppetParser <* eof) empty `shouldSucceedOn` i spec = describe "Lexer" $ do- describe "Valid lexer" $ validFiles+ describe "Valid lexer" validFiles describe "Invalid lexer" $ do- it "should fail to parse resource reference with a space after the resource type" $ invalid resourceReference "File ['/test']"+ it "should fail to parse resource reference with a space after the resource type" $ invalid resourceReference "File ['/test']" xit "should fail if there is a space after the variable name" $ invalid interpolableString "\"${os ['name']}\"" -- Utils
tests/PuppetdbSpec.hs view
@@ -1,14 +1,12 @@-module PuppetdbSpec(spec) where--import XPrelude--import qualified Data.Text as Text-import System.IO.Temp as Temp-import Test.Hspec+module PuppetdbSpec (spec) where -import PuppetDB+import qualified Data.Text as Text+import PuppetDB+import System.IO.Temp as Temp+import Test.Hspec+import XPrelude -checkPanicE :: Show x => Text -> ExceptT x IO a -> IO a+checkPanicE :: (Show x) => Text -> ExceptT x IO a -> IO a checkPanicE msg = runExceptT >=> either (panic . ((msg <> " ") <>) . show) return fqdn = "node.site.com"@@ -29,7 +27,7 @@ checkPanicE "replaceFacts" (replaceFacts pdb [(fqdn, nfacts)]) checkPanicE "commitDB" (commitDB pdb) -- check that our custom fact was indeed saved- dblines <- (fmap Text.strip . Text.lines) <$> readFile pdbfile+ dblines <- fmap Text.strip . Text.lines <$> readFile pdbfile dblines `shouldContain` ["customfact: MyCustomFactValue"] -- initiate a new puppetdbapi fpdb <- loadTestDB pdbfile >>= unwrapError "loadTestDB"@@ -38,5 +36,5 @@ checkPanicE "replaceCatalog" (replaceCatalog fpdb (generateWireCatalog fqdn mempty mempty)) checkPanicE "commit 2" (commitDB fpdb) -- check our facts again- fdblines <- (fmap (Text.strip) . Text.lines) `fmap` readFile pdbfile+ fdblines <- (fmap Text.strip . Text.lines) `fmap` readFile pdbfile fdblines `shouldContain` ["customfact: MyCustomFactValue"]
tests/Spec.hs view
@@ -1,8 +1,5 @@-import Test.Hspec--import Helpers- import qualified ErbSpec+import Helpers import qualified HieraSpec import qualified Interpreter.ClassSpec import qualified Interpreter.CollectorSpec@@ -25,6 +22,7 @@ import qualified Parser.ExprSpec import qualified Parser.LexerSpec import qualified PuppetdbSpec+import Test.Hspec main :: IO () main = hspec spec