alerta-0.1.0.5: src/Alerta/Types.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
--------------------------------------------------------------------------------
-- |
-- Module: Alerta.Types
--
-- Types used by the alerta bindings.
-- This includes request and return parameter types, as well as type synonyms used
-- to help document the bindings.
--
-- A record whose name ends in \"Resp\" indicates a response from the server.
-- A record whose name ends in \"Info\" indicates data contained within
-- responses from the server.
--------------------------------------------------------------------------------
module Alerta.Types
(
-- ** General domain terms
Resource
, Event
, Service
, Environment
, Group
, Origin
, AlertType
, UserName
, CustomerName
, Tag
, Email
, Password
, Provider
, UUID
, Href
-- ** Query and sorting options
, ShouldReverse
, Limit
, PageNo
, QueryString
, IsRepeat
-- ** Field queries
, FieldQuery
, MatchType(..)
, (=.), (!=), (~.), (!~)
-- ** Generic response
, Resp(..)
-- ** Alerts
, Severity(..)
, Status(..)
, TrendIndication(..)
, Alert(..)
, mkAlert
, AlertInfo(..)
, AlertAttr(..)
, QueryAttr(..)
, HistoryItem(..)
, ExtendedHistoryItem(..)
, Tags(..)
, Attributes(..)
, StatusChange(..)
, CreateAlertResp(..)
, AlertResp(..)
, AlertsResp(..)
, AlertCountResp(..)
, ResourceInfo(..)
, Top10Info(..)
, Top10Resp(..)
, AlertHistoryResp(..)
-- ** Environments
, EnvironmentInfo(..)
, EnvironmentsResp(..)
-- ** Services
, ServiceInfo(..)
, ServicesResp(..)
-- ** Blackouts
, Blackout(..)
, blackout
, BlackoutInfo(..)
, BlackoutStatus(..)
, ExtendedBlackoutInfo(..)
, BlackoutResp(..)
, BlackoutsResp(..)
-- ** Heartbeats
, Heartbeat(..)
, HeartbeatInfo(..)
, CreateHeartbeatResp(..)
, HeartbeatResp(..)
, HeartbeatsResp(..)
-- ** API keys
, ApiKey(..)
, CreateApiKey(..)
, ApiKeyType(..)
, ApiKeyInfo(..)
, CreateApiKeyResp(..)
, ApiKeysResp(..)
-- ** Users
, User(..)
, user
, UserAttr(..)
, IsEmpty(..)
, emptyUserAttr
, checkNonempty
, withUserName
, withUserLogin
, withUserPassword
, withUserProvider
, withUserText
, withUserEmailVerified
, UserInfo(..)
, RoleType(..)
, ExtendedUserInfo(..)
, UserResp(..)
, UsersResp(..)
-- ** Customers
, Customer(..)
, CustomerInfo(..)
, CustomerResp(..)
, CustomersResp(..)
) where
import Alerta.Util
import Control.Applicative (empty)
import Data.Aeson
import qualified Data.Aeson.Encoding as E
import Data.Aeson.Types
import Data.Aeson.TH
import Data.Coerce (coerce)
import Data.Default
import Data.Ix
import Data.Map (Map)
import Data.Monoid ((<>))
import Data.String (IsString(..))
import qualified Data.Text as T
import Data.Text (Text)
import Data.Time (UTCTime)
import GHC.Generics
import Web.HttpApiData
type Resource = Text
type Event = Text
type Service = Text
type Environment = Text
type Group = Text
type Origin = Text
type AlertType = Text
type UserName = Text
type CustomerName = Text
type Tag = Text
type Email = Text -- TODO actual email type
type Password = Text
type Provider = Text -- TODO make into data type
type ShouldReverse = Bool -- ^ whether to reverse the order of a sort
type Limit = Int -- ^ maximum number of results to return (actually a positive int)
type PageNo = Int -- ^ what page of the results to return (actually a positive int)
type UUID = Text -- TODO actual UUID type?
type Href = Text -- TODO use URL type for hrefs
-- | This is a JSON document describing a Mongo query,
-- see http://docs.mongodb.org/manual/reference/operator/query/
type QueryString = Text -- TODO should be JSON or an ADT representing a Mongo query
type IsRepeat = Bool
-- ^ true for duplicate, false if an alert is correlated (in which case alerta appends an item to
-- the history)
--------------------------------------------------------------------------------
-- Field queries
--------------------------------------------------------------------------------
type FieldQuery = (QueryAttr, Text, MatchType, Bool)
-- | Matches can be either literal or regular expressions.
-- n.b. regexes are case-insensitive and are not anchored,
-- i.e. no need to write .*regex.*
data MatchType = Regex | Literal deriving (Eq, Enum, Show, Read, Generic)
-- | Convenient syntax for the four types of field queries
-- viz. literal, negated literal, regex, negated regex.
(=.), (!=), (~.), (!~) :: QueryAttr -> Text -> FieldQuery
k =. v = (k, v, Literal, True)
k != v = (k, v, Literal, False)
k ~. v = (k, v, Regex, True)
k !~ v = (k, v, Regex, False)
-- | These are the valid keys for use in field queries.
--
-- NB no 'id', 'repeat' or 'duplicateCount' as these have special handling.
data QueryAttr =
EventQueryAttr
| EnvironmentQueryAttr
| SeverityQueryAttr
| CorrelateQueryAttr
| StatusQueryAttr
| ServiceQueryAttr
| GroupQueryAttr
| ValueQueryAttr
| TextQueryAttr
| TagsQueryAttr
| AttributesQueryAttr
| OriginQueryAttr
| TypeQueryAttr
| CreateTimeQueryAttr
| TimeoutQueryAttr
| RawDataQueryAttr
| CustomerQueryAttr
| RepeatQueryAttr
| PreviousSeverityQueryAttr
| TrendIndicationQueryAttr
| ReceiveTimeQueryAttr
| LastReceiveIdQueryAttr
| LastReceiveTimeQueryAttr
| HistoryQueryAttr
| HrefQueryAttr
deriving (Eq, Enum, Show, Generic)
instance IsString QueryAttr where
fromString "event" = EventQueryAttr
fromString "environment" = EnvironmentQueryAttr
fromString "severity" = SeverityQueryAttr
fromString "correlate" = CorrelateQueryAttr
fromString "status" = StatusQueryAttr
fromString "service" = ServiceQueryAttr
fromString "group" = GroupQueryAttr
fromString "value" = ValueQueryAttr
fromString "text" = TextQueryAttr
fromString "tags" = TagsQueryAttr
fromString "attributes" = AttributesQueryAttr
fromString "origin" = OriginQueryAttr
fromString "type" = TypeQueryAttr
fromString "createTime" = CreateTimeQueryAttr
fromString "timeout" = TimeoutQueryAttr
fromString "rawData" = RawDataQueryAttr
fromString "customer" = CustomerQueryAttr
fromString "repeat" = RepeatQueryAttr
fromString "previousSeverity" = PreviousSeverityQueryAttr
fromString "trendIndication" = TrendIndicationQueryAttr
fromString "receiveTime" = ReceiveTimeQueryAttr
fromString "lastReceiveId" = LastReceiveIdQueryAttr
fromString "lastReceiveTime" = LastReceiveTimeQueryAttr
fromString "history" = HistoryQueryAttr
fromString "href" = HrefQueryAttr
fromString other = error $ "\"" ++ other ++ "\" is not a valid QueryAttr"
instance ToHttpApiData QueryAttr where
toQueryParam = T.pack . uncapitalise . onCamelComponents (dropRight 2) . show
-- | Alert severity
data Severity =
Unknown
| Trace -- ^ 8 (grey)
| Debug -- ^ 7 (purple)
| Informational -- ^ 6 (green)
| Ok -- ^ 5 (green)
| Normal -- ^ 5 (green)
| Cleared -- ^ 5 (green)
| Indeterminate -- ^ 5 (silver)
| Warning -- ^ 4 (blue)
| Minor -- ^ 3 (yellow)
| Major -- ^ 2 (orange)
| Critical -- ^ 1 (red)
| Security -- ^ 0 (black)
deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read, Generic)
instance ToHttpApiData Severity where
toUrlPiece = T.pack . show
instance ToJSONKey Severity where
toJSONKey = toJSONKeyText (T.toLower . T.pack . show)
instance FromJSONKey Severity where
fromJSONKey = FromJSONKeyTextParser $ \case
"security" -> pure Security
"critical" -> pure Critical
"major" -> pure Major
"minor" -> pure Minor
"warning" -> pure Warning
"indeterminate" -> pure Indeterminate
"cleared" -> pure Cleared
"normal" -> pure Normal
"ok" -> pure Ok
"informational" -> pure Informational
"debug" -> pure Debug
"trace" -> pure Trace
"unknown" -> pure Unknown
other -> fail $ "Could not parse key \"" ++ T.unpack other ++ "\" as Severity"
-- | Status of an alert.
data Status = -- Status Code
OpenStatus -- ^ status code 1
| AssignStatus -- ^ status code 2
| AckStatus -- ^ status code 3
| ClosedStatus -- ^ status code 4
| ExpiredStatus -- ^ status code 5
| UnknownStatus -- ^ status code 9
deriving (Eq, Ord, Bounded, Enum, Ix, Show, Read, Generic)
instance ToHttpApiData Status where
toUrlPiece = showTextLowercase
instance ToJSONKey Status where
toJSONKey = toJSONKeyText (T.toLower . T.pack . show)
instance FromJSONKey Status where
fromJSONKey = FromJSONKeyTextParser $ \case
"open" -> pure OpenStatus
"assign" -> pure AssignStatus
"ack" -> pure AckStatus
"closed" -> pure ClosedStatus
"expired" -> pure ExpiredStatus
"unknown" -> pure UnknownStatus
other -> fail $ "Could not parse key \"" ++ T.unpack other ++ "\" as Status"
data TrendIndication = NoChange | LessSevere | MoreSevere
deriving (Eq, Ord, Bounded, Enum, Ix, Show, Generic)
instance FromHttpApiData TrendIndication where
parseQueryParam = parseBoundedTextData
--------------------------------------------------------------------------------
-- Basic response
--------------------------------------------------------------------------------
-- | This type is used for basic responses that have no content beyond whether they succeeded or
-- failed with an error message.
data Resp = OkResp | ErrorResp { respMessage :: Text }
deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Alerts
--------------------------------------------------------------------------------
-- | Data required to create (post) an alert.
data Alert = Alert
{ alertResource :: Resource
, alertEvent :: Event
, alertEnvironment :: Maybe Environment
, alertSeverity :: Maybe Severity
, alertCorrelate :: Maybe [Event]
, alertStatus :: Maybe Status
, alertService :: Maybe [Service]
, alertGroup :: Maybe Group -- ^ defaults to @Misc@
, alertValue :: Maybe Value -- ^ defaults to @n/a@
, alertText :: Maybe Text
, alertTags :: Maybe [Tag]
, alertAttributes :: Maybe (Map Text Text)
-- Attribute keys must not contain "." or "$"
-- note that key "ip" will be overwritten
, alertOrigin :: Maybe Origin -- ^ defaults to prog\/machine ('%s\/%s' % (os.path.basename(sys.argv[0]), platform.uname()[1]))
, alertType :: Maybe AlertType -- ^ defaults to @exceptionAlert@
, alertCreateTime :: Maybe UTCTime -- ^ defaults to @utcnow()@
, alertTimeout :: Maybe Int -- ^ in seconds; defaults to 86400 (24 hours)
, alertRawData :: Maybe Text
, alertCustomer :: Maybe CustomerName
} deriving (Eq, Show, Generic)
-- | Create an alert with just the mandatory fields.
mkAlert :: Resource -> Event -> Service -> Alert
mkAlert r e s = Alert
{ alertResource = r
, alertEvent = e
-- supposed to be optional, but RejectPolicy plugin requires it to be one of ALLOWED_ENVIRONMENTS
, alertEnvironment = Just "Development"
, alertSeverity = Nothing
, alertCorrelate = Nothing
, alertStatus = Nothing
-- supposed to be optional, but RejectPolicy requires it to be set
, alertService = Just [s]
, alertGroup = Nothing
, alertValue = Nothing
, alertText = Nothing
, alertTags = Nothing
, alertAttributes = Nothing
, alertOrigin = Nothing
, alertType = Nothing
, alertCreateTime = Nothing
, alertTimeout = Nothing
, alertRawData = Nothing
, alertCustomer = Nothing
}
-- | Data returned from the server about an alert
data AlertInfo = AlertInfo
{ alertInfoId :: UUID
, alertInfoResource :: Resource
, alertInfoEvent :: Event
, alertInfoEnvironment :: Environment -- ^ defaults to empty string
, alertInfoSeverity :: Maybe Severity
, alertInfoCorrelate :: [Event]
, alertInfoStatus :: Maybe Status
, alertInfoService :: [Service]
, alertInfoGroup :: Group -- ^ defaults to @misc@
, alertInfoValue :: Value -- ^ defaults to @n/a@
, alertInfoText :: Text -- ^ defaults to empty string
, alertInfoTags :: [Tag]
, alertInfoAttributes :: Map Text Text -- ^ Attribute keys must not contain "." or "$"
, alertInfoOrigin :: Origin -- ^ defaults to prog\/machine
, alertInfoType :: AlertType -- ^ defaults to @exceptionAlert@
, alertInfoCreateTime :: UTCTime
, alertInfoTimeout :: Int
, alertInfoRawData :: Maybe Text
, alertInfoCustomer :: Maybe CustomerName
, alertInfoDuplicateCount :: Maybe Int
, alertInfoRepeat :: Maybe Bool
, alertInfoPreviousSeverity :: Maybe Severity
, alertInfoTrendIndication :: Maybe TrendIndication
, alertInfoReceiveTime :: UTCTime
, alertInfoLastReceiveId :: Maybe UUID
, alertInfoLastReceiveTime :: UTCTime
, alertInfoHistory :: [HistoryItem]
, alertInfoHref :: Href
} deriving (Eq, Show, Generic)
-- | Alert attributes, used for sorting, grouping and for field-based queries
data AlertAttr =
IdAlertAttr
| ResourceAlertAttr
| EventAlertAttr
| EnvironmentAlertAttr
| SeverityAlertAttr
| CorrelateAlertAttr
| StatusAlertAttr
| ServiceAlertAttr
| GroupAlertAttr
| ValueAlertAttr
| TextAlertAttr
| TagsAlertAttr
| AttributesAlertAttr
| OriginAlertAttr
| TypeAlertAttr
| CreateTimeAlertAttr
| TimeoutAlertAttr
| RawDataAlertAttr
| CustomerAlertAttr
| DuplicateCountAlertAttr
| RepeatAlertAttr
| PreviousSeverityAlertAttr
| TrendIndicationAlertAttr
| ReceiveTimeAlertAttr
| LastReceiveIdAlertAttr
| LastReceiveTimeAlertAttr
| HistoryAlertAttr
| HrefAlertAttr
deriving (Eq, Enum, Show, Generic)
instance IsString AlertAttr where
fromString "id" = IdAlertAttr
fromString "resource" = ResourceAlertAttr
fromString "event" = EventAlertAttr
fromString "environment" = EnvironmentAlertAttr
fromString "severity" = SeverityAlertAttr
fromString "correlate" = CorrelateAlertAttr
fromString "status" = StatusAlertAttr
fromString "service" = ServiceAlertAttr
fromString "group" = GroupAlertAttr
fromString "value" = ValueAlertAttr
fromString "text" = TextAlertAttr
fromString "tags" = TagsAlertAttr
fromString "attributes" = AttributesAlertAttr
fromString "origin" = OriginAlertAttr
fromString "type" = TypeAlertAttr
fromString "createTime" = CreateTimeAlertAttr
fromString "timeout" = TimeoutAlertAttr
fromString "rawData" = RawDataAlertAttr
fromString "customer" = CustomerAlertAttr
fromString "duplicateCount" = DuplicateCountAlertAttr
fromString "repeat" = RepeatAlertAttr
fromString "previousSeverity" = PreviousSeverityAlertAttr
fromString "trendIndication" = TrendIndicationAlertAttr
fromString "receiveTime" = ReceiveTimeAlertAttr
fromString "lastReceiveId" = LastReceiveIdAlertAttr
fromString "lastReceiveTime" = LastReceiveTimeAlertAttr
fromString "history" = HistoryAlertAttr
fromString "href" = HrefAlertAttr
fromString other = error $ "\"" ++ other ++ "\" is not a valid AlertAttr"
instance ToHttpApiData AlertAttr where
toQueryParam = T.pack . uncapitalise . onCamelComponents (dropRight 2) . show
data HistoryItem = StatusHistoryItem
{ historyItemEvent :: Event
, historyItemStatus :: Status
, historyItemText :: Text
, historyItemId :: UUID
, historyItemUpdateTime :: UTCTime
} | SeverityHistoryItem {
historyItemEvent :: Event
, historyItemSeverity :: Severity
, historyItemText :: Text
, historyItemId :: UUID
, historyItemUpdateTime :: UTCTime
, historyItemValue :: Value
} deriving (Eq, Show, Generic)
-- | When performing an alert history query an enriched version of the alert history is returned with extra fields.
data ExtendedHistoryItem = StatusExtendedHistoryItem
{ statusExtendedHistoryItemId :: UUID
, statusExtendedHistoryItemResource :: Resource
, statusExtendedHistoryItemEvent :: Event
, statusExtendedHistoryItemEnvironment :: Environment
, statusExtendedHistoryItemStatus :: Status
, statusExtendedHistoryItemService :: [Service] -- ^ why this is an array I do not know
, statusExtendedHistoryItemGroup :: Group
, statusExtendedHistoryItemText :: Text
, statusExtendedHistoryItemTags :: [Tag]
, statusExtendedHistoryItemAttributes :: Map Text Text
, statusExtendedHistoryItemOrigin :: Origin
, statusExtendedHistoryItemUpdateTime :: UTCTime
, statusExtendedHistoryItemCustomer :: Maybe CustomerName
} | SeverityExtendedHistoryItem
{ severityExtendedHistoryItemId :: UUID
, severityExtendedHistoryItemResource :: Resource
, severityExtendedHistoryItemEvent :: Event
, severityExtendedHistoryItemEnvironment :: Environment
, severityExtendedHistoryItemSeverity :: Severity
, severityExtendedHistoryItemService :: [Service] -- ^ why this is an array I do not know
, severityExtendedHistoryItemGroup :: Group
, severityExtendedHistoryItemValue :: Value
, severityExtendedHistoryItemText :: Text
, severityExtendedHistoryItemTags :: [Tag]
, severityExtendedHistoryItemAttributes :: Map Text Text
, severityExtendedHistoryItemOrigin :: Origin
, severityExtendedHistoryItemUpdateTime :: UTCTime
, severityExtendedHistoryItemCustomer :: Maybe CustomerName
} deriving (Eq, Show, Generic)
newtype Tags = Tags { tags :: [Tag] } deriving (Eq, Show, Generic)
-- | Attributes are key-value pairs that can be attached to an alert.
newtype Attributes = Attributes { attributes :: Map Text Text } deriving (Eq, Show, Generic)
data StatusChange = StatusChange
{ statusChangeStatus :: Status -- docs say not "unknown" but the api allows it
-- (in fact any text is accepted - but our parsing code need to be able
-- to read it back from the alert history)
, statusChangeText :: Maybe Text
} deriving (Eq, Show, Generic)
data CreateAlertResp = OkCreateAlertResp
{ okCreateAlertRespId :: UUID
, okCreateAlertRespAlert :: Maybe AlertInfo -- ^ not present if rate limited or in blackout
, okCreateAlertRespMessage :: Maybe Text -- ^ present when rate limited or in blackout
} | ErrorCreateAlertResp {
errorCreateAlertRespMessage :: Text
} deriving (Eq, Show, Generic)
data AlertResp = OkAlertResp
{ okAlertRespAlert :: AlertInfo
, okAlertRespTotal :: Int
} | ErrorAlertResp {
errorAlertRespMessage :: Text
} deriving (Eq, Show, Generic)
data AlertsResp = OkAlertsResp
{ okAlertsRespAlerts :: [AlertInfo]
, okAlertsRespTotal :: Int
, okAlertsRespPage :: PageNo
, okAlertsRespPageSize :: Int
, okAlertsRespPages :: Int
, okAlertsRespMore :: Bool
, okAlertsRespSeverityCounts :: Maybe (Map Severity Int)
, okAlertsRespStatusCounts :: Maybe (Map Status Int)
, okAlertsRespLastTime :: UTCTime
, okAlertsRespAutoRefresh :: Bool
, okAlertsRespMessage :: Maybe Text
} | ErrorAlertsResp
{ errorAlertsRespMessage :: Text
} deriving (Eq, Show, Generic)
data AlertCountResp = OkAlertCountResp
{ okAlertCountRespTotal :: Int
, okAlertCountRespSeverityCounts :: Int
, okAlertCountRespStatusCounts :: Int
, okAlertCountRespMessage :: Maybe Text
} | ErrorAlertCountResp
{ errorAlertCountRespMessage :: Text
} deriving (Eq, Show, Generic)
data ResourceInfo = ResourceInfo
{ resourceInfoId :: UUID
, resourceInfoResource :: Resource
, resourceInfoHref :: Href
} deriving (Eq, Show, Generic)
-- | This also has a field corresponding to the "group-by" query parameter used
-- i.e. if you group by origin, then the result will have an "origin" field.
--
-- This dependently-typed feature is not currently captured in the Haskell types.
data Top10Info = Top10Info
{ top10InfoCount :: Int
, top10InfoDuplicateCount :: Int
, top10InfoEnvironments :: [Environment]
, top10InfoServices :: [Service]
, top10InfoResources :: [ResourceInfo]
} deriving (Eq, Show, Generic)
data Top10Resp = OkTop10Resp
{ okTop10RespTop10 :: [Top10Info]
, okTop10RespTotal :: Int
, okTop10RespMessage :: Maybe Text
} | ErrorTop10Resp
{ errorTop10RespMessage :: Text
} deriving (Eq, Show, Generic)
data AlertHistoryResp = OkAlertHistoryResp
{ okAlertHistoryRespHistory :: [ExtendedHistoryItem]
, okAlertHistoryRespLastTime :: UTCTime
, okAlertHistoryRespMessage :: Maybe Text
} | ErrorAlertHistoryResp
{ errorAlertHistoryResp :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Environments
--------------------------------------------------------------------------------
data EnvironmentInfo = EnvironmentInfo
{ environmentInfoCount :: Int
, environmentInfoEnvironment :: Environment
} deriving (Eq, Show, Generic)
data EnvironmentsResp = OkEnvironmentsResp
{ okEnvironmentsRespMessage :: Maybe Text
, okEnvironmentsRespTotal :: Int
, okEnvironmentsRespEnvironments :: [EnvironmentInfo]
} | ErrorEnvironmentsResp
{ errorEnvironmentsRespMessage :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Services
--------------------------------------------------------------------------------
data ServiceInfo = ServiceInfo
{ serviceInfoCount :: Int
, serviceInfoEnvironment :: Environment
, serviceInfoService :: Service
} deriving (Eq, Show, Generic)
data ServicesResp = OkServicesResp
{ okServicesRespTotal :: Int
, okServicesRespServices :: [ServiceInfo]
, okServicesRespMessage :: Maybe Text
} | ErrorServicesResp
{ errorServicesRespMessage :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Blackouts
--------------------------------------------------------------------------------
data Blackout = Blackout
{ blackoutEnvironment :: Environment
, blackoutResource :: Maybe Resource
, blackoutService :: Maybe Service
, blackoutEvent :: Maybe Event
, blackoutGroup :: Maybe Group
, blackoutTags :: Maybe [Tag]
, blackoutStartTime :: Maybe UTCTime -- ^ defaults to now
, blackoutEndTime :: Maybe UTCTime -- ^ defaults to start + duration
, blackoutDuration :: Maybe Int -- ^ in seconds; can be calculated from start and end, or else defaults to BLACKOUT_DURATION
} deriving (Eq, Show, Generic)
-- | Create a blackout with only the mandatory fields
blackout :: Environment -> Blackout
blackout env = Blackout env Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing
-- | A note on blackout priorities:
--
-- Priority is
--
-- 1. by default
--
-- 2. if resource and not event present
--
-- 3. if service present
--
-- 4. if event and not resource
--
-- 5. if group present
--
-- 6. if resource and event present
--
-- 7. if tags present
--
-- Somewhat bizarrely, the saved blackout only includes an attribute
--
-- {resource,service,event,group,tags}
--
-- if it was used to deduce the priority,
-- i.e. a priority 6 blackout will have resource and event attributes,
-- but no tags attribute, even if it was supplied when it was created.
data BlackoutInfo = BlackoutInfo
{ blackoutInfoId :: UUID
, blackoutInfoPriority :: Int
, blackoutInfoEnvironment :: Environment
, blackoutInfoResource :: Maybe Resource
, blackoutInfoService :: Maybe [Service]
, blackoutInfoEvent :: Maybe Event
, blackoutInfoGroup :: Maybe Group
, blackoutInfoTags :: Maybe [Tag]
, blackoutInfoCustomer :: Maybe CustomerName
, blackoutInfoStartTime :: UTCTime -- ^ defaults to now
, blackoutInfoEndTime :: UTCTime -- ^ defaults to start + duration
, blackoutInfoDuration :: Int -- ^ can be calculated from start and end, or else defaults to BLACKOUT_DURATION
} deriving (Eq, Show, Generic)
data BlackoutStatus = Expired | Pending | Active deriving (Eq, Ord, Bounded, Enum, Ix, Show, Generic)
data ExtendedBlackoutInfo = ExtendedBlackoutInfo
{ extendedBlackoutInfoId :: UUID
, extendedBlackoutInfoPriority :: Int -- see comment above
, extendedBlackoutInfoEnvironment :: Environment
, extendedBlackoutInfoResource :: Maybe Resource
, extendedBlackoutInfoService :: Maybe [Service]
, extendedBlackoutInfoEvent :: Maybe Event
, extendedBlackoutInfoGroup :: Maybe Group
, extendedBlackoutInfoTags :: Maybe [Tag]
, extendedBlackoutInfoCustomer :: Maybe CustomerName
, extendedBlackoutInfoStartTime :: UTCTime -- defaults to now
, extendedBlackoutInfoEndTime :: UTCTime -- defaults to start + duration
, extendedBlackoutInfoDuration :: Int -- can be calculated from start and end, or else defaults to BLACKOUT_DURATION
, extendedBlackoutInfoRemaining :: Int
, extendedBlackoutInfoStatus :: BlackoutStatus
} deriving (Eq, Show, Generic)
data BlackoutResp = OkBlackoutResp
{ okBlackoutRespId :: UUID
, okBlackoutRespBlackout :: BlackoutInfo
} | ErrorBlackoutResp {
errorBlackoutRespMessage :: Text
} deriving (Eq, Show, Generic)
data BlackoutsResp = OkBlackoutsResp
{ okBlackoutsRespTotal :: Int
, okBlackoutsRespBlackouts :: [ExtendedBlackoutInfo]
, okBlackoutsRespMessage :: Maybe Text
, okBlackoutsRespTime :: UTCTime
} | ErrorBlackoutsResp {
errorBlackoutsRespMessage :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Heartbeats
--------------------------------------------------------------------------------
-- | Data needed to create a heartbeat
data Heartbeat = Heartbeat
{ heartbeatOrigin :: Maybe Origin -- defaults to prog/nodename
, heartbeatTags :: [Tag]
, heartbeatCreateTime :: Maybe UTCTime
, heartbeatTimeout :: Maybe Int -- seconds
, heartbeatCustomer :: Maybe CustomerName -- if not admin, gets overwritten
} deriving (Eq, Show, Generic, Default)
-- | Information returned from the server about a heartbeat
data HeartbeatInfo = HeartbeatInfo
{ heartbeatInfoCreateTime :: UTCTime
, heartbeatInfoCustomer :: Maybe CustomerName
, heartbeatInfoHref :: Href
, heartbeatInfoId :: UUID
, heartbeatInfoOrigin :: Origin
, heartbeatInfoReceiveTime :: UTCTime
, heartbeatInfoTags :: [Tag]
, heartbeatInfoTimeout :: Int
, heartbeatInfoType :: Text
} deriving (Eq, Show, Generic)
data CreateHeartbeatResp = OkCreateHeartbeatResp
{ createHeartbeatRespId :: UUID
, createHeartbeatRespHeartbeat :: HeartbeatInfo
} | ErrorCreateHeartbeatResp
{ createHeartbeatRespMessage :: Text
} deriving (Eq, Show, Generic)
data HeartbeatResp = OkHeartbeatResp
{ heartbeatRespHeartbeat :: HeartbeatInfo
, heartbeatRespTotal :: Int
} | ErrorHeartbeatResp
{ heartbeatRespMessage :: Text
} deriving (Eq, Show, Generic)
data HeartbeatsResp = OkHeartbeatsResp
{ heartbeatsRespHeartbeats :: [HeartbeatInfo]
, heartbeatsRespTime :: Maybe UTCTime
, heartbeatsRespTotal :: Int
, heartbeatsRespMessage :: Maybe Text
} | ErrorHeartbeatsResp
{ heartbeatsRespErrorMessage :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- API keys
--------------------------------------------------------------------------------
-- when using admin credentials, user must be present, or associated with account
-- | 40-char UTF8
-- TODO smart constructor
data ApiKey = ApiKey { unApiKey :: !Text }
deriving (Eq, Show, Generic)
instance IsString ApiKey where
fromString = ApiKey . T.pack
instance FromJSON ApiKey where
parseJSON = withText "API key" (pure . ApiKey)
instance ToJSON ApiKey where
toJSON (ApiKey k) = String k
{-# INLINE toJSON #-}
toEncoding (ApiKey k) = E.text k
{-# INLINE toEncoding #-}
instance ToHttpApiData ApiKey where
toUrlPiece (ApiKey k) = k
-- | Data needed to create an API key
data CreateApiKey = CreateApiKey
{ createApiKeyUser :: Maybe Email -- ^ only read if authorised as admin, defaults to current user
, createApiKeyCustomer :: Maybe CustomerName -- ^ only read if authorised as admin, defaults to current customer
, createApiKeyType :: Maybe ApiKeyType -- ^ defaults to read-only
, createApiKeyText :: Maybe Text -- ^ defaults to "API Key for $user"
} deriving (Eq, Show, Generic, Default)
data ApiKeyType = ReadOnly | ReadWrite deriving (Eq, Ord, Bounded, Enum, Ix, Generic)
instance Show ApiKeyType where
show ReadWrite = "read-write"
show ReadOnly = "read-only"
instance ToJSON ApiKeyType where
toJSON = genericToJSON $ defaultOptions { constructorTagModifier = camelTo2 '-'}
instance FromJSON ApiKeyType where
parseJSON = genericParseJSON $ defaultOptions { constructorTagModifier = camelTo2 '-'}
-- | Information returned from the server about an API key
data ApiKeyInfo = ApiKeyInfo
{ apiKeyInfoUser :: Email
, apiKeyInfoKey :: ApiKey
, apiKeyInfoType :: ApiKeyType
, apiKeyInfoText :: Text
, apiKeyInfoExpireTime :: UTCTime
, apiKeyInfoCount :: Int -- number of times used
, apiKeyInfoLastUsedTime :: Maybe UTCTime
, apiKeyInfoCustomer :: Maybe CustomerName
} deriving (Eq, Show, Generic)
data CreateApiKeyResp = OkCreateApiKeyResp
{ okCreateApiKeyRespKey :: ApiKey
, okCreateApiKeyRespData :: ApiKeyInfo
} | ErrorCreateApiKeyResp
{ errorCreateApiKeyRespMessage :: Text
} deriving (Eq, Show, Generic)
data ApiKeysResp = OkApiKeysResp
{ okApiKeysRespKeys :: [ApiKeyInfo]
, okApiKeysRespTotal :: Int
, okApiKeysRespTime :: UTCTime
, okApiKeysRespMessage :: Maybe Text
} | ErrorApiKeysResp
{ errorApiKeysRespMessage :: Text
} deriving (Eq, Show, Generic)
--------------------------------------------------------------------------------
-- Users
--------------------------------------------------------------------------------
-- alerta doesn't require login, password but returns a 500 if they are missing
-- yay, cowboy coding
-- Data needed to create a user
data User = User
{ userName :: UserName
, userLogin :: Email
, userPassword :: Password
, userProvider :: Maybe Provider
, userText :: Maybe Text
, userEmailVerified :: Maybe Bool
} deriving (Show, Generic)
-- | Create a user with just the mandatory fields.
user :: UserName -> Email -> Password -> User
user name login password = User name login password Nothing Nothing Nothing
data IsEmpty = Empty | Nonempty | UnknownIfEmpty
-- | User attributes, used in updating a user.
-- It's an error to update a user with all attributes missing.
--
-- We track whether at least one attribute has been set with a phantom type.
--
-- Alerta bugs:
--
-- * can't update password without also passing provider=basic
-- as alerta checks the update message, not the user.
--
-- * can't set email_verified to false without providing another parameter.
--
-- The helper functions "withUserName" etc. can be used in conjunction with
-- the default empty "UserAttr" to build up a nonempty "UserAttr".
data UserAttr (u :: IsEmpty) = UserAttr
{ userAttrName :: Maybe UserName
, userAttrLogin :: Maybe Email
, userAttrPassword :: Maybe Password
, userAttrProvider :: Maybe Provider
, userAttrText :: Maybe Text
, userAttrEmail_verified :: Maybe Bool
} deriving (Eq, Show, Generic)
emptyUserAttr :: UserAttr 'Empty
emptyUserAttr = UserAttr Nothing Nothing Nothing Nothing Nothing Nothing
checkNonempty :: UserAttr u -> Either (UserAttr 'Empty) (UserAttr 'Nonempty)
checkNonempty a@(UserAttr Nothing Nothing Nothing Nothing Nothing Nothing) = Left $ coerce a
checkNonempty a = Right $ coerce a
instance Default (UserAttr 'Empty) where
def = emptyUserAttr
-- TODO omitNothingFields doesn't work
instance ToJSON (UserAttr 'Nonempty) where
toJSON = genericToJSON $ defaultOptions { omitNothingFields = True }
toEncoding (UserAttr n l pw pr t ev) =
pairs $
"name" .= n <>
"login" .= l <>
"password" .= pw <>
"provider" .= pr <>
"text" .= t <>
"email_verified" .= ev
instance FromJSON (UserAttr 'UnknownIfEmpty) where
parseJSON (Object v) =
UserAttr <$>
v .:? "name" <*>
v .:? "login" <*>
v .:? "password" <*>
v .:? "provider" <*>
v .:? "text" <*>
v .:? "email_verified"
parseJSON _ = empty
-- TODO replace with lenses
withUserName :: UserAttr u -> UserName -> UserAttr 'Nonempty
withUserLogin :: UserAttr u -> Email -> UserAttr 'Nonempty
withUserPassword :: UserAttr u -> Password -> UserAttr 'Nonempty
withUserProvider :: UserAttr u -> Provider -> UserAttr 'Nonempty
withUserText :: UserAttr u -> Text -> UserAttr 'Nonempty
withUserEmailVerified :: UserAttr u -> Bool -> UserAttr u
withUserName u s = u { userAttrName = Just s }
withUserLogin u s = u { userAttrLogin = Just s }
-- sets the provider to basic to work around the bug mentioned above
withUserPassword u s = u { userAttrPassword = Just s, userAttrProvider = Just "basic" }
withUserProvider u s = u { userAttrProvider = Just s }
withUserText u s = u { userAttrText = Just s }
withUserEmailVerified u b = u { userAttrEmail_verified = Just b }
data UserInfo = UserInfo
{ userInfoCreateTime :: UTCTime
, userInfoId :: UUID
, userInfoName :: UserName
, userInfoProvider :: Provider
, userInfoLogin :: Email
, userInfoText :: Text
, userInfoEmail_verified :: Bool
} deriving (Show, Generic)
data RoleType = UserRoleType | AdminRoleType
deriving (Eq, Ord, Bounded, Enum, Ix, Show, Generic)
data ExtendedUserInfo = ExtendedUserInfo
{ extendedUserInfoCreateTime :: UTCTime
, extendedUserInfoId :: UUID
, extendedUserInfoName :: UserName
, extendedUserInfoLogin :: Email
, extendedUserInfoProvider :: Provider
, extendedUserInfoRole :: RoleType
, extendedUserInfoText :: Text
, extendedUserInfoEmail_verified :: Bool
} deriving (Show, Generic)
data UserResp = OkUserResp
{ okUserRespId :: UUID
, okUserRespUser :: UserInfo
} | ErrorUserResp {
errorUserRespMessage :: Text
} deriving (Show, Generic)
data UsersResp = OkUsersResp
{ okUsersRespUsers :: [ExtendedUserInfo]
, okUsersRespTotal :: Int
, okUsersRespDomains :: [Text] -- allowed email domains
, okUsersRespGroups :: [Text] -- allowed Gitlab groups
, okUsersRespOrgs :: [Text] -- allowed Github orgs
, okUsersRespRoles :: Maybe [Text] -- allowed Keycloud roles
, okUsersRespTime :: UTCTime
, okUsersRespMessage :: Maybe Text
} | ErrorUsersResp {
errorUsersResp :: Text
} deriving (Show, Generic)
--------------------------------------------------------------------------------
-- Customers
--------------------------------------------------------------------------------
data Customer = Customer
{ customerCustomer :: CustomerName
, customerMatch :: Text -- regex, apparently
} deriving (Show, Generic)
data CustomerInfo = CustomerInfo
{ customerInfoId :: UUID
, customerInfoCustomer :: CustomerName
, customerInfoMatch :: Text -- regex, apparently
} deriving (Show, Generic)
data CustomerResp = OkCustomerResp
{ okCustomerRespId :: UUID
, okCustomerRespCustomer :: CustomerInfo
} | ErrorCustomerResp {
errorCustomerRespMessage :: Text
} deriving (Show, Generic)
data CustomersResp = OkCustomersResp
{ okCustomersRespCustomers :: [CustomerInfo]
, okCustomersRespTotal :: Int
, okCustomersRespMessage :: Maybe Text
, okCustomersRespTime :: UTCTime
} | ErrorCustomersResp {
errorCustomersMessage :: Text
} deriving (Show, Generic)
$( deriveJSON (toOpts 0 0 def) ''Severity )
$( deriveJSON (toOpts 1 1 def) ''Status )
$( deriveJSON (toOpts 1 1 def) ''Alert )
$( deriveJSON (toOpts 0 0 def { unwrap = False }) ''Tags )
$( deriveJSON (toOpts 0 0 def { unwrap = False }) ''Attributes )
$( deriveJSON (toOpts 2 2 def) ''AlertAttr )
$( deriveJSON (toOpts 2 2 def) ''AlertInfo )
$( deriveJSON (toOpts 4 3 def) ''CreateAlertResp )
$( deriveJSON (toOpts 3 2 def) ''AlertResp )
$( deriveJSON (toOpts 3 2 def) ''AlertsResp )
$( deriveJSON (toOpts 2 2 def) ''ResourceInfo )
$( deriveJSON (toOpts 2 2 def) ''Top10Info )
$( deriveJSON (toOpts 3 2 def) ''Top10Resp )
$( deriveJSON (toOpts 4 3 def) ''AlertCountResp )
$( deriveJSON (toOpts 4 3 def) ''AlertHistoryResp )
$( deriveJSON (toOpts 2 2 def) ''StatusChange )
$( deriveJSON (toOpts 2 1 def) ''Resp )
$( deriveJSON (toOpts 0 0 def) ''TrendIndication )
$( deriveJSON (toOpts 2 2 def { tag = "type" }) ''HistoryItem )
$( deriveJSON (toOpts 4 3 def { tag = "type" }) ''ExtendedHistoryItem )
$( deriveJSON (toOpts 2 2 def) ''EnvironmentInfo )
$( deriveJSON (toOpts 3 2 def) ''EnvironmentsResp )
$( deriveJSON (toOpts 2 2 def) ''ServiceInfo )
$( deriveJSON (toOpts 3 2 def) ''ServicesResp )
$( deriveJSON (toOpts 1 1 def) ''Blackout )
$( deriveJSON (toOpts 2 2 def) ''BlackoutInfo )
$( deriveJSON (toOpts 2 0 def) ''BlackoutStatus )
$( deriveJSON (toOpts 3 3 def) ''ExtendedBlackoutInfo )
$( deriveJSON (toOpts 3 2 def) ''BlackoutResp )
$( deriveJSON (toOpts 3 2 def) ''BlackoutsResp )
$( deriveJSON (toOpts 1 1 def) ''Heartbeat )
$( deriveJSON (toOpts 2 2 def) ''HeartbeatInfo )
$( deriveJSON (toOpts 3 3 def) ''CreateHeartbeatResp )
$( deriveJSON (toOpts 2 2 def) ''HeartbeatResp )
$( deriveJSON (toOpts 2 2 def) ''HeartbeatsResp )
$( deriveJSON (toOpts 3 3 def) ''CreateApiKey )
$( deriveJSON (toOpts 3 3 def) ''ApiKeyInfo )
$( deriveJSON (toOpts 5 4 def) ''CreateApiKeyResp )
$( deriveJSON (toOpts 4 3 def) ''ApiKeysResp )
$( deriveJSON (toOpts 3 2 def) ''RoleType )
$( deriveJSON (toOpts 1 1 def) ''User )
$( deriveJSON (toOpts 2 2 def) ''UserInfo )
$( deriveJSON (toOpts 3 3 def) ''ExtendedUserInfo )
$( deriveJSON (toOpts 3 2 def) ''UserResp )
$( deriveJSON (toOpts 3 2 def) ''UsersResp )
$( deriveJSON (toOpts 1 1 def) ''Customer )
$( deriveJSON (toOpts 2 2 def) ''CustomerInfo )
$( deriveJSON (toOpts 3 2 def) ''CustomerResp )
$( deriveJSON (toOpts 3 2 def) ''CustomersResp )