cloudy-0.1.0.0: src/Cloudy/Scaleway.hs
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE EmptyDataDecls #-}
module Cloudy.Scaleway where
import Data.Aeson (ToJSON(..), object, (.=), FromJSON (..), withObject, Value, (.:), withText)
import Data.Map.Strict (Map)
import Data.Proxy (Proxy (Proxy))
import Data.Text (Text)
import Servant.API ((:>), Capture, ReqBody, JSON, AuthProtect, (:<|>) ((:<|>)), PostCreated, Get, QueryParam, Headers, Header, PlainText, NoContent, MimeRender (mimeRender), PatchNoContent, Accept (contentType), PostAccepted, Patch, DeleteNoContent, MimeUnrender (..))
import Servant.Client (client, ClientM)
import Servant.Client.Core (AuthenticatedRequest)
import Web.HttpApiData (ToHttpApiData (..), FromHttpApiData)
import Data.Aeson.Types (Parser)
import Data.Kind (Type)
import Data.Time (UTCTime)
import Network.HTTP.Media ((//))
data PlainTextNoUTF8
instance Accept PlainTextNoUTF8 where
contentType _ = "text" // "plain"
instance MimeRender PlainTextNoUTF8 Text where
mimeRender _ = mimeRender (Proxy @PlainText)
instance MimeUnrender PlainTextNoUTF8 Text where
mimeUnrender _ = mimeUnrender (Proxy @PlainText)
newtype PerPage = PerPage { unPerPage :: Int }
deriving stock Show
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype PageNum = PageNum { unPageNum :: Int }
deriving stock Show
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
type Paged :: forall k l. (k -> l -> Type) -> k -> l -> Type
type family Paged verb ct resp where
Paged verb ct resp =
QueryParam "per_page" PerPage :>
QueryParam "page" PageNum :>
verb ct (Headers '[Header "x-total-count" Int] resp)
data Zone = NL1 | NL2 | NL3
deriving (Bounded, Enum, Eq, Show)
instance ToJSON Zone where toJSON = toJSON . zoneToText
instance FromJSON Zone where
parseJSON = withText "Zone" $ maybe (fail "Failed to parse Zone") pure . zoneFromText
allScalewayZones :: [Zone]
allScalewayZones = enumFromTo minBound maxBound
zoneToText :: Zone -> Text
zoneToText = \case
NL1 -> "nl-ams-1"
NL2 -> "nl-ams-2"
NL3 -> "nl-ams-3"
zoneFromText :: Text -> Maybe Zone
zoneFromText = \case
"nl-ams-1" -> Just NL1
"nl-ams-2" -> Just NL2
"nl-ams-3" -> Just NL3
_ -> Nothing
instance ToHttpApiData Zone where
toUrlPiece :: Zone -> Text
toUrlPiece = zoneToText
newtype ImageId = ImageId { unImageId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype IpId = IpId { unIpId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype OrganizationId = OrganizationId { unOrganizationId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype ProjectId = ProjectId { unProjectId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype ServerId = ServerId { unServerId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype VolumeId = VolumeId { unVolumeId :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype UserDataKey = UserDataKey { unUserDataKey :: Text }
deriving stock (Eq, Show)
deriving newtype (FromHttpApiData, FromJSON, ToHttpApiData, ToJSON)
newtype UserData = UserData { unUserData :: Text }
deriving stock (Eq, Show)
deriving newtype (MimeRender PlainTextNoUTF8, MimeUnrender PlainTextNoUTF8)
data ServersReqVolume = ServersReqVolume
{ {- name :: Text
, -} size :: Int
, volumeType :: Text
}
deriving stock Show
instance ToJSON ServersReqVolume where
toJSON volume =
object
[ {- "name" .= volume.name
, -} "size" .= volume.size
, "volume_type" .= volume.volumeType
]
data ServersRespVolume = ServersRespVolume
{ id :: VolumeId
, name :: Text
, size :: Int
, volumeType :: Text
}
deriving stock Show
instance FromJSON ServersRespVolume where
parseJSON :: Value -> Parser ServersRespVolume
parseJSON = withObject "ServersRespVolume" $ \o -> do
id_ <- o .: "id"
name <- o .: "name"
size <- o .: "size"
volumeType <- o .: "volume_type"
pure ServersRespVolume { id = id_, name, size, volumeType }
data IpsReq = IpsReq
{ type_ :: Text
, project :: ProjectId
}
deriving stock Show
instance ToJSON IpsReq where
toJSON ipsReq =
object
[ "type" .= ipsReq.type_
, "project" .= ipsReq.project
]
data IpsResp = IpsResp
{ id :: IpId
, address :: Text
, organization :: OrganizationId
, project :: ProjectId
, zone :: Zone
}
deriving stock Show
instance FromJSON IpsResp where
parseJSON :: Value -> Parser IpsResp
parseJSON = withObject "IpsResp outer wrapper" $ \o -> do
innerObj <- o .: "ip"
id_ <- innerObj .: "id"
address <- innerObj .: "address"
organization <- innerObj .: "organization"
project <- innerObj .: "project"
zone <- innerObj .: "zone"
pure IpsResp { id = id_, address, organization, project, zone }
data ServersReq = ServersReq
{ bootType :: Text
, commercialType :: Text
, image :: ImageId
, name :: Text
, publicIps :: [IpId]
, tags :: [Text]
, volumes :: Map Text ServersReqVolume
, project :: ProjectId
}
deriving stock Show
instance ToJSON ServersReq where
toJSON serversReq =
object
[ "boot_type" .= serversReq.bootType
, "commercial_type" .= serversReq.commercialType
, "image" .= serversReq.image
, "name" .= serversReq.name
, "public_ips" .= serversReq.publicIps
, "tags" .= serversReq.tags
, "volumes" .= serversReq.volumes
, "project" .= serversReq.project
]
data ServersResp = ServersResp
{ id :: ServerId
, name :: Text
, volumes :: Map Text ServersRespVolume
, state :: Text
}
deriving stock Show
instance FromJSON ServersResp where
parseJSON :: Value -> Parser ServersResp
parseJSON = withObject "ServersResp outer wrapper" $ \o -> do
innerObj <- o .: "server"
id_ <- innerObj .: "id"
name <- innerObj .: "name"
volumes <- innerObj .: "volumes"
state <- innerObj .: "state"
pure ServersResp { id = id_, name, volumes, state }
data ServersActionReq = ServersActionReq
{ action :: Text
}
deriving stock Show
instance ToJSON ServersActionReq where
toJSON serversActionReq =
object
[ "action" .= serversActionReq.action
]
data VolumesReq = VolumesReq
{ name :: Text
}
deriving stock Show
instance ToJSON VolumesReq where
toJSON volumesReq =
object
[ "name" .= volumesReq.name
]
data TaskResp = TaskResp
{ id :: Text
, description :: Text
, status :: Text
}
deriving stock Show
instance FromJSON TaskResp where
parseJSON :: Value -> Parser TaskResp
parseJSON = withObject "TaskResp outer wrapper" $ \o -> do
innerObj <- o .: "task"
id_ <- innerObj .: "id"
description <- innerObj .: "description"
status <- innerObj .: "status"
pure TaskResp { id = id_, description, status }
data VolumeConstraint = VolumeConstraint
{ minSize :: Int
, maxSize :: Int
}
deriving stock Show
instance FromJSON VolumeConstraint where
parseJSON :: Value -> Parser VolumeConstraint
parseJSON = withObject "VolumeConstraint" $ \o -> do
minSize <- o .: "min_size"
maxSize <- o .: "max_size"
pure VolumeConstraint { minSize, maxSize }
newtype ProductServersResp = ProductServersResp
{ unProductServersResp :: Map Text ProductServer }
deriving stock Show
instance FromJSON ProductServersResp where
parseJSON :: Value -> Parser ProductServersResp
parseJSON = withObject "ProductServersResp outer wrapper" $ \o -> do
productServers <- o .: "servers"
pure $ ProductServersResp productServers
data ProductServer = ProductServer
{ monthlyPrice :: Float
, ncpus :: Int
, ram :: Int
, arch :: Text
, sumInternetBandwidth :: Int
, altNames :: [Text]
, volumesConstraint :: VolumeConstraint
}
deriving stock Show
instance FromJSON ProductServer where
parseJSON :: Value -> Parser ProductServer
parseJSON = withObject "ProductServer" $ \o -> do
altNames <- o .: "alt_names"
monthlyPrice <- o .: "monthly_price"
ncpus <- o .: "ncpus"
ram <- o .: "ram"
arch <- o .: "arch"
volumesConstraint <- o .: "volumes_constraint"
networkObj <- o .: "network"
sumInternetBandwidth <- networkObj .: "sum_internal_bandwidth"
pure ProductServer
{ monthlyPrice
, ncpus
, ram
, sumInternetBandwidth
, arch
, altNames
, volumesConstraint
}
newtype ProductServersAvailabilityResp = ProductServersAvailabilityResp { unProductServersAvailabilityResp :: Map Text Text }
deriving stock Show
instance FromJSON ProductServersAvailabilityResp where
parseJSON :: Value -> Parser ProductServersAvailabilityResp
parseJSON = withObject "ProductServersAvailabilityResp outer wrapper" $ \o -> do
serversObj :: Map Text Value <- o .: "servers"
availabilityMap <- traverse parseAvail serversObj
pure $ ProductServersAvailabilityResp availabilityMap
where
parseAvail :: Value -> Parser Text
parseAvail = withObject "ProductServersAvailability availability obj" $ \a -> a .: "availability"
newtype ImagesResp = ImagesResp { unImagesResp :: [Image] }
deriving stock Show
instance FromJSON ImagesResp where
parseJSON :: Value -> Parser ImagesResp
parseJSON = withObject "ImagesResp outer wrapper" $ \o -> do
images <- o .: "images"
pure $ ImagesResp images
data Image = Image
{ id :: Text
, name :: Text
, arch :: Text
, creationDate :: UTCTime
, modificationDate :: UTCTime
, state :: Text
, rootVolId :: Text
, rootVolName :: Text
, rootVolType :: Text
, rootVolSize :: Int
}
deriving stock Show
instance FromJSON Image where
parseJSON :: Value -> Parser Image
parseJSON = withObject "Image" $ \o -> do
id' <- o .: "id"
name <- o .: "name"
arch <- o .: "arch"
creationDate <- o .: "creation_date"
modificationDate <- o .: "modification_date"
state <- o .: "state"
rootVol <- o .: "root_volume"
rootVolId <- rootVol .: "id"
rootVolName <- rootVol .: "name"
rootVolType <- rootVol .: "volume_type"
rootVolSize <- rootVol .: "size"
pure
Image
{ id = id'
, name
, arch
, creationDate
, modificationDate
, state
, rootVolId
, rootVolName
, rootVolType
, rootVolSize
}
type InstanceIpsPostApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"ips" :>
ReqBody '[JSON] IpsReq :>
PostCreated '[JSON] IpsResp
type InstanceIpsDeleteApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"ips" :>
Capture "ip_id" IpId :>
DeleteNoContent
type InstanceServersPostApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"servers" :>
ReqBody '[JSON] ServersReq :>
PostCreated '[JSON] ServersResp
type InstanceServersGetApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"servers" :>
Capture "server_id" ServerId :>
Get '[JSON] ServersResp
type InstanceServersActionPostApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"servers" :>
Capture "server_id" ServerId :>
"action" :>
ReqBody '[JSON] ServersActionReq :>
PostAccepted '[JSON] TaskResp
type InstanceServersUserDataGetApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"servers" :>
Capture "server_id" ServerId :>
"user_data" :>
Capture "key" UserDataKey :>
Get '[PlainTextNoUTF8] UserData
type InstanceServersUserDataPatchApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"servers" :>
Capture "server_id" ServerId :>
"user_data" :>
Capture "key" UserDataKey :>
ReqBody '[PlainTextNoUTF8] UserData :>
PatchNoContent
type InstanceVolumesPatchApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"volumes" :>
Capture "volume_id" VolumeId :>
ReqBody '[JSON] VolumesReq :>
Patch '[JSON] Value
type InstanceProductsServersGetApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"products" :>
"servers" :>
QueryParam "per_page" PerPage :>
Get '[JSON] ProductServersResp
type InstanceProductsServersAvailabilityGetApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"products" :>
"servers" :>
"availability" :>
QueryParam "per_page" PerPage :>
Get '[JSON] ProductServersAvailabilityResp
type InstanceImagesGetApi =
AuthProtect "auth-token" :>
"instance" :>
"v1" :>
"zones" :>
Capture "zone" Zone :>
"images" :>
QueryParam "arch" Text :>
Paged Get '[JSON] ImagesResp
type ScalewayApi =
InstanceIpsPostApi :<|>
InstanceIpsDeleteApi :<|>
InstanceServersPostApi :<|>
InstanceServersGetApi :<|>
InstanceServersActionPostApi :<|>
InstanceServersUserDataGetApi :<|>
InstanceServersUserDataPatchApi :<|>
InstanceVolumesPatchApi :<|>
InstanceProductsServersGetApi :<|>
InstanceProductsServersAvailabilityGetApi :<|>
InstanceImagesGetApi
scalewayApi :: Proxy ScalewayApi
scalewayApi = Proxy
ipsPostApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
IpsReq ->
ClientM IpsResp
ipsDeleteApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
IpId ->
ClientM NoContent
serversPostApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
ServersReq ->
ClientM ServersResp
serversGetApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
ServerId ->
ClientM ServersResp
serversActionPostApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
ServerId ->
ServersActionReq ->
ClientM TaskResp
serversUserDataGetApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
ServerId ->
UserDataKey ->
ClientM UserData
serversUserDataPatchApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
ServerId ->
UserDataKey ->
UserData ->
ClientM NoContent
volumesPatchApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
VolumeId ->
VolumesReq ->
ClientM Value
productsServersGetApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
Maybe PerPage ->
ClientM ProductServersResp
productsServersAvailabilityGetApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
Maybe PerPage ->
ClientM ProductServersAvailabilityResp
imagesGetApi ::
AuthenticatedRequest (AuthProtect "auth-token") ->
Zone ->
Maybe Text ->
Maybe PerPage ->
Maybe PageNum ->
ClientM (Headers '[Header "x-total-count" Int] ImagesResp)
ipsPostApi
:<|> ipsDeleteApi
:<|> serversPostApi
:<|> serversGetApi
:<|> serversActionPostApi
:<|> serversUserDataGetApi
:<|> serversUserDataPatchApi
:<|> volumesPatchApi
:<|> productsServersGetApi
:<|> productsServersAvailabilityGetApi
:<|> imagesGetApi = client scalewayApi