language-puppet-1.3.13: src/Puppet/NativeTypes/Helpers.hs
{-| These are the function and data types that are used to define the Puppet
native types.
-}
module Puppet.NativeTypes.Helpers
( module Puppet.PP
, ipaddr
, nativetypemethods
, paramname
, rarray
, string
, strings
, string_s
, noTrailingSlash
, fullyQualified
, fullyQualifieds
, values
, defaultvalue
, concattype
, nameval
, defaultValidate
, NativeTypeName
, parameterFunctions
, integer
, integers
, mandatory
, mandatoryIfNotAbsent
, inrange
, faketype
, defaulttype
, runarray
, perror
, validateSourceOrContent
) where
import Puppet.Prelude
import qualified Text.Read
import Data.Aeson.Lens (_Integer, _Number)
import Data.Char (isDigit)
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as HS
import qualified Data.Text as Text
import qualified Data.Vector as V
import Puppet.Interpreter.PrettyPrinter ()
import Puppet.Interpreter.Types
import Puppet.PP hiding (integer, string)
import qualified Text.PrettyPrint.ANSI.Leijen as P
type NativeTypeName = Text
paramname :: Text -> Doc
paramname = red . ttext
-- | Useful helper for buiding error messages
perror :: Doc -> Either PrettyError Resource
perror = Left . PrettyError
-- | Smart constructor for 'NativeTypeMethods'.
nativetypemethods :: [(Text, [Text -> NativeTypeValidate])] -> NativeTypeValidate -> NativeTypeMethods
nativetypemethods def extraV =
let params = fromKeys def
in NativeTypeMethods (defaultValidate params >=> parameterFunctions def >=> extraV) params
where
fromKeys = HS.fromList . map fst
faketype :: NativeTypeName -> (NativeTypeName, NativeTypeMethods)
faketype tname = (tname, NativeTypeMethods Right HS.empty)
concattype :: NativeTypeName -> (NativeTypeName, NativeTypeMethods)
concattype tname = (tname, NativeTypeMethods (defaultValidate HS.empty) HS.empty)
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)
-}
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 $ HM.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 = HM.filter def $ HM.union p defaults
defaults = HM.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
{-| This checks that a given parameter is a string. If it is a 'ResolvedInt' or
'ResolvedBool' it will convert them to strings.
-}
string :: Text -> NativeTypeValidate
string param res = case res ^. rattributes . at param of
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
string_s :: Text -> NativeTypeValidate
string_s param res = case res ^. rattributes . at param of
Nothing -> Right res
Just (PArray _) -> strings 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")
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
-- | 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 ttext valuelist) <+> "and not" <+> ttext x
Just x -> perror $ "Parameter" <+> paramname param <+> "value should be one of" <+> list (map ttext valuelist) <+> "and not" <+> pretty x
Nothing -> Right res
-- | This fills the default values of unset parameters.
defaultvalue :: Text -> Text -> NativeTypeValidate
defaultvalue value param = Right . over (rattributes . at param) (Just . fromMaybe (PString value))
-- | Checks that a given parameter, if set, is a 'ResolvedInt'.
-- If it is a 'PString' it will attempt to parse it.
integer :: Text -> NativeTypeValidate
integer prm res = string prm res >>= integer' prm
where
integer' pr rs = case rs ^. rattributes . at pr of
Just x -> integer'' prm x res
Nothing -> Right rs
integers :: Text -> NativeTypeValidate
integers param = runarray param integer''
integer'' :: Text -> PValue -> NativeTypeValidate
integer'' param val res = case val ^? _Integer of
Just v -> Right (res & rattributes . at param ?~ PNumber (fromIntegral v))
_ -> perror $ "Parameter" <+> paramname param <+> "must be an integer"
-- | Copies the "name" value into the parameter if this is not set.
-- It implies the `string` validator.
nameval :: Text -> NativeTypeValidate
nameval prm res = string prm res
>>= \r -> case r ^. rattributes . at prm of
Just (PString al) -> Right (res & rid . iname .~ al)
Just x -> perror ("The alias must be a string, not" <+> pretty x)
Nothing -> Right (r & rattributes . at prm ?~ PString (r ^. rid . iname))
-- | Checks that a given parameter is set unless the resources "ensure" is set to absent
mandatoryIfNotAbsent :: Text -> NativeTypeValidate
mandatoryIfNotAbsent param res = case res ^. rattributes . at param of
Just _ -> Right res
Nothing -> case res ^. rattributes . at "ensure" of
Just "absent" -> Right res
_ -> perror $ "Parameter" <+> paramname param <+> "should be set."
-- | 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."
-- | 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
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
parameterFunctions'' param r validationfunction = validationfunction param r
-- 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
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
_ -> Right res
fullyQualifieds :: Text -> NativeTypeValidate
fullyQualifieds param = runarray param fullyQualified'
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" <+> ttext 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
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
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 "" _ = 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
inrange :: Integer -> Integer -> Text -> NativeTypeValidate
inrange mi ma param res =
let va = res ^. rattributes . at param
na = va ^? traverse . _Number
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 P.<> "'s value should be between" <+> P.integer mi <+> "and" <+> P.integer ma
(Just x,_) -> perror $ "Parameter" <+> paramname param <+> "should be an integer, and not" <+> pretty x
validateSourceOrContent :: NativeTypeValidate
validateSourceOrContent res = let
parammap = res ^. rattributes
source = HM.member "source" parammap
content = HM.member "content" parammap
in if source && content
then perror "Source and content can't be specified at the same time"
else Right res