module DigitalOcean where
import Data.Aeson as A
import Data.Aeson.Types as A
import Data.Default
import qualified Data.HashMap.Lazy as H
import Data.IP
import Data.List (elemIndex)
import Data.Monoid ((<>))
import Data.Text (unpack)
import Data.Time (UTCTime)
import GHC.Generics
type AuthToken = String
type Slug = String
type URI = String
data ToolConfiguration = Tool { slackUri :: Maybe URI
, authToken :: Maybe AuthToken
, quiet :: Bool
} deriving (Show,Read)
instance Default ToolConfiguration where
def = Tool Nothing Nothing False
data Region = Region { regionName :: String
, regionSlug :: Slug
, regionSizes :: [ SizeSlug ]
, regionAvailable :: Bool
}
| RegionSlug Slug
| NoRegion
instance ToJSON Region where
toJSON (RegionSlug s) = toJSON s
toJSON NoRegion = object []
toJSON Region{..} = object [ "name" .= regionName
, "slug" .= regionSlug
, "sizes" .= regionSizes
, "available" .= regionAvailable
]
instance Show Region where
show (RegionSlug s) = s
show NoRegion = "NoRegion"
show Region{..} = "Region { regionName = " <> show regionName <>
", regionSlug = " <> show regionSlug <>
", regionSizes = " <> show regionSizes <>
", regionAvailable = " <> show regionAvailable <>
"}"
instance FromJSON Region where
parseJSON (String s) = return $ RegionSlug (unpack s)
parseJSON (Object o) = if H.null o
then return NoRegion
else Region
<$> o .: "name"
<*> o .: "slug"
<*> o .: "sizes"
<*> o .: "available"
parseJSON e = failParse e
sizeSlugs :: [String]
sizeSlugs = [ "512mb", "1gb", "2gb", "4gb", "8gb", "16gb", "32gb", "48gb", "64gb", "96gb" ]
data SizeSlug = M512 | G1 | G2 | G4 | G8 | G16 | G32 | G48 | G64 | G96
deriving (Enum,Ord,Eq)
instance Show SizeSlug where
show sz = sizeSlugs !! fromEnum sz
instance Read SizeSlug where
readsPrec _ sz = case elemIndex sz sizeSlugs of
Just i -> return (toEnum i, "")
Nothing -> fail $ "cannot parse " <> sz
instance ToJSON SizeSlug where
toJSON sz = toJSON $ sizeSlugs !! fromEnum sz
instance FromJSON SizeSlug where
parseJSON (String s) = case elemIndex (unpack s) sizeSlugs of
Just i -> return $ toEnum i
Nothing -> fail $ "cannot parse " <> unpack s
parseJSON e = failParse e
type ImageSlug = String
type KeyId = Int
defaultImage :: ImageSlug
defaultImage = "ubuntu-14-04-x64"
data BoxConfiguration = BoxConfiguration { configName :: String
, boxRegion :: Region
, size :: SizeSlug
, configImageSlug :: ImageSlug
, keys :: [KeyId]
} deriving (Show)
instance ToJSON BoxConfiguration where
toJSON BoxConfiguration{..} = object [ "name" .= configName
, "region" .= boxRegion
, "size" .= size
, "image" .= configImageSlug
, "ssh_keys" .= keys
, "backups" .= False
, "ipv6" .= False
, "private_networking" .= False
]
type Id = Integer
data Mega
data Giga
newtype Bytes a = Bytes { bytesSize :: Int } deriving Show
jsonBytes :: Int -> Parser (Bytes a)
jsonBytes = return . Bytes
instance FromJSON (Bytes Mega) where
parseJSON (Number n) = jsonBytes (truncate n)
parseJSON e = failParse e
instance FromJSON (Bytes Giga) where
parseJSON (Number n) = jsonBytes (truncate n)
parseJSON e = failParse e
newtype Date = Date { theDate :: UTCTime } deriving Show
instance FromJSON Date where
parseJSON d@(String _) = Date <$> parseJSON d
parseJSON e = failParse e
data Status = New
| Active
| Off
| Archive
deriving (Eq,Show)
instance FromJSON Status where
parseJSON (String s) = case s of
"new" -> return New
"active" -> return Active
"off" -> return Off
"archive" -> return Archive
_ -> fail $ "cannot parse " <> unpack s
parseJSON e = failParse e
data NetType = Public | Private deriving (Show, Eq)
data Network a = NetworkV4 { ip_address :: IP
, netmask :: IP
, gateway :: IP
, netType :: NetType
}
| NetworkV6 { ip_address :: IP
, netmask_v6 :: Int
, gateway :: IP
, netType :: NetType
} deriving Show
instance FromJSON IP where
parseJSON (String s) = return $ read $ unpack s
parseJSON e = fail $ "cannot parse IP " <> show e
instance FromJSON NetType where
parseJSON (String s) = case s of
"public" -> return Public
"private" -> return Private
e -> failParse e
parseJSON e = failParse e
data V4
data V6
jsonNetwork :: (FromJSON a3, FromJSON a2, FromJSON a1, FromJSON a) => (a3 -> a2 -> a1 -> a -> b) -> Object -> Parser b
jsonNetwork f n = f
<$> (n .: "ip_address")
<*> (n .: "netmask")
<*> (n .: "gateway")
<*> (n .: "type")
instance FromJSON (Network V4) where
parseJSON (Object n) = jsonNetwork NetworkV4 n
parseJSON e = failParse e
instance FromJSON (Network V6) where
parseJSON (Object n) = jsonNetwork NetworkV6 n
parseJSON e = fail $ "cannot parse network v6 " <> show e
data Networks = Networks { v4 :: [ Network V4 ]
, v6 :: [ Network V6 ]
}
| NoNetworks
deriving (Generic, Show)
instance FromJSON Networks where
parseJSON (Object n) = if H.null n
then return NoNetworks
else Networks
<$> (n .: "v4")
<*> (n .: "v6")
parseJSON e = fail $ "cannot parse network v6 " <> show e
data Droplet = Droplet { id :: Id
, name :: String
, memory :: Bytes Mega
, vcpus :: Int
, disk :: Bytes Giga
, locked :: Bool
, created_at :: Date
, status :: Status
, backup_ids :: [ Id ]
, snapshot_ids :: [ Id ]
, region :: Region
, size_slug :: SizeSlug
, networks :: Networks
} deriving (Show)
instance FromJSON Droplet where
parseJSON (Object o) = Droplet
<$> o .: "id"
<*> o .: "name"
<*> o .: "memory"
<*> o .: "vcpus"
<*> o .: "disk"
<*> o .: "locked"
<*> o .: "created_at"
<*> o .: "status"
<*> o .: "backup_ids"
<*> o .: "snapshot_ids"
<*> o .: "region"
<*> o .: "size_slug"
<*> o .: "networks"
parseJSON e = fail $ "cannot parse network v6 " <> show e
data ImageType = Snapshot
| Temporary
| Backup
deriving Show
instance FromJSON ImageType where
parseJSON (String s) = case s of
"snapshot" -> return Snapshot
"temporary" -> return Temporary
"backup" -> return Backup
_ -> fail $ "cannot parse " <> unpack s
parseJSON e = failParse e
data Image = Image { imageId :: Id
, imageName :: String
, distribution :: String
, imageSlug :: Maybe Slug
, publicImage :: Bool
, imageRegions :: [ Region ]
, min_disk_size :: Bytes Giga
, image_created_at :: Date
, imageType :: ImageType
} deriving Show
instance FromJSON Image where
parseJSON (Object o) = Image
<$> o .: "id"
<*> o .: "name"
<*> o .: "distribution"
<*> o .:? "slug"
<*> o .: "public"
<*> o .: "regions"
<*> o .: "min_disk_size"
<*> o .: "created_at"
<*> o .: "type"
parseJSON e = failParse e
--https://developers.digitalocean.com/documentation/v2/#ssh-keys
data Key = Key { keyId :: Id
, keyFingerprint :: String
, publicKey :: String
, keyName :: String
} deriving Show
instance FromJSON Key where
parseJSON (Object o) = Key
<$> o .: "id"
<*> o .: "fingerprint"
<*> o .: "public_key"
<*> o .: "name"
parseJSON e = failParse e
type TransferRate = Double
type Price = Double
data Size = Size { szSlug :: SizeSlug
, szMemory :: Bytes Mega
, szVcpus :: Int
, szDisk :: Bytes Giga
, szTransfer :: TransferRate
, szPrice_Monthly :: Price
, szPrice_Hourly :: Price
, szRegions :: [ Region ]
, szAvailable :: Bool
} deriving (Show)
instance FromJSON Size where
parseJSON (Object o) = Size
<$> o .: "slug"
<*> o .: "memory"
<*> o .: "vcpus"
<*> o .: "disk"
<*> o .: "transfer"
<*> o .: "price_monthly"
<*> o .: "price_hourly"
<*> o .: "regions"
<*> o .: "available"
parseJSON e = failParse e
data ActionResult = ActionResult { actionId :: Id
, actionStatus :: ActionStatus
, actionType :: ActionType
, actionStartedAt :: Maybe Date
, actionCompletedAt :: Maybe Date
, actionResourceId :: Id
, actionResourceType :: String
, actionRegionSlug :: Region
} deriving (Show)
instance FromJSON ActionResult where
parseJSON (Object o) = ActionResult
<$> o .: "id"
<*> o .: "status"
<*> o .: "type"
<*> o .:? "started_at"
<*> o .:? "completed_at"
<*> o .: "resource_id"
<*> o .: "resource_type"
<*> o .: "region_slug"
parseJSON v = fail $ "cannot parse action " ++ show v
data ActionStatus = InProgress
| Completed
| Errored
deriving (Show)
instance FromJSON ActionStatus where
parseJSON (String s) = case s of
"in-progress" -> return InProgress
"completed" -> return Completed
"errored" -> return Errored
_ -> fail $ "unknown action status " ++ show s
parseJSON v = fail $ "cannot parse action status " ++ show v
data ActionType = PowerOff
| PowerOn
deriving (Show)
instance FromJSON ActionType where
parseJSON (String s) = case s of
"power_off" -> return PowerOff
"power_on" -> return PowerOn
_ -> fail $ "unknown action type " ++ show s
parseJSON v = fail $ "cannot parse action type " ++ show v
instance ToJSON ActionType where
toJSON PowerOff = String "power_off"
toJSON PowerOn = String "power_on"
data Action = DoPowerOff
| DoPowerOn
deriving Show
instance ToJSON Action where
toJSON DoPowerOff = object [ "type" .= PowerOff ]
toJSON DoPowerOn = object [ "type" .= PowerOn ]
data Domain = Domain { domainName :: String
, domainTTL :: Int
, zone_file :: String
} deriving (Show)
instance FromJSON Domain where
parseJSON (Object o) = Domain
<$> o .: "name"
<*> o .: "ttl"
<*> o .: "zone_file"
parseJSON e = failParse e
data DNSType = A | CNAME | TXT | PTR | SRV | NS | AAAA | MX
deriving (Show, Read, Generic)
instance FromJSON DNSType
instance ToJSON DNSType
data DomainRecord = DomainRecord { recordId :: Id
, recordType :: DNSType
, recordName :: String
, recordData :: String
, recordPriority :: Maybe Double
, recordPort :: Maybe Int
, recordWeight :: Maybe Double
} deriving (Show)
instance FromJSON DomainRecord where
parseJSON (Object o) = DomainRecord
<$> o .: "id"
<*> o .: "type"
<*> o .: "name"
<*> o .: "data"
<*> o .: "priority"
<*> o .: "port"
<*> o .: "weight"
parseJSON e = failParse e
failParse :: (Show a1, Monad m) => a1 -> m a
failParse e = fail $ "cannot parse " <> show e