language-puppet 0.12.0 → 0.12.1
raw patch · 9 files changed
+132/−47 lines, 9 filesdep ~containersdep ~hspecdep ~pcre-utils
Dependency ranges changed: containers, hspec, pcre-utils
Files
- Facter.hs +11/−2
- Puppet/Daemon.hs +1/−1
- Puppet/Interpreter.hs +22/−15
- Puppet/Interpreter/PrettyPrinter.hs +1/−1
- Puppet/Interpreter/Types.hs +3/−3
- Puppet/Lens.hs +2/−0
- Puppet/Testing.hs +2/−2
- language-puppet.cabal +3/−3
- progs/PuppetResources.hs +87/−20
Facter.hs view
@@ -13,8 +13,9 @@ import System.Posix.User import System.Posix.Unistd (getSystemID, SystemID(..)) import Data.List.Split (splitOn)-import Data.List (intercalate)+import Data.List (intercalate,stripPrefix) import System.Environment+import Data.Maybe (mapMaybe) storageunits :: [(String, Int)] storageunits = [ ("", 0), ("K", 1), ("M", 2), ("G", 3), ("T", 4) ]@@ -53,6 +54,7 @@ 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)]@@ -123,12 +125,19 @@ path <- getEnv "PATH" return [ ("path", path) ] +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 :")) . stripPrefix "model name") (lines cpuinfo)+ return $ ("processorcount", show (length cpuinfos)) : cpuinfos+ puppetDBFacts :: T.Text -> PuppetDBAPI -> IO (Container T.Text) puppetDBFacts ndename pdbapi = getFacts pdbapi (QEqual FCertname ndename) >>= \case S.Right facts@(_:_) -> return (HM.fromList (map (\f -> (f ^. factname, f ^. factval)) facts)) _ -> do- rawFacts <- fmap concat (sequence [factNET, factRAM, factOS, fversion, factMountPoints, factOS, factUser, factUName, fenv])+ rawFacts <- fmap concat (sequence [factNET, factRAM, factOS, fversion, factMountPoints, factOS, factUser, factUName, fenv, factProcessor]) let ofacts = genFacts $ map (T.pack *** T.pack) rawFacts (hostname, ddomainname) = T.break (== '.') ndename domainname = if T.null ddomainname
Puppet/Daemon.hs view
@@ -107,7 +107,7 @@ -> HieraQueryFunc -> T.Text -> Facts- -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog))+ -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) gCatalog prefs getStatements getTemplate stats hquery ndename facts = do logDebug ("Received query for node " <> ndename) traceEventIO ("Received query for node " <> T.unpack ndename)
Puppet/Interpreter.hs view
@@ -34,6 +34,12 @@ vmapM :: (Monad m, Foldable t) => (a -> m b) -> t a -> m [b] vmapM f = mapM f . toList +-- | This is the main function for computing catalogs. It returns the+-- result of the compulation (either an error, or a tuple containing all+-- the resources, dependency map, exported resources, and defined resources+-- (this last one might not be up to date and is only useful for code+-- coverage tests)) along with all messages that have been generated by the+-- compilation process. getCatalog :: ( TopLevelType -> T.Text -> IO (S.Either Doc Statement) ) -- ^ get statements function -> (Either T.Text T.Text -> T.Text -> Container ScopeInformation -> IO (S.Either Doc T.Text)) -- ^ compute template function -> PuppetDBAPI@@ -42,7 +48,7 @@ -> Container PuppetTypeMethods -- ^ List of native types -> Container ( [PValue] -> InterpreterMonad PValue ) -> HieraQueryFunc -- ^ Hiera query function- -> IO (Pair (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog)) [Pair Priority Doc])+ -> IO (Pair (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) [Pair Priority Doc]) getCatalog gtStatement gtTemplate pdbQuery ndename facts nTypes extfuncs hquery = do let rdr = InterpreterReader nTypes gtStatement gtTemplate pdbQuery extfuncs ndename hquery dummypos = initialPPos "dummy"@@ -58,7 +64,7 @@ isParent _ (ContImport _ _) = return False -- no relationship through import isParent _ ContRoot = return False isParent _ (ContImported _) = return False-isParent _ (ContDefine _ _) = return False+isParent _ (ContDefine _ _ _) = return False isParent cur (ContClass possibleparent) = do preuse (scopes . ix cur . scopeParent) >>= \case Nothing -> throwPosError ("Internal error: could not find scope" <+> ttext cur <+> "possible parent" <+> ttext possibleparent)@@ -146,7 +152,7 @@ S.Right x -> evalTopLevel x S.Left y -> throwPosError y -computeCatalog :: T.Text -> InterpreterMonad (FinalCatalog, EdgeMap, FinalCatalog)+computeCatalog :: T.Text -> InterpreterMonad (FinalCatalog, EdgeMap, FinalCatalog, [Resource]) computeCatalog ndename = do (restop, node) <- getstt TopNode ndename let finalStep [] = return []@@ -174,7 +180,8 @@ _ -> curr :!: cure verified <- fmap (ifromList . map (\r -> (r ^. rid, r))) $ mapM validateNativeType (toList real) mp <- makeEdgeMap verified- return (verified, mp, exported)+ definedRes <- use definedResources+ return (verified, mp, exported, HM.elems definedRes) dependencyErrors :: [T.Tree G.Vertex] -> (G.Vertex -> (RIdentifier, RIdentifier, [RIdentifier])) -> InterpreterMonad () dependencyErrors _ _ = throwPosError "Undefined dependency cycle"@@ -200,11 +207,11 @@ let containerMap :: HM.HashMap RIdentifier [RIdentifier] !containerMap = ifromListWith (<>) $ do r <- toList ct- let toResource ContRoot = return $ RIdentifier "class" "::"- toResource (ContClass cn) = return $ RIdentifier "class" cn- toResource (ContDefine t n) = return $ RIdentifier t n- toResource (ContImported _) = mzero- toResource (ContImport _ _) = mzero+ let toResource ContRoot = return $ RIdentifier "class" "::"+ toResource (ContClass cn) = return $ RIdentifier "class" cn+ toResource (ContDefine t n _) = return $ RIdentifier t n+ toResource (ContImported _) = mzero+ toResource (ContImport _ _) = mzero o <- toResource (rcurcontainer r) return (o, [r ^. rid]) -- This function uses the previous map in order to resolve to non@@ -539,7 +546,7 @@ modulename = case T.splitOn "::" deftype of [] -> deftype (x:_) -> x- let curContType = ContDefine deftype defname+ let curContType = ContDefine deftype defname (r ^. rpos) p <- use curPos void $ enterScope SENormal curContType modulename p (spurious, dls) <- getstt TopDefine deftype@@ -676,11 +683,11 @@ let !defaulttags = {-# SCC "rrGetTags" #-} HS.fromList (rt : classtags) <> tgs allsegs x = x : T.splitOn "::" x !classtags = getClassTags cnt- getClassTags (ContClass cn ) = allsegs cn- getClassTags (ContDefine dt _) = allsegs dt- getClassTags (ContRoot ) = []- getClassTags (ContImported _ ) = []- getClassTags (ContImport _ _ ) = []+ getClassTags (ContClass cn ) = allsegs cn+ getClassTags (ContDefine dt _ _) = allsegs dt+ getClassTags (ContRoot ) = []+ getClassTags (ContImported _ ) = []+ getClassTags (ContImport _ _ ) = [] allScope <- use curScope fqdn <- view thisNodename let baseresource = Resource (RIdentifier rt rn) (HS.singleton rn) mempty mempty allScope vrt defaulttags p fqdn
Puppet/Interpreter/PrettyPrinter.hs view
@@ -90,7 +90,7 @@ pretty (ContImported x) = magenta "imported" <> braces (pretty x) pretty ContRoot = dullyellow (text "::") pretty (ContClass cname) = dullyellow (text "class") <+> dullgreen (text (T.unpack cname))- pretty (ContDefine dtype dname) = pretty (PResourceReference dtype dname)+ pretty (ContDefine dtype dname _) = pretty (PResourceReference dtype dname) instance Pretty ResDefaults where pretty (ResDefaults t _ v p) = capitalize t <+> showPPos p <$> containerComma v
Puppet/Interpreter/Types.hs view
@@ -109,7 +109,7 @@ data CurContainerDesc = ContRoot -- ^ Contained at node or root level | ContClass !T.Text -- ^ Contained in a class- | ContDefine !T.Text !T.Text -- ^ Contained in a define+ | ContDefine !T.Text !T.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 deriving (Eq, Generic, Ord)@@ -238,7 +238,7 @@ type FinalCatalog = HM.HashMap RIdentifier Resource -data DaemonMethods = DaemonMethods { _dGetCatalog :: T.Text -> Facts -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog))+data DaemonMethods = DaemonMethods { _dGetCatalog :: T.Text -> Facts -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) -- ^ The most important function, computing catalogs. Given a node name and a list of facts, it returns the result of the catalog compilation : either an error, or a tuple containing all the resources in this catalog, the dependency map, the exported resources, and a list of known resources, that might not be up to date, but are here for code coverage tests. , _dParserStats :: MStats , _dCatalogStats :: MStats , _dTemplateStats :: MStats@@ -345,7 +345,7 @@ scopeName (ContRoot ) = "::" scopeName (ContImported x ) = "::imported::" `T.append` scopeName x scopeName (ContClass x ) = x-scopeName (ContDefine dt dn) = "#define/" `T.append` dt `T.append` "/" `T.append` dn+scopeName (ContDefine dt dn _) = "#define/" `T.append` dt `T.append` "/" `T.append` dn scopeName (ContImport _ x ) = "::import::" `T.append` scopeName x getScopeName :: InterpreterMonad T.Text
Puppet/Lens.hs view
@@ -144,10 +144,12 @@ sget (DefineDeclaration _ _ s _) = s sget (Node _ s _ _) = s sget (TopContainer s _) = s+ sget (SHFunctionCall (HFunctionCall _ _ _ s _) _) = s sget _ = V.empty sset :: Statement -> V.Vector Statement -> Statement sset (ClassDeclaration n args inh _ p) s = ClassDeclaration n args inh s p sset (Node ns _ nd' p) s = Node ns s nd' p sset (DefineDeclaration n args _ p) s = DefineDeclaration n args s p sset (TopContainer _ p) s = TopContainer s p+ sset (SHFunctionCall (HFunctionCall t e pr _ e2) p) s = SHFunctionCall (HFunctionCall t e pr s e2) p sset x _ = x
Puppet/Testing.hs view
@@ -110,7 +110,7 @@ testingDaemon :: PuppetDBAPI -- ^ Contains the puppetdb API functions -> FilePath -- ^ Path to the manifests -> (T.Text -> IO (Container T.Text)) -- ^ The facter function- -> IO (T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog)))+ -> IO (T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource]))) testingDaemon pdb pdir allFacts = do LOG.updateGlobalLogger "Puppet.Daemon" (LOG.setLevel LOG.WARNING) prefs <- genPreferences pdir@@ -118,7 +118,7 @@ return (\nodname -> allFacts nodname >>= _dGetCatalog q nodname) -- | A default testing daemon.-defaultDaemon :: FilePath -> IO (T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog)))+defaultDaemon :: FilePath -> IO (T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource]))) defaultDaemon pdir = do pdb <- getDefaultDB PDBTest >>= \case S.Left x -> error (show x)
language-puppet.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: language-puppet-version: 0.12.0+version: 0.12.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, simulationg of complex interactions between nodes, Puppet master replacement, and more ! homepage: http://lpuppet.banquise.net/@@ -100,7 +100,7 @@ , time == 1.4.* , filecache >= 0.2.5 && < 0.3 , regex-pcre-builtin >= 0.94.4- , pcre-utils >= 0.1.0.1 && < 0.2+ , pcre-utils >= 0.1.2 && < 0.2 , process >= 1.1 && < 1.3 , iconv == 0.4.* , http-types == 0.8.*@@ -158,7 +158,7 @@ extensions: BangPatterns, OverloadedStrings ghc-options: -Wall -rtsopts -threaded -with-rtsopts "-A2M" ghc-prof-options: -auto-all -caf-all -fprof-auto- build-depends: language-puppet,base,text,parsec,vector,ansi-wl-pprint,bytestring,mtl,hslogger,Diff,unordered-containers,strict-base-types,optparse-applicative,regex-pcre-builtin,lens,aeson,yaml,parallel-io+ build-depends: language-puppet,base,text,parsec,vector,ansi-wl-pprint,bytestring,mtl,hslogger,Diff,unordered-containers,strict-base-types,optparse-applicative,regex-pcre-builtin,lens,aeson,yaml,parallel-io,containers,Glob,hspec main-is: PuppetResources.hs executable pdbquery
progs/PuppetResources.hs view
@@ -104,6 +104,7 @@ import System.IO import qualified Data.HashMap.Strict as HM+import qualified Data.Set as Set import qualified System.Log.Logger as LOG import qualified Data.ByteString.Lazy.Char8 as BSL import qualified Data.Text as T@@ -119,6 +120,13 @@ import Data.Aeson (encode) import Data.Yaml (decodeFileEither) import Control.Lens as L+import Control.Concurrent.ParallelIO (parallel)+import Data.Maybe (mapMaybe)+import qualified System.FilePath.Glob as G+import Data.Either (partitionEithers)+import Data.List (isInfixOf)+import qualified Test.Hspec.Runner as H+import System.Exit (exitFailure, exitSuccess) import Facter @@ -136,14 +144,12 @@ import PuppetDB.Common import Puppet.Testing hiding ((<$>)) import Puppet.Lens--import Control.Concurrent.ParallelIO (parallel_)-import Data.Maybe (mapMaybe)+import Puppet.Stats tshow :: Show a => a -> T.Text tshow = T.pack . show -type QueryFunc = T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog))+type QueryFunc = T.Text -> IO (S.Either Doc (FinalCatalog, EdgeMap, FinalCatalog, [Resource])) checkErrorStrict :: S.Either Doc x -> IO x checkErrorStrict (S.Left rr) = putDoc rr >> putStrLn "" >> error "error!"@@ -154,13 +160,13 @@ hackish as it will generate facts from the local computer ! -} -initializedaemonWithPuppet :: LOG.Priority -> PuppetDBAPI -> FilePath -> Maybe FilePath -> (Facts -> Facts) -> IO QueryFunc+initializedaemonWithPuppet :: LOG.Priority -> PuppetDBAPI -> FilePath -> Maybe FilePath -> (Facts -> Facts) -> IO (QueryFunc, MStats, MStats, MStats) initializedaemonWithPuppet prio pdbapi puppetdir hierapath overrideFacts = do LOG.updateGlobalLogger "Puppet.Daemon" (LOG.setLevel prio) q <- fmap ((prefPDB .~ pdbapi) . (hieraPath .~ hierapath)) (genPreferences puppetdir) >>= initDaemon let f ndename = fmap overrideFacts (puppetDBFacts ndename pdbapi) >>= _dGetCatalog q ndename- return f+ return (f, _dParserStats q, _dCatalogStats q, _dTemplateStats q) parseFile :: FilePath -> IO (Either P.ParseError (V.Vector Statement)) parseFile fp = T.readFile fp >>= runMyParser puppetParser fp@@ -252,7 +258,7 @@ <> help "Puppet directory") nn = strOption ( long "node" <> short 'o'- <> help "Node name. Using 'allnodes' enables a special mode where all nodes present in site.pp are tried. Run with +RTS -N")+ <> help "Node name. Using 'allnodes' enables a special mode where all nodes present in site.pp are tried. Run with +RTS -N. Using 'deadcode' will do the same, but will print warnings about code that's not being used.") pdbfile = strOption ( long "pdbfile" <> help "Path to the testing PuppetDB file.") hiera = strOption ( long "hiera"@@ -283,6 +289,41 @@ isBool (PBoolean False) = Just "false" isBool _ = Nothing +-- this finds the dead code+findDeadCode :: String -> [Resource] -> Set.Set FilePath -> IO ()+findDeadCode puppetdir catalogs allfiles = do+ -- first collect all files / positions from all the catalogs+ let allpositions = Set.fromList $ catalogs ^.. traverse . rpos+ -- now find all haskell files+ puppetfiles <- Set.fromList . concat . fst <$> G.globDir [G.compile "**/*.pp"] (puppetdir <> "/modules")+ let deadfiles = Set.filter ("/manifests/" `isInfixOf`) $ puppetfiles `Set.difference` allfiles+ usedfiles = puppetfiles `Set.intersection` allfiles+ unless (Set.null deadfiles) $ do+ putDoc ("The following files" <+> int (Set.size deadfiles) <+> "are not used: " <> list (map string $ Set.toList deadfiles))+ putStrLn ""+ allparses <- parallel (map parseFile (Set.toList usedfiles))+ let (parseFailed, parseSucceeded) = partitionEithers allparses+ unless (null parseFailed) $ do+ putDoc ("The following" <+> int (length parseFailed) <+> "files could not be parsed:" </> indent 4 (vcat (map (string . show) parseFailed)))+ putStrLn ""+ let getSubStatements s@(ResourceDeclaration{}) = [s]+ getSubStatements (ConditionalStatement conds _) = conds ^.. traverse . _2 . tgt+ getSubStatements s@(ClassDeclaration{}) = extractPrism s+ getSubStatements s@(DefineDeclaration{}) = extractPrism s+ getSubStatements s@(Node{}) = extractPrism s+ getSubStatements s@(SHFunctionCall{}) = extractPrism s+ getSubStatements (TopContainer v s) = getSubStatements s ++ v ^.. tgt+ getSubStatements _ = []+ tgt = folded . to getSubStatements . folded+ extractPrism s = s ^.. _Statements . traverse . to getSubStatements . traverse+ allResources = parseSucceeded ^.. folded . folded . to getSubStatements . folded+ deadResources = filter isDead allResources+ isDead (ResourceDeclaration _ _ _ _ pp) = not $ Set.member pp allpositions+ isDead _ = True+ unless (null deadResources) $ do+ putDoc ("The following" <+> int (length deadResources) <+> "resource declarations are not used:" </> indent 4 (vcat (map pretty deadResources)))+ putStrLn ""+ run :: CommandLine -> IO () run (CommandLine _ _ _ _ _ f Nothing _ _ _ _ _) = parseFile f >>= \case Left rr -> error ("parse error:" ++ show rr)@@ -301,12 +342,14 @@ (Just p, Nothing) -> HM.union `fmap` loadFactsOverrides p (Nothing, Just p) -> (flip HM.union) `fmap` loadFactsOverrides p (Nothing, Nothing) -> return id- queryfunc <- initializedaemonWithPuppet prio pdbapi puppetdir hpath factsOverrides+ (queryfunc,mPStats,_,_) <- initializedaemonWithPuppet prio pdbapi puppetdir hpath factsOverrides printFunc <- hIsTerminalDevice stdout >>= \isterm -> return $ \x -> if isterm then putDoc x >> putStrLn "" else displayIO stdout (renderCompact x) >> putStrLn ""- if tnodename == "allnodes"+ let allnodes = tnodename == "allnodes" || deadcode+ deadcode = tnodename == "deadcode"+ exit <- if allnodes then do allstmts <- parseFile (puppetdir <> "/manifests/site.pp") >>= \presult -> case presult of Left rr -> error (show rr)@@ -314,17 +357,38 @@ let topnodes = mapMaybe getNodeName (V.toList allstmts) getNodeName (Node (NodeName n) _ _ _) = Just n getNodeName _ = Nothing- parallel_ (map (computeCatalogs True queryfunc pdbapi printFunc c) topnodes)+ cats <- parallel (map (computeCatalogs True queryfunc pdbapi printFunc c) topnodes) putStrLn ("Tested " ++ show (length topnodes) ++ " nodes.")- else computeCatalogs False queryfunc pdbapi printFunc c tnodename+ -- the the parsing statistics, so that we known which files+ -- were parsed+ pStats <- getStats mPStats+ -- merge all the resources together+ let cc = mapMaybe fst cats+ testFailures = getSum (cats ^. traverse . _2 . _Just . to (Sum . H.summaryFailures))+ allres = (cc ^.. folded . _1 . folded) ++ (cc ^.. folded . _2 . folded)+ allfiles = Set.fromList $ map T.unpack $ HM.keys pStats+ when deadcode $ findDeadCode puppetdir allres allfiles+ return $ if testFailures > 0+ then exitFailure+ else exitSuccess+ else do+ r <- computeCatalogs False queryfunc pdbapi printFunc c tnodename+ return $ case snd r of+ Just s -> if (H.summaryFailures s > 0)+ then exitFailure+ else exitSuccess+ Nothing -> exitSuccess void $ commitDB pdbapi+ exit -computeCatalogs :: Bool -> QueryFunc -> PuppetDBAPI -> (Doc -> IO ()) -> CommandLine -> T.Text -> IO ()+computeCatalogs :: Bool -> QueryFunc -> PuppetDBAPI -> (Doc -> IO ()) -> CommandLine -> T.Text -> IO (Maybe (FinalCatalog, [Resource]), Maybe H.Summary) computeCatalogs testOnly queryfunc pdbapi printFunc (CommandLine _ showjson showcontent mrt mrn puppetdir _ _ _ _ _ _) tnodename = queryfunc tnodename >>= \case- S.Left rr -> if testOnly- then putDoc ("Problem with" <+> ttext tnodename <+> ":" <+> rr </> mempty)- else putDoc rr >> putStrLn "" >> error "error!"- S.Right (rawcatalog,m,rawexported) -> do+ S.Left rr -> do+ if testOnly+ then putDoc ("Problem with" <+> ttext tnodename <+> ":" <+> rr </> mempty)+ else putDoc rr >> putStrLn "" >> error "error!"+ return (Nothing, Just (H.Summary 1 1))+ S.Right (rawcatalog,m,rawexported,knownRes) -> do let wireCatalog = generateWireCatalog tnodename (rawcatalog <> rawexported) m void $ replaceCatalog pdbapi wireCatalog let cmpMatch Nothing _ curcat = return curcat@@ -336,23 +400,26 @@ Right Nothing -> return False _ -> return True filterCatalog = cmpMatch mrt (_1 . itype . unpacked) >=> cmpMatch mrn (_1 . iname . unpacked)- case (testOnly, showcontent, showjson) of- (True, _, _) -> void $ testCatalog tnodename puppetdir rawcatalog basicTest- (_, _, True) -> BSL.putStrLn (encode (prepareForPuppetApply wireCatalog))+ testResult <- case (testOnly, showcontent, showjson) of+ (True, _, _) -> Just `fmap` testCatalog tnodename puppetdir rawcatalog basicTest+ (_, _, True) -> BSL.putStrLn (encode (prepareForPuppetApply wireCatalog)) >> return Nothing (_, True, _) -> do catalog <- filterCatalog rawcatalog unless (mrt == Just "file" || mrt == Nothing) (error $ "Show content only works for file, not for " ++ show mrt) case mrn of Just f -> printContent f catalog Nothing -> error "You should supply a resource name when using showcontent"+ return Nothing _ -> do catalog <- filterCatalog rawcatalog exported <- filterCatalog rawexported- void $ testCatalog tnodename puppetdir rawcatalog basicTest+ r <- testCatalog tnodename puppetdir rawcatalog basicTest printFunc (pretty (HM.elems catalog)) unless (HM.null exported) $ do printFunc (mempty <+> dullyellow "Exported:" <+> mempty) printFunc (pretty (HM.elems exported))+ return (Just r)+ return (Just (rawcatalog <> rawexported, knownRes), testResult) main :: IO () main = execParser pinfo >>= run