summaryrefslogtreecommitdiff
path: root/src/Propellor
diff options
context:
space:
mode:
authorNicolas Schodet2020-06-29 00:24:52 +0200
committerNicolas Schodet2020-06-29 20:33:50 +0200
commit72de4dd5717e954c2460338d1c6e89860e70c6e6 (patch)
treecbd1c809ed92c4af0075b13e3ed124f8cf72156e /src/Propellor
parenta8fbde90ccb39e21430e9a93304cb6a56e197896 (diff)
Add Icinga 2 supporticinga2
Diffstat (limited to 'src/Propellor')
-rw-r--r--src/Propellor/Property/Icinga2.hs191
1 files changed, 191 insertions, 0 deletions
diff --git a/src/Propellor/Property/Icinga2.hs b/src/Propellor/Property/Icinga2.hs
new file mode 100644
index 00000000..8f6f5eeb
--- /dev/null
+++ b/src/Propellor/Property/Icinga2.hs
@@ -0,0 +1,191 @@
+-- | Maintainer: Nicolas Schodet <nico@ni.fr.eu.org>
+--
+-- Support for Icinga2 monitoring system.
+
+module Propellor.Property.Icinga2 (
+ ApiPermissions(..),
+ allApiPermissions,
+ webApiPermissions,
+ installed,
+ restarted,
+ reloaded,
+ idoMysqlConfigured,
+ apiConfigured,
+ apiUserGranted,
+ webInstalled,
+ confAdjustSection,
+ confContainsSetting,
+ confFileContains,
+ confHosts,
+ confServices,
+ confQuote,
+) where
+
+import Propellor.Base
+import Data.List
+import qualified Propellor.Property.Apt as Apt
+import qualified Propellor.Property.ConfFile as ConfFile
+import qualified Propellor.Property.File as File
+import qualified Propellor.Property.Service as Service
+
+-- | API User permissions.
+newtype ApiPermissions = ApiPermissions [String]
+
+-- | Allow everything.
+allApiPermissions :: ApiPermissions
+allApiPermissions = ApiPermissions ["*"]
+
+-- | Needed permissions for Icinga Web 2.
+webApiPermissions :: ApiPermissions
+webApiPermissions = ApiPermissions
+ [ "status/query"
+ , "actions/*"
+ , "objects/modify/*"
+ , "objects/query/*" ]
+
+-- | A section in configuration, need to be the full section description.
+type ConfSection = String
+
+-- | A key in configuration.
+type ConfKey = String
+
+-- | A configuration statement, can be multiline.
+type ConfStatement = [String]
+
+-- | Make sure Icinga 2 is installed.
+installed :: Property DebianLike
+installed = Apt.installed ["icinga2"]
+
+-- | Restart Icinga 2.
+restarted :: Property DebianLike
+restarted = Service.restarted "icinga2"
+
+-- | Reload Icinga 2 configuration.
+reloaded :: Property DebianLike
+reloaded =
+ Service.reloaded "icinga2" `before` delay
+ where
+ -- Reloading too fast causes problems, systemd waits forever.
+ delay = cmdProperty "sleep" ["1"] `assume` MadeChange
+
+-- | Configure IDO MySQL.
+idoMysqlConfigured :: Property DebianLike
+idoMysqlConfigured =
+ enabled
+ `describe` "icinga2 IDO MySQL feature enabled"
+ `requires` installed
+ `requires` Apt.installed ["icinga2-ido-mysql"]
+ `onChange` restarted
+ where
+ enabled = pathEnabled `File.isSymlinkedTo` pathAvailable
+ pathEnabled = "/etc/icinga2/features-enabled/ido-mysql.conf"
+ pathAvailable = File.LinkTarget "../features-available/ido-mysql.conf"
+
+-- | Configure and enable the API.
+apiConfigured :: Property DebianLike
+apiConfigured =
+ check (not <$> isConfigured) go
+ `requires` installed
+ `onChange` restarted
+ where
+ isConfigured :: IO Bool
+ isConfigured = doesFileExist "/etc/icinga2/features-enabled/api.conf"
+ go = cmdProperty "icinga2" ["api", "setup"]
+
+-- | Create an API user and grant permissions.
+apiUserGranted
+ :: IsContext c
+ => User
+ -> ApiPermissions
+ -> c
+ -> Property (HasInfo + DebianLike)
+apiUserGranted (User username) (ApiPermissions perms) context =
+ go
+ `requires` apiConfigured
+ where
+ go :: Property (HasInfo + DebianLike)
+ go = withPrivData (Password username) context $ \getpassword ->
+ property' desc $ \w -> getpassword $ \priv -> ensureProperty w $
+ goprop $ privDataVal priv
+ goprop :: String -> Property DebianLike
+ goprop password = confAdjustSection desc section (adj password) (ins password) usersFile
+ desc = ("user " ++ username ++ " granted API usage")
+ section = "object ApiUser " ++ (confQuote username)
+ usersFile = "conf.d/api-users.conf"
+ adj password = const
+ [ section ++ " {"
+ , " password = " ++ (confQuote password)
+ , " permissions = [ " ++ qperms ++ " ]" ]
+ ins password ls = ls ++ [""] ++ (adj password []) ++ ["}"]
+ qperms = intercalate ", " $ map confQuote $ perms
+
+-- | Install Icinga Web 2.
+webInstalled :: Property DebianLike
+webInstalled = Apt.installed ["icingaweb2"]
+
+-- | Adjust a section of Icinga configuration file.
+confAdjustSection
+ :: Desc
+ -> ConfSection
+ -> ConfFile.AdjustSection
+ -> ConfFile.InsertSection
+ -> FilePath
+ -> Property DebianLike
+confAdjustSection desc section adj ins f =
+ ConfFile.adjustSection desc start past adj ins conff
+ `onChange` reloaded
+ where
+ start = (== (section ++ " {"))
+ past = (== "}")
+ conff = "/etc/icinga2" </> f
+
+-- | Ensures that a configuration file contains a section with a key=value
+-- setting. Works for simple item using a simple format (single line).
+confContainsSetting
+ :: FilePath
+ -> (ConfSection, ConfKey, String)
+ -> Property DebianLike
+confContainsSetting f (section, key, value) = confAdjustSection
+ (f ++ " section '" ++ section ++ "' contains " ++ key ++ "=" ++ value)
+ section
+ go
+ (++ confsection)
+ f
+ where
+ confkey = " " ++ key ++ " = "
+ confline = confkey ++ value
+ go [] = [confline]
+ go (l:ls) = if isKeyVal l then confline : ls else l : go ls
+ isKeyVal = (confkey `isPrefixOf`)
+ confsection = ["", section ++ " {", confline, "}"]
+
+-- | Replace a full configuration file.
+confFileContains :: FilePath -> [ConfStatement] -> Property DebianLike
+confFileContains f stmts =
+ conff `File.hasContent` (concat stmts)
+ `onChange` reloaded
+ where
+ conff = "/etc/icinga2" </> f
+
+-- | Replace the full host definition.
+confHosts :: [ConfStatement] -> Property DebianLike
+confHosts hosts = confFileContains "conf.d/hosts.conf" hosts
+
+-- | Replace the full services definition.
+confServices :: [ConfStatement] -> Property DebianLike
+confServices services = confFileContains "conf.d/services.conf" services
+
+-- | Quote a string to be included in configuration.
+confQuote :: String -> String
+confQuote s =
+ ['"'] ++ (concatMap escape s) ++ ['"']
+ where
+ escape c
+ | c == '"' = ['\\', '"']
+ | c == '\\' = ['\\', '\\']
+ | c == '\t' = ['\\', 't']
+ | c == '\r' = ['\\', 'r']
+ | c == '\n' = ['\\', 'n']
+ | c == '\b' = ['\\', 'b']
+ | c == '\f' = ['\\', 'f']
+ | otherwise = [c]