salmon-ops-0.2.0.0: src/Salmon/Builtin/Cards/Postgres.hs
{- | Web UI cards for more of the Postgres builtins
("Salmon.Builtin.Nodes.Postgres"), in the style of "Salmon.Builtin.Cards"
(feature d8a79380; 'Salmon.Builtin.Cards.pgDatabase' is there already).
__Opt-in__, attached per node with 'Salmon.Builtin.Cards.withCard'.
A card takes only what the builtin already publishes in its @ref@ and
@help@: a role name, a port, a slot, a cluster, a setting, a file's path.
It never takes a password, so it cannot show one or any digest of one; for a
password file or a TLS key it shows the path only.
-}
module Salmon.Builtin.Cards.Postgres (
-- * Cards
user,
userPassFile,
replicationUser,
replicationSlot,
hbaLine,
alterSystem,
serverTls,
-- * Before rendering
userCard,
userPassFileCard,
replicationUserCard,
replicationSlotCard,
hbaLineCard,
alterSystemCard,
serverTlsCard,
) where
import Data.Text (Text)
import qualified Data.Text as Text
import Salmon.Builtin.Cards (Card (..), Verdicts (..), renderCard)
import qualified Salmon.Builtin.Nodes.Postgres as Postgres
unchecked :: Text
unchecked = "no check result on this page yet"
port_ :: Postgres.Port -> (Text, Text)
port_ p = ("port", Text.pack (show p))
-- the sentence for a builtin with no check
reapplied :: Text
reapplied = "no check: run up applies it every time (it is idempotent); under serve it parks and nothing watches it"
-- | For 'Postgres.user' on this port: the role, never the password.
user :: Postgres.Port -> Postgres.User -> Text
user p u = renderCard (userCard p u)
-- | 'user' before rendering.
userCard :: Postgres.Port -> Postgres.User -> Card
userCard p u =
Card
{ cardFacts = [("role", u.userRole), port_ p]
, cardVerdicts = userVerdicts "the declared one"
, cardUp = "creates the login role if missing, then sets its password (either way)"
, cardDown = "nothing: a role is never dropped by this node"
, cardLinks = []
, cardDocs = Just "postgres-user"
}
userVerdicts :: Text -> Verdicts
userVerdicts which =
Verdicts
{ verdictInPlace = "the role exists, may log in, and its password is " <> which
, verdictToApply = "missing, not allowed to log in, or holding another password: up sets it"
, verdictUnsure = "could not ask the cluster, or read the declared password"
, verdictNoCheck = "the node declares no check"
, verdictUnchecked = unchecked
}
{- | For 'Postgres.userPassFile' on this port, with the password file at
this path: the role and the path, never the file's contents.
-}
userPassFile :: Postgres.Port -> FilePath -> Postgres.User -> Text
userPassFile p path u = renderCard (userPassFileCard p path u)
-- | 'userPassFile' before rendering.
userPassFileCard :: Postgres.Port -> FilePath -> Postgres.User -> Card
userPassFileCard p path u =
Card
{ cardFacts = [("role", u.userRole), port_ p, ("password file", Text.pack path)]
, cardVerdicts = userVerdicts "the file's"
, cardUp = "creates the login role if missing, then sets its password from the file, read at that moment"
, cardDown = "nothing: a role is never dropped by this node"
, cardLinks = []
, cardDocs = Just "postgres-user-passfile"
}
-- | For 'Postgres.replicationUser' on this port: the role, never the password.
replicationUser :: Postgres.Port -> Postgres.User -> Text
replicationUser p u = renderCard (replicationUserCard p u)
-- | 'replicationUser' before rendering.
replicationUserCard :: Postgres.Port -> Postgres.User -> Card
replicationUserCard p u =
Card
{ cardFacts = [("role", u.userRole), port_ p, ("attributes", "REPLICATION LOGIN")]
, cardVerdicts = noCheckVerdicts "the role is there" "missing: up creates it"
, cardUp = "creates the role with REPLICATION LOGIN if missing, then sets its password (either way)"
, cardDown = "nothing: a role is never dropped by this node"
, cardLinks = []
, cardDocs = Just "postgres-replication-user"
}
noCheckVerdicts :: Text -> Text -> Verdicts
noCheckVerdicts inPlace toApply =
Verdicts
{ verdictInPlace = inPlace
, verdictToApply = toApply
, verdictUnsure = "could not ask the server"
, verdictNoCheck = reapplied
, verdictUnchecked = unchecked
}
-- | For 'Postgres.replicationSlot' on this port.
replicationSlot :: Postgres.Port -> Postgres.ReplicationSlotName -> Text
replicationSlot p slot = renderCard (replicationSlotCard p slot)
-- | 'replicationSlot' before rendering.
replicationSlotCard :: Postgres.Port -> Postgres.ReplicationSlotName -> Card
replicationSlotCard p slot =
Card
{ cardFacts = [("slot", slot), ("kind", "physical"), port_ p]
, cardVerdicts = noCheckVerdicts "the slot is there" "missing: up creates it"
, cardUp = "creates the physical replication slot unless pg_replication_slots has it"
, cardDown = "nothing: the slot is left (a slot nobody reads from holds WAL back, so drop it by hand when its standby is gone)"
, cardLinks = []
, cardDocs = Just "postgres-replication-slot"
}
-- | For 'Postgres.hbaLine' on this cluster.
hbaLine :: Postgres.ClusterName -> Text -> Text
hbaLine cluster line = renderCard (hbaLineCard cluster line)
-- | 'hbaLine' before rendering.
hbaLineCard :: Postgres.ClusterName -> Text -> Card
hbaLineCard cluster line =
Card
{ cardFacts = [("cluster", cluster), ("line", line)]
, cardVerdicts = noCheckVerdicts "the line is in pg_hba.conf" "missing: up appends it"
, cardUp = "appends the line to pg_hba.conf unless it is there verbatim, then reloads the cluster"
, cardDown = "nothing: the line stays (and a changed declaration leaves the old line in place)"
, cardLinks = []
, cardDocs = Just "postgres-hba-line"
}
-- | For 'Postgres.alterSystemSet' on this port.
alterSystem :: Postgres.Port -> Text -> Text -> Text
alterSystem p param val = renderCard (alterSystemCard p param val)
-- | 'alterSystem' before rendering.
alterSystemCard :: Postgres.Port -> Text -> Text -> Card
alterSystemCard p param val =
Card
{ cardFacts = [("setting", param), ("value", val), port_ p]
, cardVerdicts = noCheckVerdicts "the setting is written" "not written yet: up writes it"
, cardUp = "ALTER SYSTEM SET, written to postgresql.auto.conf; it takes effect on a reload or, for some settings, a restart"
, cardDown = "nothing: the setting stays"
, cardLinks = []
, cardDocs = Just "postgres-alter-system"
}
{- | For 'Postgres.serverTls' (and 'Postgres.serverTlsAfter') on this port:
the three files' paths, never what is in them (the key above all).
-}
serverTls :: Postgres.Port -> Postgres.ServerTls -> Text
serverTls p tls = renderCard (serverTlsCard p tls)
-- | 'serverTls' before rendering.
serverTlsCard :: Postgres.Port -> Postgres.ServerTls -> Card
serverTlsCard p tls =
Card
{ cardFacts =
[ port_ p
, ("certificate", Text.pack tls.tls_certFile)
, ("key", Text.pack tls.tls_keyFile)
, ("client CA", Text.pack tls.tls_caFile)
]
, cardVerdicts =
Verdicts
{ verdictInPlace = "TLS is on, as declared"
, verdictToApply = "TLS is off or not as declared"
, verdictUnsure = "could not ask the server"
, verdictNoCheck = "no check of its own: the settings and the reload under it run on each pass; a broken certificate is not noticed until a client connects"
, verdictUnchecked = unchecked
}
, cardUp = "nothing of its own: under it, ssl and the three file settings are set, then the configuration is reloaded"
, cardDown = "nothing: TLS stays configured"
, cardLinks = []
, cardDocs = Just "postgres-server-tls"
}