packages feed

groot-0.0.1.0: src/Groot/Types.hs

{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE LambdaCase           #-}
{-# LANGUAGE OverloadedStrings    #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE TemplateHaskell      #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeSynonymInstances #-}

{-|
Module      : Groot.Types
Description : Type definitions for Groot
Copyright   : A. Alonso Dominguez (c) 2017, http://github.com/alonsodomin
License     : Apache 2.0
Maintainer  : A. Alonso Dominguez <alonso.domin (λ) google>
Stability   : experimental
Portability : portable

This is a module providing global type definitions needed in Groot
-}
module Groot.Types
     ( ServiceId (..)
     , AccountId (..)
     , Arn (..)
     , arnAccountId
     , arnRegion
     , arnResourcePath
     , arnServiceId
     , viewArn
     , Ami (..)
     -- Cluster
     , ClusterName (..)
     , ClusterArnPath (..)
     , ClusterArn
     , arnClusterName
     , ClusterRef(..)
     , ClusterStatus(..)
     , ClusterFilter
     , isActiveCluster
     , isInactiveCluster
     , clusterHasNameOrArn
     -- Container Instance
     , ContainerInstanceId (..)
     , ContainerInstanceArn
     , arnContainerInstanceId
     , ContainerInstanceRef(..)
     -- EC2 Instance
     , EC2InstanceId (..)
     -- Container Service
     , ServiceName (..)
     , ContainerServiceArn
     , arnContainerServiceName
     , ContainerServiceRef(..)
     , ContainerServiceCoords(..)
     , ContainerServiceStatus(..)
     , ContainerServiceFilter
     , isActiveContainerService
     , isInactiveContainerService
     , isContainerService
     -- Task
     , TaskId (..)
     , TaskArn
     , arnTaskId
     , TaskRef(..)
     , TaskStatus(..)
     -- Task Definition
     , TaskDefRef(..)
     , TaskFamily (..)
     , TaskDefId (..)
     , tdiTaskFamily
     , tdiTaskRevision
     , nextTaskDefId
     , TaskDefArn
     , arnTaskDefId
     , arnTaskDefFamily
     , arnTaskDefRevision
     , TaskDefStatus(..)
     , TaskDefFilter(..)
     ) where

import           Control.Lens
import           Control.Monad        (join)
import           Data.Attoparsec.Text
import           Data.Data
import           Data.Monoid
import           Data.String
import           Data.Text            (Text)
import qualified Data.Text            as T
import           Data.UUID            (UUID)
import qualified Data.UUID            as UUID
import           GHC.Generics         hiding (to)
import           Network.AWS
import qualified Network.AWS.ECS      as ECS
import           Prelude              hiding (takeWhile)

import           Groot.Data.Filter
import           Groot.Data.Text

-- | An AWS service identifier, typically used in AWS ARNs
data ServiceId =
    AutoScaling
  | ECS
  | EC2
  deriving (Eq, Show, Generic, Enum, Bounded, Data)

instance FromText ServiceId where
  parser = takeLowerText >>= \case
    "autoscaling" -> pure AutoScaling
    "ecs"         -> pure ECS
    "ec2"         -> pure EC2
    e             ->
      fromTextError $ "Failure parsing service id from " <> e

instance ToText ServiceId where
  toText AutoScaling = "autoscaling"
  toText ECS         = "ecs"
  toText EC2         = "ec2"

-- | An AWS account identifier
newtype AccountId = AccountId Text
  deriving (Eq, Show, Data, Generic)

instance IsString AccountId where
  fromString = AccountId . T.pack

instance ToText AccountId where
  toText (AccountId s) = s

-- | An AWS Resource Name (ARN for short) used to uniquely identify
-- a given resource
data Arn a = Arn
  { _arnServiceId    :: ServiceId
  , _arnRegion       :: Region
  , _arnAccountId    :: AccountId
  , _arnResourcePath :: a
  } deriving (Eq, Show, Data, Generic)

-- |Obtain the 'ServiceId' from a given 'Arn'
arnServiceId :: forall a. Getter (Arn a) ServiceId
arnServiceId = to _arnServiceId

-- |Obtain the AWS region from a given 'Arn'
arnRegion :: forall a. Getter (Arn a) Region
arnRegion = to _arnRegion

-- |Obtain the 'AccountId' from a given 'Arn'
arnAccountId :: forall a. Getter (Arn a) AccountId
arnAccountId = to _arnAccountId

-- |Obtain the resource path from a given 'Arn'
arnResourcePath :: forall a. Getter (Arn a) a
arnResourcePath = to _arnResourcePath

instance FromText a => FromText (Arn a) where
  parser = do
    "arn:aws:"
    serviceId <- subparser =<< takeTill (== ':')
    char ':'
    region    <- subparser =<< takeTill (== ':')
    char ':'
    account   <- takeWhile (/= ':')
    char ':'
    path <- parser
    return $ Arn serviceId region (AccountId account) path

instance ToText a => ToText (Arn a) where
  toText (Arn service region account path) = T.concat [
      "arn:aws:"
    , toText service
    , ":"
    , toText region
    , ":"
    , toText account
    , ":"
    , toText path
    ]

-- |Parse an ARN given a getter an a resource from where to obtain the text form
viewArn :: forall a b. FromText b => Getting (First Text) a Text -> a -> Maybe (Arn b)
viewArn l item = join $ either (\_ -> Nothing) Just <$> fromText <$> item ^? l

-- | An AWS Machine Image, used to uniquely identify a given
-- image for an specific instance
newtype Ami = Ami Text
  deriving (Eq, Show, Data, Generic)

instance FromText Ami where
  parser = do
    "ami-"
    ident <- takeText
    return $ Ami ident

instance ToText Ami where
  toText (Ami ident) = T.append "ami-" ident

-- Cluster

-- |Type describing the cluster name
newtype ClusterName = ClusterName Text
  deriving (Eq, Show, Data, Generic)

instance IsString ClusterName where
  fromString = ClusterName . T.pack

instance FromText ClusterName where
  parser = ClusterName <$> takeText

instance ToText ClusterName where
  toText (ClusterName x) = x

-- |Type describing the ARN path for ECS clusters
newtype ClusterArnPath = ClusterArnPath ClusterName
  deriving (Eq, Show, Data, Generic)

-- |Obtains the cluster name from a given Cluster ARN path
capClusterName :: Getter ClusterArnPath ClusterName
capClusterName = to (\(ClusterArnPath name) -> name)

-- |Type describing cluster ARNs
type ClusterArn = Arn ClusterArnPath

-- |Obtains the cluster name from a cluster ARN
arnClusterName :: Getter ClusterArn ClusterName
arnClusterName = arnResourcePath . capClusterName

capPreffix :: Text
capPreffix = "cluster/"

instance FromText ClusterArnPath where
  parser = do
    string capPreffix
    clusterName <- parser
    return $ ClusterArnPath clusterName

instance ToText ClusterArnPath where
  toText (ClusterArnPath clusterName) =
    T.append capPreffix $ toText clusterName

-- |Type used across the code to make references to clusters
newtype ClusterRef = ClusterRef Text
  deriving (Eq, Show, Data, Generic)

instance IsString ClusterRef where
  fromString = ClusterRef . T.pack

instance ToText ClusterRef where
  toText (ClusterRef txt) = txt

-- |Different statuses available for clusters
data ClusterStatus =
    CSActive
  | CSInactive
  deriving (Eq, Show, Ord, Read, Enum, Bounded, Data, Generic)

instance ToText ClusterStatus where
  toText CSActive   = "ACTIVE"
  toText CSInactive = "INACTIVE"

-- |Filter definition for clusters
data ClusterFilter =
    CFRef ClusterRef
  | CFStatus ClusterStatus
  deriving (Eq, Show)

-- |Cluster filter predicates based on status
isActiveCluster, isInactiveCluster :: ClusterFilter
isActiveCluster   = CFStatus CSActive
isInactiveCluster = CFStatus CSInactive

-- |Cluster filter based on name or ARN
clusterHasNameOrArn :: Text -> ClusterFilter
clusterHasNameOrArn = CFRef . ClusterRef

instance Filter ClusterFilter where
  type FilterItem ClusterFilter = ECS.Cluster

  matches (CFRef (ClusterRef txt)) cluster =
       maybe False (== txt) (cluster ^. ECS.cClusterName)
    || maybe False (== txt) (cluster ^. ECS.cClusterARN)
  matches (CFStatus CSActive) cluster =
    maybe False (== (T.pack "ACTIVE")) $ cluster ^. ECS.cStatus
  matches (CFStatus CSInactive) cluster =
    maybe False (== (T.pack "INACTIVE")) $ cluster ^. ECS.cStatus

-- Container Instance

-- |Type describing a Container Instance Identifier
newtype ContainerInstanceId = ContainerInstanceId UUID
  deriving (Eq, Show)

instance FromText ContainerInstanceId where
  parser = ContainerInstanceId <$> uuid

instance ToText ContainerInstanceId where
  toText (ContainerInstanceId s) = UUID.toText s

-- |Type describing a Container Instance ARN path
newtype ContainerInstanceArnPath = ContainerInstanceArnPath ContainerInstanceId
  deriving (Eq, Show)

-- |Obtain a Container Instance Identifier from a Container Instance ARN
ciapContainerInstanceId :: Getter ContainerInstanceArnPath ContainerInstanceId
ciapContainerInstanceId = to (\(ContainerInstanceArnPath x) -> x)

-- |Type describing a Container Instance ARN
type ContainerInstanceArn = Arn ContainerInstanceArnPath

-- |Obtain a Container Instance Identifier from a Container Instance ARN
arnContainerInstanceId :: Getter ContainerInstanceArn ContainerInstanceId
arnContainerInstanceId = arnResourcePath . ciapContainerInstanceId

ciapPreffix :: Text
ciapPreffix = "container-instance/"

instance FromText ContainerInstanceArnPath where
  parser = do
    string ciapPreffix
    instanceId <- parser
    return $ ContainerInstanceArnPath instanceId

instance ToText ContainerInstanceArnPath where
  toText (ContainerInstanceArnPath instanceId) =
    T.append ciapPreffix $ toText instanceId

-- |Type used across the code to make references to container instances
newtype ContainerInstanceRef = ContainerInstanceRef Text
  deriving (Eq, Show, Data, Read, Generic)

instance ToText ContainerInstanceRef where
  toText (ContainerInstanceRef s) = s

instance IsString ContainerInstanceRef where
  fromString = ContainerInstanceRef . T.pack

-- EC2 Instance

newtype EC2InstanceId = EC2InstanceId Text
  deriving (Eq, Show)

instance ToText EC2InstanceId where
  toText (EC2InstanceId id) = id

-- Container Service

-- |Type describing a service name
newtype ServiceName = ServiceName Text
  deriving (Eq, Show)

instance FromText ServiceName where
  parser = ServiceName <$> takeText

instance ToText ServiceName where
  toText (ServiceName s) = s

-- |Type describing a service ARN path
newtype ContainerServiceArnPath = ContainerServiceArnPath ServiceName
  deriving (Eq, Show)

-- |Obtain a service name from a service ARN path
csapContainerServiceName :: Getter ContainerServiceArnPath ServiceName
csapContainerServiceName = to (\(ContainerServiceArnPath x) -> x)

-- |Type describing a service ARN
type ContainerServiceArn = Arn ContainerServiceArnPath

-- |Obtain a service name from a service ARN
arnContainerServiceName :: Getter ContainerServiceArn ServiceName
arnContainerServiceName = arnResourcePath . csapContainerServiceName

csapPreffix :: Text
csapPreffix = "service/"

instance FromText ContainerServiceArnPath where
  parser = do
    string csapPreffix
    serviceName <- parser
    return $ ContainerServiceArnPath serviceName

instance ToText ContainerServiceArnPath where
  toText (ContainerServiceArnPath serviceName) =
    T.append csapPreffix $ toText serviceName

-- |Type used across the code to make references to services
newtype ContainerServiceRef = ContainerServiceRef Text
  deriving (Eq, Show, Generic, Data, Read)

instance IsString ContainerServiceRef where
  fromString = ContainerServiceRef . T.pack

instance ToText ContainerServiceRef where
  toText (ContainerServiceRef s) = s

-- |Service coordinates are compoound of a service reference and a cluster reference.
-- They uniquely identify a service
data ContainerServiceCoords = ContainerServiceCoords ContainerServiceRef ClusterRef
  deriving (Eq, Show, Generic, Data)

-- |Service statuses
data ContainerServiceStatus =
    CSSActive
  | CSSInactive
  deriving (Eq, Show, Ord, Enum, Bounded, Data, Generic)

-- |Service filters
data ContainerServiceFilter =
    CSFRef ContainerServiceRef
  | CSFStatus ContainerServiceStatus
  deriving (Eq, Show)

-- |Service filter predicate based on service status
isActiveContainerService, isInactiveContainerService :: ContainerServiceFilter
isActiveContainerService   = CSFStatus CSSActive
isInactiveContainerService = CSFStatus CSSInactive

-- |Service filter preficate based on service name or ARN
isContainerService :: ContainerServiceRef -> ContainerServiceFilter
isContainerService = CSFRef

instance Filter ContainerServiceFilter where
  type FilterItem ContainerServiceFilter = ECS.ContainerService

  matches (CSFStatus CSSActive) serv =
    maybe False (== (T.pack "ACTIVE")) (serv ^. ECS.csStatus)
  matches (CSFStatus CSSInactive) serv =
    maybe False (== (T.pack "INACTIVE")) (serv ^. ECS.csStatus)

  matches (CSFRef (ContainerServiceRef ref)) serv =
       maybe False (== ref) (serv ^. ECS.csServiceName)
    || maybe False (== ref) (serv ^. ECS.csServiceARN)

-- Task

-- |Type describing a task identifier
newtype TaskId = TaskId UUID
  deriving (Eq, Show)

instance FromText TaskId where
  parser = TaskId <$> uuid

instance ToText TaskId where
  toText (TaskId s) = UUID.toText s

-- |Type describing a task ARN path
newtype TaskArnPath = TaskArnPath TaskId
  deriving (Eq, Show)

-- |Obtain a task identifier from a task ARN
tapTaskId :: Getter TaskArnPath TaskId
tapTaskId = to (\(TaskArnPath x) -> x)

-- |Type describing a task ARN
type TaskArn = Arn TaskArnPath

-- |Obtain a task identifier from a task ARN
arnTaskId :: Getter TaskArn TaskId
arnTaskId = arnResourcePath . tapTaskId

tapPreffix :: Text
tapPreffix = "task/"

instance FromText TaskArnPath where
  parser = do
    string tapPreffix
    taskId <- parser
    return $ TaskArnPath taskId

instance ToText TaskArnPath where
  toText (TaskArnPath taskId) =
    T.append tapPreffix $ toText taskId

-- |Type used across the code to make references to tasks
newtype TaskRef = TaskRef Text
  deriving (Eq, Show, Generic, Data, Read)

instance IsString TaskRef where
  fromString = TaskRef . T.pack

instance ToText TaskRef where
  toText (TaskRef t) = t

-- |Task statuses
data TaskStatus =
    TSRunning
  | TSStopped
  | TSPending
  deriving (Eq, Show, Ord, Enum, Bounded, Data, Generic, Read)

-- Task Definition

-- |Type used across the code to make references to task definitions
newtype TaskDefRef = TaskDefRef Text
  deriving (Eq, Generic, Data, Show, Read)

instance IsString TaskDefRef where
  fromString = TaskDefRef . T.pack

instance ToText TaskDefRef where
  toText (TaskDefRef t) = t

-- |Type describing a task definition family
newtype TaskFamily = TaskFamily Text
  deriving (Eq, Show, Data, Generic)

instance IsString TaskFamily where
  fromString = TaskFamily . T.pack

instance FromText TaskFamily where
  parser = TaskFamily <$> takeText

instance ToText TaskFamily where
  toText (TaskFamily s) = s

-- |A Task Definition Identifier, compound of a task family and a revision number
data TaskDefId = TaskDefId
  { _tdiTaskFamily   :: TaskFamily
  , _tdiTaskRevision :: Int
  } deriving (Eq, Show, Data, Generic)

makeLenses ''TaskDefId

-- |Increments the revision number of a given task definition
nextTaskDefId :: TaskDefId -> TaskDefId
nextTaskDefId (TaskDefId family revision) = TaskDefId family (revision + 1)

instance FromText TaskDefId where
  parser = do
    family   <- subparser =<< takeTill (== ':')
    char ':'
    revision <- parser
    return $ TaskDefId family revision

instance ToText TaskDefId where
  toText (TaskDefId family revision) =
    T.concat [toText family, ":", toText revision]

-- |Type describing the task definition ARN path
newtype TaskDefArnPath = TaskDefArnPath TaskDefId
  deriving (Eq, Show, Data, Generic)

-- |Obtain a task definition identifier from a task definition ARN path
tdapTaskDefId :: Getter TaskDefArnPath TaskDefId
tdapTaskDefId = to (\(TaskDefArnPath x) -> x)

-- |Type describing a task definition ARN
type TaskDefArn = Arn TaskDefArnPath

-- |Obtain a task definition identifier from a task definition ARN
arnTaskDefId :: Getter TaskDefArn TaskDefId
arnTaskDefId = arnResourcePath . tdapTaskDefId

-- |Obtain a task definition family from a task definition ARN
arnTaskDefFamily :: Getter TaskDefArn TaskFamily
arnTaskDefFamily = arnTaskDefId . tdiTaskFamily

-- |Obtain a task definition revision number from a task definition ARN
arnTaskDefRevision :: Getter TaskDefArn Int
arnTaskDefRevision = arnTaskDefId . tdiTaskRevision

tdapPreffix :: Text
tdapPreffix = "task-definition/"

instance FromText TaskDefArnPath where
  parser = do
    string tdapPreffix
    taskDefId <- parser
    return $ TaskDefArnPath taskDefId

instance ToText TaskDefArnPath where
  toText (TaskDefArnPath taskDefId) =
    T.append tdapPreffix $ toText taskDefId

-- |Task definition statuses
data TaskDefStatus =
    TDSActive
  | TDSInactive
  deriving (Eq, Show, Ord, Enum, Bounded, Read, Generic, Data)

-- |Task definition filters
data TaskDefFilter =
    TDFFamily TaskFamily
  | TDFStatus TaskDefStatus
  deriving (Eq, Show)

instance Filter TaskDefFilter where
  type FilterItem TaskDefFilter = ECS.TaskDefinition

  matches (TDFFamily (TaskFamily family)) taskDef =
    maybe False (== family) $ taskDef ^. ECS.tdFamily
  matches (TDFStatus TDSActive) taskDef =
    maybe False (== ECS.TDSActive) $ taskDef ^. ECS.tdStatus
  matches (TDFStatus TDSInactive) taskDef =
    maybe False (== ECS.TDSInactive) $ taskDef ^. ECS.tdStatus