packages feed

language-puppet-1.5.1: src/Puppet/Interpreter/Helpers.hs

{-# LANGUAGE RankNTypes #-}
{-# OPTIONS_HADDOCK hide, prune, ignore-exports #-}

-- | Internal helpers module.
module Puppet.Interpreter.Helpers where

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 ->
  -- | 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)]
    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)

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

extractPrism :: Doc -> Prism' a b -> a -> InterpreterMonad b
extractPrism msg p a =
  case preview p a of
    Just b -> return b
    Nothing -> throwPosError ("Could not extract prism in" <+> msg)

-- Scope
popScope :: InterpreterMonad ()
popScope = curScope %= List.tail

pushScope :: CurContainerDesc -> InterpreterMonad ()
pushScope s = curScope %= (s :)

getScopeName :: InterpreterMonad Text
getScopeName = scopeName <$> getScope

scopeName :: CurContainerDesc -> Text
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

containerModName :: CurContainerDesc -> Text
containerModName ContRoot = "::"
containerModName (ContImported x) = containerModName x
containerModName (ContClass x) = x
containerModName (ContDefine dt _ _) = dt
containerModName (ContImport _ x) = containerModName x

getScope :: InterpreterMonad CurContainerDesc
{-# INLINEABLE getScope #-}
getScope =
  use curScope >>= \s ->
    if null s
      then throwPosError "Internal error: empty scope!"
      else pure (List.head s)

getCurContainer :: InterpreterMonad CurContainer
{-# INLINEABLE getCurContainer #-}
getCurContainer = do
  scp <- getScopeName
  preuse (scopes . ix scp . scopeContainer) >>= \case
    Just x -> return x
    Nothing -> throwPosError ("Internal error: can't find the current container for" <+> ppline scp)

rcurcontainer :: Resource -> CurContainerDesc
rcurcontainer r = fromMaybe ContRoot (r ^? rscope . _head)

-- Singleton getters available in the InterpreterMonad --
getPuppetPaths :: InterpreterMonad PuppetDirPaths
getPuppetPaths = singleton PuppetPaths

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) = 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

-- | Ask the value of a fact given a specified key
-- The fact set comes from the reader used by the interpreter monad.
askFact :: Text -> InterpreterMonad (Maybe PValue)
askFact key = do
  facts <- singleton Facts
  pure $ lookupFacts key facts

isIgnoredModule :: Text -> InterpreterMonad Bool
isIgnoredModule m = singleton (IsIgnoredModule m)

-- | Throws an error if we are in strict mode
-- A warning in permissive mode
checkStrict ::
  -- | The warning message.
  Doc ->
  -- | The error message.
  Doc ->
  InterpreterMonad ()
checkStrict wrn err = do
  extMod <- isExternalModule
  let priority =
        if extMod
          then Log.INFO
          else Log.WARNING
  str <- singleton IsStrict
  if str && not extMod
    then throwPosError err
    else do
      srcname <- use (curPos . _1 . _sourceName)
      logWriter priority (wrn <+> "at" <+> ppstring srcname)

isExternalModule :: InterpreterMonad Bool
isExternalModule =
  getScope >>= \case
    ContClass n -> isExternal n
    ContDefine n _ _ -> isExternal n
    _ -> return False
  where
    isExternal = singleton . IsExternalModule . List.head . Text.splitOn "::"

-- Logging --
error :: (MonadWriter InterpreterWriter m) => Doc -> m ()
error d = tell [Log.ERROR :!: d]

critical :: (MonadWriter InterpreterWriter m) => Doc -> m ()
critical d = tell [Log.CRITICAL :!: d]

warn :: (MonadWriter InterpreterWriter m) => Doc -> m ()
warn d = tell [Log.WARNING :!: d]

debug :: (MonadWriter InterpreterWriter m) => Doc -> m ()
debug d = tell [Log.DEBUG :!: d]

logWriter :: (MonadWriter InterpreterWriter m) => Log.Priority -> Doc -> m ()
logWriter prio d = tell [prio :!: d]

safeDecodeUtf8 :: ByteString -> InterpreterMonad Text
{-# INLINEABLE safeDecodeUtf8 #-}
safeDecodeUtf8 i = pure (Text.decodeUtf8 i)

normalizeRIdentifier :: Text -> Text -> RIdentifier
normalizeRIdentifier = RIdentifier . dropInitialColons

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)
              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)