hdo 0.2 → 0.3
raw patch · 23 files changed
+947/−231 lines, 23 filesdep +case-insensitivedep +http-conduitdep +parsecdep −wreqdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: case-insensitive, http-conduit, parsec
Dependencies removed: wreq
Dependency ranges changed: base
API changes (from Hackage documentation)
- Network.DO.Commands: CoDO :: (m [Key], k) -> (m [Size], k) -> (m [Image], k) -> (m [Region], k) -> CoDO m k
- Network.DO.Commands: ListImages :: ([Image] -> a) -> DO a
- Network.DO.Commands: ListKeys :: ([Key] -> a) -> DO a
- Network.DO.Commands: ListRegions :: ([Region] -> a) -> DO a
- Network.DO.Commands: ListSizes :: ([Size] -> a) -> DO a
- Network.DO.Commands: [listImagesH] :: CoDO m k -> (m [Image], k)
- Network.DO.Commands: [listKeysH] :: CoDO m k -> (m [Key], k)
- Network.DO.Commands: [listRegionsH] :: CoDO m k -> (m [Region], k)
- Network.DO.Commands: [listSizesH] :: CoDO m k -> (m [Size], k)
- Network.DO.Commands: data CoDO m k
- Network.DO.Commands: data DO a
- Network.DO.Commands: instance GHC.Base.Functor (Network.DO.Commands.CoDO m)
- Network.DO.Commands: instance GHC.Base.Functor Network.DO.Commands.DO
- Network.DO.Commands: instance GHC.Base.Monad m => Network.DO.Pairing.PairingM (Network.DO.Commands.CoDO m) Network.DO.Commands.DO m
- Network.DO.Commands: listImages :: DO [Image]
- Network.DO.Commands: listKeys :: DO [Key]
- Network.DO.Commands: listRegions :: DO [Region]
- Network.DO.Commands: listSizes :: DO [Size]
- Network.DO.Commands: type CoDOT m = CofreeT (CoDO m)
- Network.DO.Commands: type DOT = FreeT DO
- Network.DO.Droplets.Commands: CoDropletCommands :: (m [Droplet], k) -> (BoxConfiguration -> (m (Result Droplet), k)) -> (Id -> (m (Maybe String), k)) -> (Id -> Action -> (m (Result ActionResult), k)) -> (Id -> Id -> (m (Result ActionResult), k)) -> (Id -> (m [Image], k)) -> (Droplet -> (m (Result ()), k)) -> (Id -> (m (Result Droplet), k)) -> CoDropletCommands m k
- Network.DO.Droplets.Commands: [actionDropletH] :: CoDropletCommands m k -> Id -> Action -> (m (Result ActionResult), k)
- Network.DO.Droplets.Commands: [consoleH] :: CoDropletCommands m k -> Droplet -> (m (Result ()), k)
- Network.DO.Droplets.Commands: [createDropletH] :: CoDropletCommands m k -> BoxConfiguration -> (m (Result Droplet), k)
- Network.DO.Droplets.Commands: [destroyDropletH] :: CoDropletCommands m k -> Id -> (m (Maybe String), k)
- Network.DO.Droplets.Commands: [getActionH] :: CoDropletCommands m k -> Id -> Id -> (m (Result ActionResult), k)
- Network.DO.Droplets.Commands: [listDropletsH] :: CoDropletCommands m k -> (m [Droplet], k)
- Network.DO.Droplets.Commands: [listSnapshotsH] :: CoDropletCommands m k -> Id -> (m [Image], k)
- Network.DO.Droplets.Commands: [showDropletH] :: CoDropletCommands m k -> Id -> (m (Result Droplet), k)
- Network.DO.Droplets.Commands: createDroplet :: BoxConfiguration -> DropletCommands (Result Droplet)
- Network.DO.Droplets.Commands: data CoDropletCommands m k
- Network.DO.Droplets.Commands: data DropletCommands a
- Network.DO.Droplets.Commands: destroyDroplet :: Id -> DropletCommands (Maybe String)
- Network.DO.Droplets.Commands: dropletAction :: Id -> Action -> DropletCommands (Result ActionResult)
- Network.DO.Droplets.Commands: dropletConsole :: Droplet -> DropletCommands (Result ())
- Network.DO.Droplets.Commands: getAction :: Id -> Id -> DropletCommands (Result ActionResult)
- Network.DO.Droplets.Commands: instance GHC.Base.Functor (Network.DO.Droplets.Commands.CoDropletCommands m)
- Network.DO.Droplets.Commands: instance GHC.Base.Functor Network.DO.Droplets.Commands.DropletCommands
- Network.DO.Droplets.Commands: instance GHC.Base.Monad m => Network.DO.Pairing.PairingM (Network.DO.Droplets.Commands.CoDropletCommands m) Network.DO.Droplets.Commands.DropletCommands m
- Network.DO.Droplets.Commands: listDropletSnapshots :: Id -> DropletCommands [Image]
- Network.DO.Droplets.Commands: listDroplets :: DropletCommands [Droplet]
- Network.DO.Droplets.Commands: showDroplet :: Id -> DropletCommands (Result Droplet)
- Network.DO.Droplets.Commands: type CoDropletCommandsT m = CofreeT (CoDropletCommands m)
- Network.DO.Droplets.Commands: type DropletCommandsT = FreeT DropletCommands
- Network.DO.Droplets.Net: dropletCommandsInterpreter :: (MonadIO m, ComonadEnv ToolConfiguration w) => w a -> CoDropletCommands (RESTT m) (w a)
- Network.DO.Droplets.Net: instance Network.DO.Net.Common.Listable Network.DO.Types.Droplet
- Network.DO.Droplets.Utils: findByIdOrName :: String -> [Droplet] -> [Droplet]
- Network.DO.Droplets.Utils: publicIP :: Droplet -> Maybe IP
- Network.DO.Names: generateName :: IO String
- Network.DO.Net: instance Network.DO.Net.Common.Listable Network.DO.Types.Image
- Network.DO.Net: instance Network.DO.Net.Common.Listable Network.DO.Types.Key
- Network.DO.Net: instance Network.DO.Net.Common.Listable Network.DO.Types.Region
- Network.DO.Net: instance Network.DO.Net.Common.Listable Network.DO.Types.Size
- Network.DO.Net: mkDOClient :: (MonadIO m) => ToolConfiguration -> CofreeT (Product (CoDO (RESTT m)) (CoDropletCommands (RESTT m))) (Env ToolConfiguration) (RESTT m ())
- Network.DO.Net.Common: (</>) :: String -> String -> String
- Network.DO.Net.Common: apiVersion :: String
- Network.DO.Net.Common: authorisation :: String -> Options
- Network.DO.Net.Common: class Listable a
- Network.DO.Net.Common: listEndpoint :: Listable a => Proxy a -> String
- Network.DO.Net.Common: listField :: Listable a => Proxy a -> Text
- Network.DO.Net.Common: queryList :: (ComonadEnv ToolConfiguration w, Monad m, Listable b, FromJSON b) => Proxy b -> w a -> (RESTT m [b], w a)
- Network.DO.Net.Common: rootURI :: String
- Network.DO.Net.Common: toList :: (FromJSON a) => Text -> Value -> [a]
- Network.DO.Net.Common: toURI :: String -> URI
- Network.DO.Pairing: class (Functor f, Functor g) => Pairing f g
- Network.DO.Pairing: class (Functor f, Functor g, Monad m) => PairingM f g m
- Network.DO.Pairing: injl :: (Monad m, Functor f, Functor g) => f a -> FreeT (Sum f g) m a
- Network.DO.Pairing: injr :: (Monad m, Functor f, Functor g) => g a -> FreeT (Sum f g) m a
- Network.DO.Pairing: instance (GHC.Base.Monad m, Network.DO.Pairing.PairingM f h m, Network.DO.Pairing.PairingM g k m) => Network.DO.Pairing.PairingM (Data.Functor.Sum.Sum f g) (Data.Functor.Product.Product h k) m
- Network.DO.Pairing: instance (GHC.Base.Monad m, Network.DO.Pairing.PairingM h f m, Network.DO.Pairing.PairingM k g m) => Network.DO.Pairing.PairingM (Data.Functor.Product.Product h k) (Data.Functor.Sum.Sum f g) m
- Network.DO.Pairing: instance GHC.Base.Monad m => Network.DO.Pairing.PairingM ((,) (m a)) ((->) a) m
- Network.DO.Pairing: instance Network.DO.Pairing.Pairing ((,) a) ((->) a)
- Network.DO.Pairing: instance Network.DO.Pairing.Pairing ((->) a) ((,) a)
- Network.DO.Pairing: instance Network.DO.Pairing.Pairing Data.Functor.Identity.Identity Data.Functor.Identity.Identity
- Network.DO.Pairing: pair :: Pairing f g => (a -> b -> r) -> f a -> g b -> r
- Network.DO.Pairing: pairEffect :: (Pairing f g, Comonad w, Monad m) => (a -> b -> r) -> CofreeT f w a -> FreeT g m b -> m r
- Network.DO.Pairing: pairEffect' :: (Pairing f g, Comonad w, Monad m) => (a -> b -> m r) -> CofreeT f w a -> FreeT g m b -> m r
- Network.DO.Pairing: pairEffectM :: (PairingM f g m, Comonad w) => (a -> b -> m r) -> CofreeT f w (m a) -> FreeT g m b -> m r
- Network.DO.Pairing: pairM :: PairingM f g m => (a -> b -> m r) -> f a -> g b -> m r
- Network.DO.Pretty: class (Show a) => Pretty a where pretty = text . show
- Network.DO.Pretty: instance (Network.DO.Pretty.Pretty a, Network.DO.Pretty.Pretty b) => Network.DO.Pretty.Pretty (Data.Either.Either a b)
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty ()
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty (Network.DO.Types.Bytes Network.DO.Types.Giga)
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty (Network.DO.Types.Bytes Network.DO.Types.Mega)
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty (Network.DO.Types.Network a)
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Data.IP.Addr.IP
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty GHC.Types.Char
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.ActionResult
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Date
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Droplet
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Error
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Image
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Key
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.NetType
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Networks
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Region
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Size
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.SizeSlug
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.Status
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty Network.DO.Types.TransferRate
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty a => Network.DO.Pretty.Pretty (GHC.Base.Maybe a)
- Network.DO.Pretty: instance Network.DO.Pretty.Pretty a => Network.DO.Pretty.Pretty [a]
- Network.DO.Pretty: outputResult :: (Pretty a, MonadIO m) => a -> m ()
- Network.DO.Pretty: pretty :: Pretty a => a -> Doc
- Network.DO.Types: A :: DNSType
- Network.DO.Types: AAAA :: DNSType
- Network.DO.Types: ActionResult :: Id -> ActionStatus -> ActionType -> Maybe Date -> Maybe Date -> Id -> String -> Region -> ActionResult
- Network.DO.Types: Active :: Status
- Network.DO.Types: Archive :: Status
- Network.DO.Types: Backup :: ImageType
- Network.DO.Types: BoxConfiguration :: String -> Region -> SizeSlug -> ImageSlug -> [KeyId] -> Bool -> BoxConfiguration
- Network.DO.Types: Bytes :: Int -> Bytes a
- Network.DO.Types: CNAME :: DNSType
- Network.DO.Types: Completed :: ActionStatus
- Network.DO.Types: CreateSnapshot :: String -> Action
- Network.DO.Types: Date :: UTCTime -> Date
- Network.DO.Types: DoPowerOff :: Action
- Network.DO.Types: DoPowerOn :: Action
- Network.DO.Types: Domain :: String -> Int -> String -> Domain
- Network.DO.Types: DomainRecord :: Id -> DNSType -> String -> String -> Maybe Double -> Maybe Int -> Maybe Double -> DomainRecord
- Network.DO.Types: Droplet :: Id -> String -> Bytes Mega -> Int -> Bytes Giga -> Bool -> Date -> Status -> [Id] -> [Id] -> Region -> SizeSlug -> Networks -> Droplet
- Network.DO.Types: Error :: String -> Error
- Network.DO.Types: Errored :: ActionStatus
- Network.DO.Types: G1 :: SizeSlug
- Network.DO.Types: G16 :: SizeSlug
- Network.DO.Types: G2 :: SizeSlug
- Network.DO.Types: G32 :: SizeSlug
- Network.DO.Types: G4 :: SizeSlug
- Network.DO.Types: G48 :: SizeSlug
- Network.DO.Types: G64 :: SizeSlug
- Network.DO.Types: G8 :: SizeSlug
- Network.DO.Types: G96 :: SizeSlug
- Network.DO.Types: Image :: Id -> String -> String -> Maybe Slug -> Bool -> [Region] -> Bytes Giga -> Date -> ImageType -> Image
- Network.DO.Types: InProgress :: ActionStatus
- Network.DO.Types: Key :: Id -> String -> String -> String -> Key
- Network.DO.Types: M512 :: SizeSlug
- Network.DO.Types: MX :: DNSType
- Network.DO.Types: MakeSnapshot :: ActionType
- Network.DO.Types: NS :: DNSType
- Network.DO.Types: NetworkV4 :: IP -> IP -> IP -> NetType -> Network a
- Network.DO.Types: NetworkV6 :: IP -> Int -> IP -> NetType -> Network a
- Network.DO.Types: Networks :: [Network V4] -> [Network V6] -> Networks
- Network.DO.Types: New :: Status
- Network.DO.Types: NoNetworks :: Networks
- Network.DO.Types: NoRegion :: Region
- Network.DO.Types: Off :: Status
- Network.DO.Types: PTR :: DNSType
- Network.DO.Types: PowerOff :: ActionType
- Network.DO.Types: PowerOn :: ActionType
- Network.DO.Types: Private :: NetType
- Network.DO.Types: Public :: NetType
- Network.DO.Types: Region :: String -> Slug -> [SizeSlug] -> Bool -> Region
- Network.DO.Types: RegionSlug :: Slug -> Region
- Network.DO.Types: SRV :: DNSType
- Network.DO.Types: Size :: SizeSlug -> Bytes Mega -> Int -> Bytes Giga -> TransferRate -> Price -> Price -> [Region] -> Bool -> Size
- Network.DO.Types: Snapshot :: ImageType
- Network.DO.Types: TXT :: DNSType
- Network.DO.Types: Temporary :: ImageType
- Network.DO.Types: Tool :: Maybe URI -> Maybe AuthToken -> Bool -> ToolConfiguration
- Network.DO.Types: [actionCompletedAt] :: ActionResult -> Maybe Date
- Network.DO.Types: [actionId] :: ActionResult -> Id
- Network.DO.Types: [actionRegionSlug] :: ActionResult -> Region
- Network.DO.Types: [actionResourceId] :: ActionResult -> Id
- Network.DO.Types: [actionResourceType] :: ActionResult -> String
- Network.DO.Types: [actionStartedAt] :: ActionResult -> Maybe Date
- Network.DO.Types: [actionStatus] :: ActionResult -> ActionStatus
- Network.DO.Types: [actionType] :: ActionResult -> ActionType
- Network.DO.Types: [authToken] :: ToolConfiguration -> Maybe AuthToken
- Network.DO.Types: [backgroundCreate] :: BoxConfiguration -> Bool
- Network.DO.Types: [backup_ids] :: Droplet -> [Id]
- Network.DO.Types: [boxRegion] :: BoxConfiguration -> Region
- Network.DO.Types: [bytesSize] :: Bytes a -> Int
- Network.DO.Types: [configImageSlug] :: BoxConfiguration -> ImageSlug
- Network.DO.Types: [configName] :: BoxConfiguration -> String
- Network.DO.Types: [created_at] :: Droplet -> Date
- Network.DO.Types: [disk] :: Droplet -> Bytes Giga
- Network.DO.Types: [distribution] :: Image -> String
- Network.DO.Types: [domainName] :: Domain -> String
- Network.DO.Types: [domainTTL] :: Domain -> Int
- Network.DO.Types: [dropletId] :: Droplet -> Id
- Network.DO.Types: [gateway] :: Network a -> IP
- Network.DO.Types: [imageId] :: Image -> Id
- Network.DO.Types: [imageName] :: Image -> String
- Network.DO.Types: [imageRegions] :: Image -> [Region]
- Network.DO.Types: [imageSlug] :: Image -> Maybe Slug
- Network.DO.Types: [imageType] :: Image -> ImageType
- Network.DO.Types: [image_created_at] :: Image -> Date
- Network.DO.Types: [ip_address] :: Network a -> IP
- Network.DO.Types: [keyFingerprint] :: Key -> String
- Network.DO.Types: [keyId] :: Key -> Id
- Network.DO.Types: [keyName] :: Key -> String
- Network.DO.Types: [keys] :: BoxConfiguration -> [KeyId]
- Network.DO.Types: [locked] :: Droplet -> Bool
- Network.DO.Types: [memory] :: Droplet -> Bytes Mega
- Network.DO.Types: [min_disk_size] :: Image -> Bytes Giga
- Network.DO.Types: [msg] :: Error -> String
- Network.DO.Types: [name] :: Droplet -> String
- Network.DO.Types: [netType] :: Network a -> NetType
- Network.DO.Types: [netmask] :: Network a -> IP
- Network.DO.Types: [netmask_v6] :: Network a -> Int
- Network.DO.Types: [networks] :: Droplet -> Networks
- Network.DO.Types: [publicImage] :: Image -> Bool
- Network.DO.Types: [publicKey] :: Key -> String
- Network.DO.Types: [quiet] :: ToolConfiguration -> Bool
- Network.DO.Types: [recordData] :: DomainRecord -> String
- Network.DO.Types: [recordId] :: DomainRecord -> Id
- Network.DO.Types: [recordName] :: DomainRecord -> String
- Network.DO.Types: [recordPort] :: DomainRecord -> Maybe Int
- Network.DO.Types: [recordPriority] :: DomainRecord -> Maybe Double
- Network.DO.Types: [recordType] :: DomainRecord -> DNSType
- Network.DO.Types: [recordWeight] :: DomainRecord -> Maybe Double
- Network.DO.Types: [regionAvailable] :: Region -> Bool
- Network.DO.Types: [regionName] :: Region -> String
- Network.DO.Types: [regionSizes] :: Region -> [SizeSlug]
- Network.DO.Types: [regionSlug] :: Region -> Slug
- Network.DO.Types: [region] :: Droplet -> Region
- Network.DO.Types: [size] :: BoxConfiguration -> SizeSlug
- Network.DO.Types: [size_slug] :: Droplet -> SizeSlug
- Network.DO.Types: [slackUri] :: ToolConfiguration -> Maybe URI
- Network.DO.Types: [snapshot_ids] :: Droplet -> [Id]
- Network.DO.Types: [status] :: Droplet -> Status
- Network.DO.Types: [szAvailable] :: Size -> Bool
- Network.DO.Types: [szDisk] :: Size -> Bytes Giga
- Network.DO.Types: [szMemory] :: Size -> Bytes Mega
- Network.DO.Types: [szPrice_Hourly] :: Size -> Price
- Network.DO.Types: [szPrice_Monthly] :: Size -> Price
- Network.DO.Types: [szRegions] :: Size -> [Region]
- Network.DO.Types: [szSlug] :: Size -> SizeSlug
- Network.DO.Types: [szTransfer] :: Size -> TransferRate
- Network.DO.Types: [szVcpus] :: Size -> Int
- Network.DO.Types: [theDate] :: Date -> UTCTime
- Network.DO.Types: [v4] :: Networks -> [Network V4]
- Network.DO.Types: [v6] :: Networks -> [Network V6]
- Network.DO.Types: [vcpus] :: Droplet -> Int
- Network.DO.Types: [zone_file] :: Domain -> String
- Network.DO.Types: data Action
- Network.DO.Types: data ActionResult
- Network.DO.Types: data ActionStatus
- Network.DO.Types: data ActionType
- Network.DO.Types: data BoxConfiguration
- Network.DO.Types: data DNSType
- Network.DO.Types: data Domain
- Network.DO.Types: data DomainRecord
- Network.DO.Types: data Droplet
- Network.DO.Types: data Giga
- Network.DO.Types: data Image
- Network.DO.Types: data ImageType
- Network.DO.Types: data Key
- Network.DO.Types: data Mega
- Network.DO.Types: data NetType
- Network.DO.Types: data Network a
- Network.DO.Types: data Networks
- Network.DO.Types: data Region
- Network.DO.Types: data Size
- Network.DO.Types: data SizeSlug
- Network.DO.Types: data Status
- Network.DO.Types: data ToolConfiguration
- Network.DO.Types: data V4
- Network.DO.Types: data V6
- Network.DO.Types: defaultImage :: ImageSlug
- Network.DO.Types: error :: String -> Result a
- Network.DO.Types: failParse :: (Show a1, Monad m) => a1 -> m a
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON (Network.DO.Types.Bytes Network.DO.Types.Giga)
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON (Network.DO.Types.Bytes Network.DO.Types.Mega)
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON (Network.DO.Types.Network Network.DO.Types.V4)
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON (Network.DO.Types.Network Network.DO.Types.V6)
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Data.IP.Addr.IP
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.ActionResult
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.ActionStatus
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.ActionType
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.DNSType
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Date
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Domain
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.DomainRecord
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Droplet
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Image
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.ImageType
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Key
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.NetType
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Networks
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Region
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Size
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.SizeSlug
- Network.DO.Types: instance Data.Aeson.Types.FromJSON.FromJSON Network.DO.Types.Status
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.Action
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.ActionType
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.BoxConfiguration
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.DNSType
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.Region
- Network.DO.Types: instance Data.Aeson.Types.ToJSON.ToJSON Network.DO.Types.SizeSlug
- Network.DO.Types: instance Data.Default.Class.Default Network.DO.Types.ToolConfiguration
- Network.DO.Types: instance GHC.Classes.Eq Network.DO.Types.Error
- Network.DO.Types: instance GHC.Classes.Eq Network.DO.Types.NetType
- Network.DO.Types: instance GHC.Classes.Eq Network.DO.Types.SizeSlug
- Network.DO.Types: instance GHC.Classes.Eq Network.DO.Types.Status
- Network.DO.Types: instance GHC.Classes.Ord Network.DO.Types.SizeSlug
- Network.DO.Types: instance GHC.Enum.Enum Network.DO.Types.SizeSlug
- Network.DO.Types: instance GHC.Generics.Generic Network.DO.Types.DNSType
- Network.DO.Types: instance GHC.Generics.Generic Network.DO.Types.Networks
- Network.DO.Types: instance GHC.Read.Read Network.DO.Types.DNSType
- Network.DO.Types: instance GHC.Read.Read Network.DO.Types.Error
- Network.DO.Types: instance GHC.Read.Read Network.DO.Types.SizeSlug
- Network.DO.Types: instance GHC.Read.Read Network.DO.Types.ToolConfiguration
- Network.DO.Types: instance GHC.Show.Show (Network.DO.Types.Bytes a)
- Network.DO.Types: instance GHC.Show.Show (Network.DO.Types.Network a)
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Action
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.ActionResult
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.ActionStatus
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.ActionType
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.BoxConfiguration
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.DNSType
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Date
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Domain
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.DomainRecord
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Droplet
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Error
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Image
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.ImageType
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Key
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.NetType
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Networks
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Region
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Size
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.SizeSlug
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.Status
- Network.DO.Types: instance GHC.Show.Show Network.DO.Types.ToolConfiguration
- Network.DO.Types: jsonBytes :: Int -> Parser (Bytes a)
- Network.DO.Types: jsonNetwork :: (FromJSON a3, FromJSON a2, FromJSON a1, FromJSON a) => (a3 -> a2 -> a1 -> a -> b) -> Object -> Parser b
- Network.DO.Types: newtype Bytes a
- Network.DO.Types: newtype Date
- Network.DO.Types: newtype Error
- Network.DO.Types: sizeSlugs :: [String]
- Network.DO.Types: type AuthToken = String
- Network.DO.Types: type Id = Integer
- Network.DO.Types: type ImageSlug = String
- Network.DO.Types: type KeyId = Int
- Network.DO.Types: type Price = Double
- Network.DO.Types: type Result a = Either Error a
- Network.DO.Types: type Slug = String
- Network.DO.Types: type TransferRate = Double
- Network.DO.Types: type URI = String
- Network.REST: DeleteWith :: Options -> URI -> a -> REST a
- Network.REST: Get :: URI -> (Value -> a) -> REST a
- Network.REST: GetWith :: Options -> URI -> (Value -> a) -> REST a
- Network.REST: Post :: URI -> Value -> (Maybe Value -> a) -> REST a
- Network.REST: PostWith :: Options -> URI -> Value -> (Either String Value -> a) -> REST a
- Network.REST: WaitFor :: Int -> String -> a -> REST a
- Network.REST: data REST a
- Network.REST: deleteJSONWith :: (Monad m) => Options -> URI -> RESTT m ()
- Network.REST: getJSON :: (Monad m) => URI -> RESTT m Value
- Network.REST: getJSONWith :: (Monad m) => Options -> URI -> RESTT m Value
- Network.REST: postJSON :: (Monad m) => URI -> Value -> RESTT m (Maybe Value)
- Network.REST: postJSONWith :: (Monad m) => Options -> URI -> Value -> RESTT m (Either String Value)
- Network.REST: runWreq :: RESTT IO r -> IO r
- Network.REST: ssh :: (MonadIO m, Exception e) => [String] -> m (Either e ())
- Network.REST: type RESTT = FreeT REST
- Network.REST: waitFor :: (Monad m) => Int -> String -> RESTT m ()
+ Network.DO: A :: DNSType
+ Network.DO: AAAA :: DNSType
+ Network.DO: ActionResult :: Id -> ActionStatus -> result -> Maybe Date -> Maybe Date -> Id -> String -> Region -> ActionResult result
+ Network.DO: Active :: Status
+ Network.DO: Archive :: Status
+ Network.DO: Assign :: IPActionType
+ Network.DO: AssignIP :: Id -> IPAction
+ Network.DO: Backup :: ImageType
+ Network.DO: BoxConfiguration :: String -> Region -> SizeSlug -> ImageSlug -> [KeyId] -> Bool -> BoxConfiguration
+ Network.DO: Bytes :: Int -> Bytes a
+ Network.DO: CNAME :: DNSType
+ Network.DO: Completed :: ActionStatus
+ Network.DO: CreateSnapshot :: String -> Action
+ Network.DO: Date :: UTCTime -> Date
+ Network.DO: DoPowerOff :: Action
+ Network.DO: DoPowerOn :: Action
+ Network.DO: Domain :: DomainName -> Maybe Int -> Maybe String -> Domain
+ Network.DO: DomainConfig :: DomainName -> IP -> DomainConfig
+ Network.DO: DomainName :: String -> DomainName
+ Network.DO: DomainRecord :: Id -> DNSType -> String -> String -> Maybe Int -> Maybe Int -> Maybe Int -> DomainRecord
+ Network.DO: Droplet :: Id -> String -> Bytes Mega -> Int -> Bytes Giga -> Bool -> Date -> Status -> [Id] -> [Id] -> Region -> SizeSlug -> Networks -> Droplet
+ Network.DO: Error :: String -> Error
+ Network.DO: Errored :: ActionStatus
+ Network.DO: FloatingIP :: IP -> Maybe Droplet -> Region -> FloatingIP
+ Network.DO: G1 :: SizeSlug
+ Network.DO: G16 :: SizeSlug
+ Network.DO: G2 :: SizeSlug
+ Network.DO: G32 :: SizeSlug
+ Network.DO: G4 :: SizeSlug
+ Network.DO: G48 :: SizeSlug
+ Network.DO: G64 :: SizeSlug
+ Network.DO: G8 :: SizeSlug
+ Network.DO: G96 :: SizeSlug
+ Network.DO: Image :: Id -> String -> String -> Maybe Slug -> Bool -> [Region] -> Bytes Giga -> Date -> ImageType -> Image
+ Network.DO: InProgress :: ActionStatus
+ Network.DO: Key :: Id -> String -> String -> String -> Key
+ Network.DO: M512 :: SizeSlug
+ Network.DO: MX :: DNSType
+ Network.DO: MakeSnapshot :: DropletActionType
+ Network.DO: NS :: DNSType
+ Network.DO: NetworkV4 :: IP -> IP -> IP -> NetType -> Network a
+ Network.DO: NetworkV6 :: IP -> Int -> IP -> NetType -> Network a
+ Network.DO: Networks :: [Network V4] -> [Network V6] -> Networks
+ Network.DO: New :: Status
+ Network.DO: NoNetworks :: Networks
+ Network.DO: NoRegion :: Region
+ Network.DO: Off :: Status
+ Network.DO: PTR :: DNSType
+ Network.DO: PowerOff :: DropletActionType
+ Network.DO: PowerOn :: DropletActionType
+ Network.DO: Private :: NetType
+ Network.DO: Public :: NetType
+ Network.DO: Region :: String -> Slug -> [SizeSlug] -> Bool -> Region
+ Network.DO: RegionSlug :: Slug -> Region
+ Network.DO: SRV :: DNSType
+ Network.DO: Size :: SizeSlug -> Bytes Mega -> Int -> Bytes Giga -> TransferRate -> Price -> Price -> [Region] -> Bool -> Size
+ Network.DO: Snapshot :: ImageType
+ Network.DO: TXT :: DNSType
+ Network.DO: TargetDroplet :: Id -> FloatingIPTarget
+ Network.DO: TargetRegion :: Slug -> FloatingIPTarget
+ Network.DO: Temporary :: ImageType
+ Network.DO: Tool :: Maybe URI -> Maybe AuthToken -> Bool -> ToolConfiguration
+ Network.DO: Unassign :: IPActionType
+ Network.DO: UnassignIP :: IPAction
+ Network.DO: [actionCompletedAt] :: ActionResult result -> Maybe Date
+ Network.DO: [actionId] :: ActionResult result -> Id
+ Network.DO: [actionRegionSlug] :: ActionResult result -> Region
+ Network.DO: [actionResourceId] :: ActionResult result -> Id
+ Network.DO: [actionResourceType] :: ActionResult result -> String
+ Network.DO: [actionStartedAt] :: ActionResult result -> Maybe Date
+ Network.DO: [actionStatus] :: ActionResult result -> ActionStatus
+ Network.DO: [actionType] :: ActionResult result -> result
+ Network.DO: [authToken] :: ToolConfiguration -> Maybe AuthToken
+ Network.DO: [backgroundCreate] :: BoxConfiguration -> Bool
+ Network.DO: [backup_ids] :: Droplet -> [Id]
+ Network.DO: [boxRegion] :: BoxConfiguration -> Region
+ Network.DO: [bytesSize] :: Bytes a -> Int
+ Network.DO: [configImageSlug] :: BoxConfiguration -> ImageSlug
+ Network.DO: [configName] :: BoxConfiguration -> String
+ Network.DO: [created_at] :: Droplet -> Date
+ Network.DO: [disk] :: Droplet -> Bytes Giga
+ Network.DO: [distribution] :: Image -> String
+ Network.DO: [domainName] :: Domain -> DomainName
+ Network.DO: [domainTTL] :: Domain -> Maybe Int
+ Network.DO: [domain] :: DomainName -> String
+ Network.DO: [dropletId] :: Droplet -> Id
+ Network.DO: [floatingDroplet] :: FloatingIP -> Maybe Droplet
+ Network.DO: [floatingIp] :: FloatingIP -> IP
+ Network.DO: [floatingRegion] :: FloatingIP -> Region
+ Network.DO: [gateway] :: Network a -> IP
+ Network.DO: [imageId] :: Image -> Id
+ Network.DO: [imageName] :: Image -> String
+ Network.DO: [imageRegions] :: Image -> [Region]
+ Network.DO: [imageSlug] :: Image -> Maybe Slug
+ Network.DO: [imageType] :: Image -> ImageType
+ Network.DO: [image_created_at] :: Image -> Date
+ Network.DO: [ip_address] :: Network a -> IP
+ Network.DO: [keyFingerprint] :: Key -> String
+ Network.DO: [keyId] :: Key -> Id
+ Network.DO: [keyName] :: Key -> String
+ Network.DO: [keys] :: BoxConfiguration -> [KeyId]
+ Network.DO: [locked] :: Droplet -> Bool
+ Network.DO: [memory] :: Droplet -> Bytes Mega
+ Network.DO: [min_disk_size] :: Image -> Bytes Giga
+ Network.DO: [msg] :: Error -> String
+ Network.DO: [name] :: Droplet -> String
+ Network.DO: [netType] :: Network a -> NetType
+ Network.DO: [netmask] :: Network a -> IP
+ Network.DO: [netmask_v6] :: Network a -> Int
+ Network.DO: [networks] :: Droplet -> Networks
+ Network.DO: [publicImage] :: Image -> Bool
+ Network.DO: [publicKey] :: Key -> String
+ Network.DO: [quiet] :: ToolConfiguration -> Bool
+ Network.DO: [recordData] :: DomainRecord -> String
+ Network.DO: [recordId] :: DomainRecord -> Id
+ Network.DO: [recordName] :: DomainRecord -> String
+ Network.DO: [recordPort] :: DomainRecord -> Maybe Int
+ Network.DO: [recordPriority] :: DomainRecord -> Maybe Int
+ Network.DO: [recordType] :: DomainRecord -> DNSType
+ Network.DO: [recordWeight] :: DomainRecord -> Maybe Int
+ Network.DO: [regionAvailable] :: Region -> Bool
+ Network.DO: [regionName] :: Region -> String
+ Network.DO: [regionSizes] :: Region -> [SizeSlug]
+ Network.DO: [regionSlug] :: Region -> Slug
+ Network.DO: [region] :: Droplet -> Region
+ Network.DO: [size] :: BoxConfiguration -> SizeSlug
+ Network.DO: [size_slug] :: Droplet -> SizeSlug
+ Network.DO: [slackUri] :: ToolConfiguration -> Maybe URI
+ Network.DO: [snapshot_ids] :: Droplet -> [Id]
+ Network.DO: [status] :: Droplet -> Status
+ Network.DO: [szAvailable] :: Size -> Bool
+ Network.DO: [szDisk] :: Size -> Bytes Giga
+ Network.DO: [szMemory] :: Size -> Bytes Mega
+ Network.DO: [szPrice_Hourly] :: Size -> Price
+ Network.DO: [szPrice_Monthly] :: Size -> Price
+ Network.DO: [szRegions] :: Size -> [Region]
+ Network.DO: [szSlug] :: Size -> SizeSlug
+ Network.DO: [szTransfer] :: Size -> TransferRate
+ Network.DO: [szVcpus] :: Size -> Int
+ Network.DO: [theDate] :: Date -> UTCTime
+ Network.DO: [v4] :: Networks -> [Network V4]
+ Network.DO: [v6] :: Networks -> [Network V6]
+ Network.DO: [vcpus] :: Droplet -> Int
+ Network.DO: [zone_file] :: Domain -> Maybe String
+ Network.DO: assignFloatingIP :: (Monad w) => IP -> Id -> Command w (Result (ActionResult IPActionType))
+ Network.DO: createDomain :: (Monad w) => DomainName -> IP -> Command w (Result Domain)
+ Network.DO: createDroplet :: (Monad w) => BoxConfiguration -> Command w (Either Error Droplet)
+ Network.DO: createFloatingIP :: (Monad w) => FloatingIPTarget -> Command w (Result FloatingIP)
+ Network.DO: createRecord :: (Monad w) => DomainName -> DomainRecord -> Command w (Result DomainRecord)
+ Network.DO: data Action
+ Network.DO: data ActionResult result
+ Network.DO: data ActionStatus
+ Network.DO: data BoxConfiguration
+ Network.DO: data DNSType
+ Network.DO: data Domain
+ Network.DO: data DomainConfig
+ Network.DO: data DomainRecord
+ Network.DO: data Droplet
+ Network.DO: data DropletActionType
+ Network.DO: data FloatingIP
+ Network.DO: data FloatingIPTarget
+ Network.DO: data Giga
+ Network.DO: data IPAction
+ Network.DO: data IPActionType
+ Network.DO: data Image
+ Network.DO: data ImageType
+ Network.DO: data Key
+ Network.DO: data Mega
+ Network.DO: data NetType
+ Network.DO: data Network a
+ Network.DO: data Networks
+ Network.DO: data Region
+ Network.DO: data Size
+ Network.DO: data SizeSlug
+ Network.DO: data Status
+ Network.DO: data ToolConfiguration
+ Network.DO: data V4
+ Network.DO: data V6
+ Network.DO: defaultImage :: ImageSlug
+ Network.DO: deleteDomain :: (Monad w) => DomainName -> Command w (Maybe String)
+ Network.DO: deleteFloatingIP :: (Monad w) => IP -> Command w (Maybe String)
+ Network.DO: deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Maybe String)
+ Network.DO: destroyDroplet :: (Monad w) => Integer -> Command w (Maybe String)
+ Network.DO: dropletAction :: (Monad w) => Id -> Action -> Command w (Result (ActionResult DropletActionType))
+ Network.DO: dropletConsole :: (Monad w) => Droplet -> Command w (Result ())
+ Network.DO: error :: String -> Result a
+ Network.DO: failParse :: (Show a1, Monad m) => a1 -> m a
+ Network.DO: findByIdOrName :: String -> [Droplet] -> [Droplet]
+ Network.DO: generateName :: IO String
+ Network.DO: getAction :: (Monad w) => Id -> Id -> Command w (Result (ActionResult DropletActionType))
+ Network.DO: getAuthFromEnv :: IO (Maybe AuthToken)
+ Network.DO: jsonBytes :: Int -> Parser (Bytes a)
+ Network.DO: jsonNetwork :: (FromJSON a3, FromJSON a2, FromJSON a1, FromJSON a) => (a3 -> a2 -> a1 -> a -> b) -> Object -> Parser b
+ Network.DO: listDomains :: (Monad w) => Command w [Domain]
+ Network.DO: listDropletSnapshots :: (Monad w) => Id -> Command w [Image]
+ Network.DO: listDroplets :: (Monad w) => Command w [Droplet]
+ Network.DO: listFloatingIPs :: (Monad w) => Command w [FloatingIP]
+ Network.DO: listImages :: (Monad w) => Command w [Image]
+ Network.DO: listKeys :: (Monad w) => Command w [Key]
+ Network.DO: listRecords :: (Monad w) => DomainName -> Command w [DomainRecord]
+ Network.DO: listRegions :: (Monad w) => Command w [Region]
+ Network.DO: listSizes :: (Monad w) => Command w [Size]
+ Network.DO: newtype Bytes a
+ Network.DO: newtype Date
+ Network.DO: newtype DomainName
+ Network.DO: newtype Error
+ Network.DO: outputResult :: (Pretty a, MonadIO m) => a -> m ()
+ Network.DO: parseRecord :: String -> Result DomainRecord
+ Network.DO: publicIP :: Droplet -> Maybe IP
+ Network.DO: runDO :: Command IO a -> Maybe AuthToken -> IO a
+ Network.DO: runDOEnv :: Command IO a -> IO a
+ Network.DO: showDroplet :: (Monad w) => Integer -> Command w (Either Error Droplet)
+ Network.DO: sizeSlugs :: [String]
+ Network.DO: type AuthToken = String
+ Network.DO: type Command w a = FreeT (DO :+: (DropletCommands :+: (IPCommands :+: DomainCommands))) (RESTT w) a
+ Network.DO: type Id = Integer
+ Network.DO: type ImageSlug = String
+ Network.DO: type KeyId = Int
+ Network.DO: type Price = Double
+ Network.DO: type Result a = Either Error a
+ Network.DO: type Slug = String
+ Network.DO: type TransferRate = Double
+ Network.DO: type URI = String
+ Network.DO: unassignFloatingIP :: (Monad w) => IP -> Command w (Result (ActionResult IPActionType))
Files
- README.md +167/−27
- hdo.cabal +10/−5
- main/hdo.hs +45/−44
- src/Network/DO.hs +119/−0
- src/Network/DO/Commands.hs +7/−7
- src/Network/DO/Domain.hs +4/−0
- src/Network/DO/Domain/Commands.hs +62/−0
- src/Network/DO/Domain/Net.hs +83/−0
- src/Network/DO/Droplets/Commands.hs +6/−6
- src/Network/DO/Droplets/Net.hs +10/−26
- src/Network/DO/IP.hs +9/−0
- src/Network/DO/IP/Commands.hs +52/−0
- src/Network/DO/IP/Net.hs +62/−0
- src/Network/DO/Net.hs +13/−2
- src/Network/DO/Net/Common.hs +8/−13
- src/Network/DO/Pairing.hs +40/−11
- src/Network/DO/Pretty.hs +28/−2
- src/Network/DO/Types.hs +145/−23
- src/Network/REST.hs +4/−6
- src/Network/REST/Commands.hs +9/−4
- src/Network/REST/Conduit.hs +63/−0
- src/Network/REST/Wreq.hs +0/−54
- stack.yaml +1/−1
README.md view
@@ -2,40 +2,180 @@ [](https://travis-ci.org/capital-match/hdo) -**WARNING** This implementation still covers only a small subset of DO API- This is a [Digital Ocean](https://www.digitalocean.com/) client written in [Haskell](http://haskell.org). It can be used either as a library or as command-line utility. +# Build -## Compile-`stack setup`-`stack build`+*hdo* is built with [stack](https://github.com/commercialhaskell/stack/), using [Stackage LTS-7.7](https://www.stackage.org/lts-7.7) dependencies. To build, follow the standard instructions: -## Usage-First export the AUTH_TOKEN:-`export AUTH_TOKEN=2342342341234eaf`+Install the correct GHC version: -List existing droplets:-`stack exec docean -- droplets list`+> stack setup -List existing regions:-`stack exec docean -- regions list`+Build and install the package locally, possibly making executable `docean` available from `PATH`: -## Implemented commands:+> stack install++# Usage++## Command-line Tool ++Command-line tool is named `docean`. If it's been installed, it should be available in your PATH. Otherwise, you can run it from the source directory using `stack exec docean -- <args>`. `docean` expects authentication token to be available from environment variable `AUTH_TOKEN`, see [Digital Ocean API](https://developers.digitalocean.com/documentation/v2/#authentication) for details on how to get that authentication token.++> export AUTH_TOKEN=2342342341234eaf++## Available Commands++**NOTE**: Not all the available APIs from DO are implemented (yet).++### Droplets++#### Creating a Droplet++This is the most complex command, as creating a droplet can use a lot of different options.+ ```-droplets create-droplets destroy <dropletId>-droplets list-droplets power_off <dropletId>-droplets power_on <dropletId>-droplets snapshot <dropletId> <snapshotName>-droplets action <dropletId> <actionId>-droplets <dropletId> snapshots-droplets <dropletId>-droplets ssh <dropletId or Name>-images list-regions list-keys list-sizes list+$ docean droplets create -r ams2 -s 1gb -i ubuntu-16-04-x64 -n deploy-test -k '[2436510]'+waiting for droplet deploy-test to become Active: 60s+waiting for droplet deploy-test to become Active: 59s+waiting for droplet deploy-test to become Active: 58s+waiting for droplet deploy-test to become Active: 57s+waiting for droplet deploy-test to become Active: 56s+waiting for droplet deploy-test to become Active: 55s+waiting for droplet deploy-test to become Active: 54s+waiting for droplet deploy-test to become Active: 53s+waiting for droplet deploy-test to become Active: 52s+30876135+ deploy-test [Active] ams2: Amsterdam 2+ 1024M/30G/1 cores+ IPv4+ 95.85.44.239/255.255.255.0 [Public]+ IPv6 ```++The available parameters are:++* `-r|--region`: Sets the region where droplet is created, using the *region slug*+* `-s|--size`: Sets the size of the droplet, from `512mb` to `96gb`+* `-n|--name`: Name of the newly created droplet+* `-i|--image`: Slug of the image to use as base image for droplet+* `-k|--keys`: List (in the form readable by Haskell to convert to type `[Int]`) of keys to add for accessing the droplet+* `-b|--background`: Create droplet in the background, returning only the identifier of action to be checked later. If not set, creation is *synchronous*: The client will check wait for droplet to be up for 60s.++When successfully created, the main characteristics of the droplet are printed to the console.++#### Other Droplet Operations++Destroy an existing droplet:++```+$ droplets destroy 30876135+-+```++List all created droplets:++```+$ docean droplets list+21002433+ droplet-01 [Active] sgp1: Singapore 1+ 1024M/30G/1 cores+ IPv4+ 1.2.3.4/255.255.240.0 [Public]+ IPv6+26082279+ droplet-02 [Active] sgp1: Singapore 1+ 512M/20G/1 cores+ IPv4+ 1.2.3.5/255.255.240.0 [Public]+ IPv6+31114780+ droplet-03 [Active] ams2: Amsterdam 2+ 2048M/40G/2 cores+ IPv4+ 5.6.7.8/255.255.240.0 [Public]+ IPv6+```++Shutdown/startup a droplet:++```+$ docean droplets power_off 31166171+[163908062] 2016-11-01 21:08:46 UTC -> -+ 31166171 PowerOff: InProgress+```++```+$ docean droplets power_on 31166171+[163908350] 2016-11-01 21:10:16 UTC -> -+ 31166171 PowerOn: InProgress+```++Retrieve status of an action initiated on the droplet (usually related to power on/off or snapshots):++```+$ docean droplets action 31166171 163908062+[163908062] 2016-11-01 21:08:46 UTC -> 2016-11-01 21:08:58 UTC+ 31166171 PowerOff: Completed+```++Take a snapshot of an existing droplet (must be powered off):++```+$ docean droplets snapshot 31166171 mysnapshot+[163908530] 2016-11-01 21:11:19 UTC -> -+ 31166171 MakeSnapshot: InProgress+```++List existing snapshots for droplet:++```+$ docean droplets 31166171 snapshots+20648640 mysnapshot Ubuntu+```++Connect to the droplet through SSH (can be given a name or a droplet ID):++```+$ docean droplets ssh 31166171+The authenticity of host '1.2.3.4 (1.2.3.4)' can't be established.+RSA key fingerprint is 3f:16:4f:f4:25:85:85:77:1e:90:bd:8c:6b:90:48:eb.+Are you sure you want to continue connecting (yes/no)? yes+Warning: Permanently added '1.2.3.4' (RSA) to the list of known hosts.+Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-45-generic x86_64)++ * Documentation: https://help.ubuntu.com+ * Management: https://landscape.canonical.com+ * Support: https://ubuntu.com/advantage++0 packages can be updated.+0 updates are security updates.++++The programs included with the Ubuntu system are free software;+the exact distribution terms for each program are described in the+individual files in /usr/share/doc/*/copyright.++Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by+applicable law.++root@droplet:~# +```++### Images++### Regions++### Keys++### Sizes++### Floating IPs++### Domains++## Library++TBD
hdo.cabal view
@@ -1,5 +1,5 @@ Name: hdo-Version: 0.2+Version: 0.3 Cabal-Version: >= 1.18 Maintainer: Arnaud Bailly <arnaud.oqube@gmail.com> Author: Arnaud Bailly@@ -20,24 +20,30 @@ Default-Language: Haskell2010 GHC-Options: -Wall -fno-warn-orphans Hs-Source-Dirs: src- Other-Modules: Network.REST.Commands, Network.REST.Wreq, Network.SSH- exposed-modules: Network.DO.Commands- , Network.DO.Types+ Other-Modules: Network.REST.Commands, Network.REST.Conduit, Network.SSH, Network.DO.Types , Network.DO.Net, Network.DO.Net.Common+ , Network.DO.Commands , Network.DO.Droplets.Commands, Network.DO.Droplets.Net, Network.DO.Droplets.Utils+ , Network.DO.IP, Network.DO.IP.Commands, Network.DO.IP.Net+ , Network.DO.Domain, Network.DO.Domain.Commands, Network.DO.Domain.Net , Network.DO.Names, Network.DO.Pairing , Network.REST, Network.DO.Pretty+ + exposed-modules: Network.DO Build-Depends: aeson , base >= 4 && < 5 , bytestring+ , case-insensitive , comonad , data-default , free+ , http-conduit , iproute , lens , pretty , mtl , network-uri+ , parsec , random , text , tagged@@ -46,7 +52,6 @@ , unix, process , unordered-containers , vector- , wreq Executable docean Default-Language: Haskell2010
main/hdo.hs view
@@ -1,30 +1,16 @@-{-# LANGUAGE DoAndIfThenElse #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DoAndIfThenElse, MultiParamTypeClasses, OverloadedStrings, RecordWildCards, ScopedTypeVariables #-} module Main where -import Control.Exception (catch, throw)-import Control.Monad.IO.Class (MonadIO (..))-import Control.Monad.Trans.Free (FreeT)-import Data.Functor.Sum+import Control.Exception (catch, throw)+import Control.Monad.IO.Class (MonadIO (..)) import Data.Maybe-import Data.Monoid ((<>))-import Prelude as P hiding (error)+import Data.Monoid ((<>))+import Network.DO+import Prelude as P hiding (error) import System.Console.GetOpt import System.Environment import System.IO-import System.IO.Error (isDoesNotExistError)-import Network.DO.Commands-import Network.DO.Droplets.Commands-import Network.DO.Droplets.Utils-import Network.DO.Names-import Network.DO.Net-import Network.DO.Pairing-import Network.DO.Pretty (outputResult)-import Network.DO.Types as DO-import Network.REST+import System.IO.Error (isDoesNotExistError) generalOptions :: [OptDescr (ToolConfiguration -> ToolConfiguration)] generalOptions = [ Option ['t'] ["auth-token"]@@ -56,16 +42,13 @@ "add a key to access box (default: '[]')" ] -getAuthFromEnv :: IO (Maybe AuthToken)-getAuthFromEnv = (Just `fmap` getEnv "AUTH_TOKEN") `catch` (\ (e :: IOError) -> if isDoesNotExistError e then return Nothing else throw e)- getSlackUriFromEnv :: IO (Maybe URI) getSlackUriFromEnv = (Just `fmap` getEnv "SLACK_URI") `catch` (\ (e :: IOError) -> if isDoesNotExistError e then return Nothing else throw e) defaultBox :: IO BoxConfiguration defaultBox = do name <- generateName- return $ BoxConfiguration name (RegionSlug "ams2") G4 defaultImage [429079] False+ return $ BoxConfiguration name (RegionSlug "ams2") G4 defaultImage [] False defaultTool :: IO ToolConfiguration defaultTool = do@@ -89,38 +72,56 @@ main = do hSetBuffering stdin NoBuffering args <- getArgs- (opts, cmds) <- parseOptions args- runWreq $ pairEffectM (\ _ b -> return b) (mkDOClient opts) (parseCommandOptions cmds)+ (_, cmds) <- parseOptions args+ runDOEnv (parseCommandOptions cmds) -parseCommandOptions :: (MonadIO m) => [String] -> FreeT (Sum DO DropletCommands) (RESTT m) ()+parseCommandOptions :: (MonadIO m) => [String] -> Command m () parseCommandOptions ("droplets":"create":args) = do b <- liftIO defaultBox case getOpt Permute createDropletOptions args of- (c,[],[]) -> injr (createDroplet (foldl (flip P.id) b c)) >>= outputResult+ (c,[],[]) -> createDroplet (foldl (flip P.id) b c) >>= outputResult (_,_,errs) -> liftIO $ ioError (userError (concat errs ++ usage))-parseCommandOptions ("droplets":"destroy":dropletId:[]) = injr ( destroyDroplet (P.read dropletId) ) >>= outputResult-parseCommandOptions ("droplets":"list":_) = injr ( listDroplets ) >>= outputResult+parseCommandOptions ("droplets":"destroy":dropletId:[]) = destroyDroplet (P.read dropletId) >>= outputResult+parseCommandOptions ("droplets":"list":_) = listDroplets >>= outputResult parseCommandOptions ("droplets":"power_off":dropletId:[])- = injr ( dropletAction (P.read dropletId) DoPowerOff) >>= outputResult+ = dropletAction (P.read dropletId) DoPowerOff >>= outputResult parseCommandOptions ("droplets":"power_on":dropletId:[])- = injr ( dropletAction (P.read dropletId) DoPowerOn) >>= outputResult+ = dropletAction (P.read dropletId) DoPowerOn >>= outputResult parseCommandOptions ("droplets":"snapshot":dropletId:snapshotName:[])- = injr ( dropletAction (P.read dropletId) (CreateSnapshot snapshotName)) >>= outputResult+ = dropletAction (P.read dropletId) (CreateSnapshot snapshotName) >>= outputResult parseCommandOptions ("droplets":"action":dropletId:actionId:[])- = injr ( getAction (P.read dropletId) (P.read actionId)) >>= outputResult+ = getAction (P.read dropletId) (P.read actionId) >>= outputResult parseCommandOptions ("droplets":dropletId:"snapshots":[])- = injr ( listDropletSnapshots (P.read dropletId)) >>= outputResult+ = listDropletSnapshots (P.read dropletId) >>= outputResult parseCommandOptions ("droplets":dropletId:[])- = injr ( showDroplet (P.read dropletId)) >>= outputResult+ = showDroplet (P.read dropletId) >>= outputResult parseCommandOptions ("droplets":"ssh":dropletIdOrName:[]) = (do- droplets <- injr $ (findByIdOrName dropletIdOrName) <$> listDroplets+ droplets <- findByIdOrName dropletIdOrName <$> listDroplets case droplets of- (did:_) -> injr $ dropletConsole did+ (did:_) -> dropletConsole did [] -> return (error $ "no droplet with id or name " <> dropletIdOrName) ) >>= outputResult-parseCommandOptions ("images":"list":_) = injl listImages >>= outputResult-parseCommandOptions ("regions":"list":_) = injl listRegions >>= outputResult-parseCommandOptions ("keys":"list":_) = injl listKeys >>= outputResult-parseCommandOptions ("sizes":"list":_) = injl listSizes >>= outputResult-parseCommandOptions e = fail $ "I don't know how to interpret commands " ++ unwords e ++ "\n" ++ usage+parseCommandOptions ("images":"list":_) = listImages >>= outputResult+parseCommandOptions ("regions":"list":_) = listRegions >>= outputResult+parseCommandOptions ("keys":"list":_) = listKeys >>= outputResult+parseCommandOptions ("sizes":"list":_) = listSizes >>= outputResult+parseCommandOptions ("ips":"list":_) = listFloatingIPs >>= outputResult+parseCommandOptions ("ips":"create":dropletOrRegion:[]) = do+ regions <- listRegions+ outputResult =<< if dropletOrRegion `elem` map regionSlug regions+ then createFloatingIP (TargetRegion dropletOrRegion)+ else createFloatingIP (TargetDroplet $ read dropletOrRegion)+parseCommandOptions ("ips":"delete":ip:[]) = deleteFloatingIP (P.read ip) >>= outputResult+parseCommandOptions ("ips":ip:"assign":did:[]) = assignFloatingIP (P.read ip) (P.read did) >>= outputResult+parseCommandOptions ("ips":ip:"unassign": []) = unassignFloatingIP (P.read ip) >>= outputResult+parseCommandOptions ("dns":"list":_) = listDomains >>= outputResult+parseCommandOptions ("dns":"create":name:ip:[]) = createDomain (P.read name) (P.read ip) >>= outputResult+parseCommandOptions ("dns":"delete":name:[]) = deleteDomain (P.read name) >>= outputResult+parseCommandOptions ("dns":name:"list":_) = listRecords (P.read name) >>= outputResult+parseCommandOptions ("dns":name:"create":rest) =+ case (parseRecord $ unwords rest) of+ Left (Error e) -> fail e+ Right r -> createRecord (P.read name) r >>= outputResult+parseCommandOptions ("dns":name:"delete":rid:[]) = deleteRecord (P.read name) (P.read rid) >>= outputResult+parseCommandOptions e = fail $ "I don't know how to interpret commands " ++ unwords e ++ "\n" ++ usage
+ src/Network/DO.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE ScopedTypeVariables, TypeOperators #-}+module Network.DO(+ -- * Types+ Command,+ module Network.DO.Types,+ -- * Generic Commands+ listKeys, listSizes, listRegions, listImages,+ -- * Droplets Commands+ listDroplets, createDroplet, showDroplet, destroyDroplet,+ dropletAction, dropletConsole, getAction, listDropletSnapshots,+ -- * Floating IPs Commands+ listFloatingIPs, createFloatingIP, deleteFloatingIP, assignFloatingIP, unassignFloatingIP,+ -- * Domains Commands+ listDomains, createDomain, deleteDomain,+ listRecords, createRecord, deleteRecord,+ -- * Utilities+ runDOEnv, runDO, getAuthFromEnv, outputResult,+ generateName,+ module Network.DO.Droplets.Utils) where++import Control.Exception (catch, throw)+import Control.Monad.Trans.Free+import Data.IP+import qualified Network.DO.Commands as C+import qualified Network.DO.Domain as C+import qualified Network.DO.Droplets.Commands as C+import Network.DO.Droplets.Utils+import qualified Network.DO.IP.Commands as C+import Network.DO.Names+import Network.DO.Net+import Network.DO.Pairing+import Network.DO.Pretty+import Network.DO.Types+import Network.REST+import System.Environment (getEnv)+import System.IO.Error (isDoesNotExistError)++type Command w a = FreeT (C.DO :+: C.DropletCommands :+: C.IPCommands :+: C.DomainCommands) (RESTT w) a++listKeys :: (Monad w) => Command w [Key]+listKeys = injl C.listKeys++listSizes :: (Monad w) => Command w [Size]+listSizes = injl C.listSizes++listImages :: (Monad w) => Command w [Image]+listImages = injl C.listImages++listRegions :: (Monad w) => Command w [Region]+listRegions = injl C.listRegions++listFloatingIPs :: (Monad w) => Command w [FloatingIP]+listFloatingIPs = injrrl C.listFloatingIPs++createFloatingIP :: (Monad w) => FloatingIPTarget -> Command w (Result FloatingIP)+createFloatingIP = injrrl . C.createFloatingIP++deleteFloatingIP :: (Monad w) => IP -> Command w (Maybe String)+deleteFloatingIP = injrrl . C.deleteFloatingIP++assignFloatingIP :: (Monad w) => IP -> Id -> Command w (Result (ActionResult IPActionType))+assignFloatingIP ip did = injrrl $ C.floatingIPAction ip (AssignIP did)++unassignFloatingIP :: (Monad w) => IP -> Command w (Result (ActionResult IPActionType))+unassignFloatingIP ip = injrrl $ C.floatingIPAction ip UnassignIP++listDomains :: (Monad w) => Command w [Domain]+listDomains = injrrr C.listDomains++createDomain :: (Monad w) => DomainName -> IP -> Command w (Result Domain)+createDomain dname ip = injrrr $ C.createDomain dname ip++deleteDomain :: (Monad w) => DomainName -> Command w (Maybe String)+deleteDomain = injrrr . C.deleteDomain++listRecords :: (Monad w) => DomainName -> Command w [DomainRecord]+listRecords = injrrr . C.listRecords++createRecord :: (Monad w) => DomainName -> DomainRecord -> Command w (Result DomainRecord)+createRecord dname ip = injrrr $ C.createRecord dname ip++deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Maybe String)+deleteRecord dname rid = injrrr $ C.deleteRecord dname rid++listDroplets :: (Monad w) => Command w [Droplet]+listDroplets = injrl C.listDroplets++createDroplet :: (Monad w) => BoxConfiguration -> Command w (Either Error Droplet)+createDroplet = injrl . C.createDroplet++showDroplet :: (Monad w) => Integer -> Command w (Either Error Droplet)+showDroplet = injrl . C.showDroplet++destroyDroplet :: (Monad w) => Integer -> Command w (Maybe String)+destroyDroplet = injrl . C.destroyDroplet++dropletAction :: (Monad w) => Id -> Action -> Command w (Result (ActionResult DropletActionType))+dropletAction did = injrl . C.dropletAction did++dropletConsole :: (Monad w) => Droplet -> Command w (Result ())+dropletConsole = injrl . C.dropletConsole++getAction :: (Monad w) => Id -> Id -> Command w (Result (ActionResult DropletActionType))+getAction did = injrl . C.getAction did++listDropletSnapshots :: (Monad w) => Id -> Command w [Image]+listDropletSnapshots = injrl . C.listDropletSnapshots++-- | Run DO actions, extracting authentication token from environment variable `AUTH_TOKEN`.+runDOEnv :: Command IO a -> IO a+runDOEnv actions = getAuthFromEnv >>= runDO actions++-- | Run DO actions, passing a built authentication token.+runDO :: Command IO a -> Maybe AuthToken -> IO a+runDO actions token = runConduit $ pairEffectM (\ _ b -> return b) (mkDOClient $ Tool Nothing token False) actions+++getAuthFromEnv :: IO (Maybe AuthToken)+getAuthFromEnv = (Just `fmap` getEnv "AUTH_TOKEN") `catch` (\ (e :: IOError) -> if isDoesNotExistError e then return Nothing else throw e)
src/Network/DO/Commands.hs view
@@ -33,9 +33,9 @@ listRegions = ListRegions P.id -- dual type, for creating interpreters-data CoDO m k = CoDO { listKeysH :: (m [Key], k)- , listSizesH :: (m [Size], k)- , listImagesH :: (m [Image], k)+data CoDO m k = CoDO { listKeysH :: (m [Key], k)+ , listSizesH :: (m [Size], k)+ , listImagesH :: (m [Image], k) , listRegionsH :: (m [Region], k) } deriving Functor @@ -44,7 +44,7 @@ -- pair DSL with interpreter within some monadic context instance (Monad m) => PairingM (CoDO m) DO m where- pairM f (CoDO ks _ _ _) (ListKeys k) = pairM f ks k- pairM f (CoDO _ szs _ _) (ListSizes k) = pairM f szs k- pairM f (CoDO _ _ imgs _) (ListImages k) = pairM f imgs k- pairM f (CoDO _ _ _ rgns) (ListRegions k) = pairM f rgns k+ pairM f (CoDO ks _ _ _ ) (ListKeys k) = pairM f ks k+ pairM f (CoDO _ szs _ _ ) (ListSizes k) = pairM f szs k+ pairM f (CoDO _ _ imgs _ ) (ListImages k) = pairM f imgs k+ pairM f (CoDO _ _ _ rgns ) (ListRegions k) = pairM f rgns k
+ src/Network/DO/Domain.hs view
@@ -0,0 +1,4 @@+module Network.DO.Domain(module C) where++import Network.DO.Domain.Commands as C+import Network.DO.Domain.Net as C
+ src/Network/DO/Domain/Commands.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Network.DO.Domain.Commands where++import Control.Comonad.Trans.Cofree+import Control.Monad.Trans.Free+import Data.IP+import Network.DO.Pairing+import Network.DO.Types+import Prelude as P++data DomainCommands a = ListDomains ([Domain] -> a)+ | CreateDomain DomainName IP (Result Domain -> a)+ | DeleteDomain DomainName (Maybe String -> a)+ | ListRecords DomainName ([DomainRecord] -> a)+ | CreateRecord DomainName DomainRecord (Result DomainRecord -> a)+ | DeleteRecord DomainName Id (Maybe String -> a)+ deriving (Functor)++type DomainCommandsT = FreeT DomainCommands++-- smart constructors+listDomains :: DomainCommands [Domain]+listDomains = ListDomains P.id++createDomain :: DomainName -> IP -> DomainCommands (Result Domain)+createDomain dname ip = CreateDomain dname ip P.id++deleteDomain :: DomainName -> DomainCommands (Maybe String)+deleteDomain ip = DeleteDomain ip P.id++listRecords :: DomainName -> DomainCommands [DomainRecord]+listRecords n = ListRecords n P.id++createRecord :: DomainName -> DomainRecord -> DomainCommands (Result DomainRecord)+createRecord dname record = CreateRecord dname record P.id++deleteRecord :: DomainName -> Id -> DomainCommands (Maybe String)+deleteRecord dname rid = DeleteRecord dname rid P.id++-- dual type, for creating interpreters+data CoDomainCommands m k =+ CoDomainCommands { listDomainsH :: (m [Domain], k)+ , createDomainH :: DomainName -> IP -> (m (Result Domain), k)+ , deleteDomainH :: DomainName -> (m (Maybe String), k)+ , listRecordsH :: DomainName -> (m [DomainRecord], k)+ , createRecordH :: DomainName -> DomainRecord -> (m (Result DomainRecord), k)+ , deleteRecordH :: DomainName -> Id -> (m (Maybe String), k)+ } deriving Functor++-- Cofree closure of CoDomainCommands functor+type CoDomainCommandsT m = CofreeT (CoDomainCommands m)++-- pair DSL with interpreter within some monadic context+instance (Monad m) => PairingM (CoDomainCommands m) DomainCommands m where+ pairM f (CoDomainCommands ks _ _ _ _ _) (ListDomains k) = pairM f ks k+ pairM f (CoDomainCommands _ tgt _ _ _ _) (CreateDomain n i k) = pairM f (tgt n i) k+ pairM f (CoDomainCommands _ _ del _ _ _) (DeleteDomain n k) = pairM f (del n) k+ pairM f (CoDomainCommands _ _ _ ks _ _) (ListRecords n k) = pairM f (ks n) k+ pairM f (CoDomainCommands _ _ _ _ tgt _) (CreateRecord n r k) = pairM f (tgt n r) k+ pairM f (CoDomainCommands _ _ _ _ _ del) (DeleteRecord n i k) = pairM f (del n i) k
+ src/Network/DO/Domain/Net.hs view
@@ -0,0 +1,83 @@+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Network interpreter for Floating Domains specific API+module Network.DO.Domain.Net(dnsCommandsInterpreter) where++import Control.Applicative+import Control.Comonad.Env.Class (ComonadEnv, ask)+import Control.Monad.Trans (MonadIO)+import Data.Aeson as A hiding (Result)+import Data.IP+import Data.Maybe+import Data.Proxy+import Network.DO.Domain.Commands+import Network.DO.Net.Common+import Network.DO.Types as DO hiding (URI)+import Network.REST+import Prelude as P hiding (error)++domainsURI :: String+domainsURI = "domains"++domainsEndpoint :: String+domainsEndpoint = rootURI </> apiVersion </> domainsURI++instance Listable Domain where+ listEndpoint _ = domainsEndpoint+ listField _ = "domains"++doCreateDomain :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> IP -> (RESTT m (Result Domain), w a)+doCreateDomain w name ip = maybe (return $ error "no authentication token defined", w)+ runQuery+ (authToken (ask w))+ where+ runQuery t = let opts = authorisation t+ domain = postJSONWith opts (toURI domainsEndpoint) (toJSON config) >>= return . fromResponse "domain"+ in (domain, w)++ where+ config = DomainConfig name ip++doDeleteDomain :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> (RESTT m (Maybe String), w a)+doDeleteDomain w name = maybe (return $ Just "no authentication token defined", w)+ (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ domainsEndpoint </> show name) >> return Nothing+ in (r, w))+ (authToken (ask w))++doListRecords :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> (RESTT m [DomainRecord], w a)+doListRecords w name = maybe (return [], w)+ (\ t -> let records = toList "domain_records" <$> getJSONWith (authorisation t) (toURI $ domainsEndpoint </> show name </> "records")+ in (records, w))+ (authToken (ask w))++doCreateRecord :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> DomainRecord -> (RESTT m (Result DomainRecord), w a)+doCreateRecord w name record =+ maybe (return $ error "no authentication token defined", w)+ runQuery+ (authToken (ask w))+ where+ runQuery t = let opts = authorisation t+ domain = postJSONWith opts (toURI $ domainsEndpoint </> show name </> "records") (toJSON record) >>= return . fromResponse "domain_record"+ in (domain, w)++doDeleteRecord :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> Id -> (RESTT m (Maybe String), w a)+doDeleteRecord w name rid =+ maybe (return $ Just "no authentication token defined", w)+ (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ domainsEndpoint </> show name </> "records" </> show rid ) >> return Nothing+ in (r, w))+ (authToken (ask w))++dnsCommandsInterpreter :: (MonadIO m, ComonadEnv ToolConfiguration w) => w a -> CoDomainCommands (RESTT m) (w a)+dnsCommandsInterpreter = CoDomainCommands+ <$> queryList (Proxy :: Proxy Domain)+ <*> doCreateDomain+ <*> doDeleteDomain+ <*> doListRecords+ <*> doCreateRecord+ <*> doDeleteRecord+
src/Network/DO/Droplets/Commands.hs view
@@ -18,8 +18,8 @@ data DropletCommands a = ListDroplets ([Droplet] -> a) | CreateDroplet BoxConfiguration (Result Droplet -> a) | DestroyDroplet Id (Maybe String -> a)- | DropletAction Id Action (Result ActionResult -> a)- | GetAction Id Id (Result ActionResult -> a)+ | DropletAction Id Action (Result (ActionResult DropletActionType) -> a)+ | GetAction Id Id (Result (ActionResult DropletActionType) -> a) | ListSnapshots Id ([Image] -> a) | Console Droplet (Result () -> a) | ShowDroplet Id (Result Droplet -> a)@@ -41,13 +41,13 @@ destroyDroplet :: Id -> DropletCommands (Maybe String) destroyDroplet did = DestroyDroplet did P.id -dropletAction :: Id -> Action -> DropletCommands (Result ActionResult)+dropletAction :: Id -> Action -> DropletCommands (Result (ActionResult DropletActionType)) dropletAction did action = DropletAction did action P.id dropletConsole :: Droplet -> DropletCommands (Result ()) dropletConsole droplet = Console droplet P.id -getAction :: Id -> Id -> DropletCommands (Result ActionResult)+getAction :: Id -> Id -> DropletCommands (Result (ActionResult DropletActionType)) getAction did actId = GetAction did actId P.id listDropletSnapshots :: Id -> DropletCommands [Image]@@ -58,8 +58,8 @@ data CoDropletCommands m k = CoDropletCommands { listDropletsH :: (m [Droplet], k) , createDropletH :: BoxConfiguration -> (m (Result Droplet), k) , destroyDropletH :: Id -> (m (Maybe String), k)- , actionDropletH :: Id -> Action -> (m (Result ActionResult), k)- , getActionH :: Id -> Id -> (m (Result ActionResult), k)+ , actionDropletH :: Id -> Action -> (m (Result (ActionResult DropletActionType)), k)+ , getActionH :: Id -> Id -> (m (Result (ActionResult DropletActionType)), k) , listSnapshotsH :: Id -> (m [Image], k) , consoleH :: Droplet -> (m (Result ()), k) , showDropletH :: Id -> (m (Result Droplet), k)
src/Network/DO/Droplets/Net.hs view
@@ -1,9 +1,4 @@-{-# LANGUAGE DoAndIfThenElse #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DoAndIfThenElse, FlexibleContexts, MultiParamTypeClasses, OverloadedStrings, RecordWildCards, ScopedTypeVariables #-} -- | Network interpreter for Droplets specific API module Network.DO.Droplets.Net(dropletCommandsInterpreter) where@@ -13,8 +8,6 @@ import Control.Exception (IOException) import Control.Monad.Trans (MonadIO) import Data.Aeson as A hiding (Result)-import qualified Data.Aeson.Types as A-import qualified Data.HashMap.Strict as H import Data.Maybe import Data.Monoid ((<>)) import Data.Proxy@@ -23,7 +16,6 @@ import Network.DO.Net.Common import Network.DO.Types as DO hiding (URI) import Network.REST-import Network.Wreq hiding (Proxy) import Prelude as P hiding (error) dropletsURI :: String@@ -43,10 +35,6 @@ in (snapshots, w)) (authToken (ask w)) -dropletFromResponse :: Either String Value -> Result Droplet-dropletFromResponse (Right (Object b)) = either error (Right . id) $ A.parseEither parseJSON (b H.! "droplet")-dropletFromResponse v = error $ "cannot decode JSON value to a droplet " ++ show v- doCreate :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> BoxConfiguration -> (RESTT m (Result Droplet), w a) doCreate w config = maybe (return $ error "no authentication token defined", w) runQuery@@ -54,7 +42,7 @@ where runQuery t = let opts = authorisation t droplets = postJSONWith opts (toURI dropletsEndpoint) (toJSON config) >>= handleResponse- handleResponse d = case dropletFromResponse d of+ handleResponse d = case fromResponse "droplet" d of Right b -> if (not $ backgroundCreate config) then waitForBoxToBeUp opts 60 b else return (Right b)@@ -67,27 +55,23 @@ in (r, w)) (authToken (ask w)) -actionResult :: Either String Value -> Result ActionResult-actionResult (Right (Object r)) = either error (Right . id) $ A.parseEither parseJSON (r H.! "action")-actionResult e = error $ "cannot extract action result from " ++ show e--doAction :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> Action -> (RESTT m (Result ActionResult), w a)+doAction :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> Action -> (RESTT m (Result (ActionResult DropletActionType)), w a) doAction w dropletId action = maybe (return $ error "no authentication token defined", w) (\ t -> let r = postJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId </> "actions") (toJSON action)- >>= return . actionResult+ >>= return . fromResponse "action" in (r, w)) (authToken (ask w)) -doGetAction :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> Id -> (RESTT m (Result ActionResult), w a)+doGetAction :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> Id -> (RESTT m (Result (ActionResult DropletActionType)), w a) doGetAction w dropletId actionId = maybe (return $ error "no authentication token defined", w) (\ t -> let r = getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId </> "actions" </> show actionId)- >>= return . actionResult . Right+ >>= return . fromResponse "action" . Right in (r, w)) (authToken (ask w)) doShowDroplet :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> (RESTT m (Result Droplet), w a) doShowDroplet w dropletId = maybe (return $ error "no authentication token defined", w)- (\ t -> let r = dropletFromResponse . Right <$> getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId)+ (\ t -> let r = fromResponse "droplet" . Right <$> getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId) in (r, w)) (authToken (ask w)) @@ -96,8 +80,8 @@ (\ip -> do s <- ssh ["root@" <> show ip ] case s of- Left (e :: IOException) -> return $ error (show e)- Right () -> return $ Right ()+ Left (e :: IOException) -> return $ error (show e)+ Right () -> return $ Right () ) (publicIP droplet) in (r, w)@@ -107,7 +91,7 @@ waitForBoxToBeUp opts n box = do waitFor 1000000 ("waiting for droplet " ++ name box ++ " to become Active: " ++ show (n) ++ "s") b <- getJSONWith opts (toURI $ dropletsEndpoint </> show (dropletId box))- case dropletFromResponse (Right b) of+ case fromResponse "droplet" (Right b) of Right box'-> if status box' == Active then return (Right box') else waitForBoxToBeUp opts (n-1) box'
+ src/Network/DO/IP.hs view
@@ -0,0 +1,9 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Network.DO.IP(module Network.DO.IP.Commands+ ,module Network.DO.IP.Net+ ) where++import Network.DO.IP.Commands+import Network.DO.IP.Net
+ src/Network/DO/IP/Commands.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Network.DO.IP.Commands where++import Control.Comonad.Trans.Cofree+import Control.Monad.Trans.Free+import Data.IP+import Network.DO.Pairing+import Network.DO.Types+import Prelude as P++-- functor for DO DSL+data IPCommands a = ListFloatingIPs ([FloatingIP] -> a)+ | CreateIP FloatingIPTarget (Result FloatingIP -> a)+ | DeleteIP IP (Maybe String -> a)+ | ActionIP IP IPAction (Result (ActionResult IPActionType) -> a)+ deriving (Functor)++-- free transformer to embed effects+type IPCommandsT = FreeT IPCommands++-- smart constructors+listFloatingIPs :: IPCommands [FloatingIP]+listFloatingIPs = ListFloatingIPs P.id++createFloatingIP :: FloatingIPTarget -> IPCommands (Result FloatingIP)+createFloatingIP target = CreateIP target P.id++deleteFloatingIP :: IP -> IPCommands (Maybe String)+deleteFloatingIP ip = DeleteIP ip P.id++floatingIPAction :: IP -> IPAction -> IPCommands (Result (ActionResult IPActionType))+floatingIPAction ip action = ActionIP ip action P.id++-- dual type, for creating interpreters+data CoIPCommands m k =+ CoIPCommands { listFloatingIPsH :: (m [FloatingIP], k)+ , createFloatingIPH :: FloatingIPTarget -> (m (Result FloatingIP), k)+ , deleteIPH :: IP -> (m (Maybe String), k)+ , actionIPH :: IP -> IPAction -> (m (Result (ActionResult IPActionType)), k)+ } deriving Functor++-- Cofree closure of CoIPCommands functor+type CoIPCommandsT m = CofreeT (CoIPCommands m)++-- pair DSL with interpreter within some monadic context+instance (Monad m) => PairingM (CoIPCommands m) IPCommands m where+ pairM f (CoIPCommands ks _ _ _) (ListFloatingIPs k) = pairM f ks k+ pairM f (CoIPCommands _ tgt _ _) (CreateIP i k) = pairM f (tgt i) k+ pairM f (CoIPCommands _ _ del _) (DeleteIP i k) = pairM f (del i) k+ pairM f (CoIPCommands _ _ _ act) (ActionIP i a k) = pairM f (act i a) k
+ src/Network/DO/IP/Net.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Network interpreter for Floating IPs specific API+module Network.DO.IP.Net(ipCommandsInterpreter) where++import Control.Applicative+import Control.Comonad.Env.Class (ComonadEnv, ask)+import Control.Monad.Trans (MonadIO)+import Data.Aeson as A hiding (Result)+import Data.IP+import Data.Maybe+import Data.Proxy+import Network.DO.IP.Commands+import Network.DO.Net.Common+import Network.DO.Types as DO hiding (URI)+import Network.REST+import Prelude as P hiding (error)++floatingIpsURI :: String+floatingIpsURI = "floating_ips"++floatingIpsEndpoint :: String+floatingIpsEndpoint = rootURI </> apiVersion </> floatingIpsURI++instance Listable FloatingIP where+ listEndpoint _ = floatingIpsEndpoint+ listField _ = "floating_ips"++doCreateIP :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> FloatingIPTarget -> (RESTT m (Result FloatingIP), w a)+doCreateIP w config = maybe (return $ error "no authentication token defined", w)+ runQuery+ (authToken (ask w))+ where+ runQuery t = let opts = authorisation t+ ip = postJSONWith opts (toURI floatingIpsEndpoint) (toJSON config) >>= return . fromResponse "floating_ip"+ in (ip, w)++doDeleteIP :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> IP -> (RESTT m (Maybe String), w a)+doDeleteIP w ip = maybe (return $ Just "no authentication token defined", w)+ (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ floatingIpsEndpoint </> show ip) >> return Nothing+ in (r, w))+ (authToken (ask w))++doAction :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> IP -> IPAction -> (RESTT m (Result (ActionResult IPActionType)), w a)+doAction w ip action = maybe (return $ error "no authentication token defined", w)+ (\ t -> let r = postJSONWith (authorisation t) (toURI $ floatingIpsEndpoint </> show ip </> "actions") (toJSON action)+ >>= return . fromResponse "action"+ in (r, w))+ (authToken (ask w))++ipCommandsInterpreter :: (MonadIO m, ComonadEnv ToolConfiguration w) => w a -> CoIPCommands (RESTT m) (w a)+ipCommandsInterpreter = CoIPCommands+ <$> queryList (Proxy :: Proxy FloatingIP)+ <*> doCreateIP+ <*> doDeleteIP+ <*> doAction+
src/Network/DO/Net.hs view
@@ -4,6 +4,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeOperators #-} -- | Interpreter for accessing DO API through the web using [wreq http://www.serpentine.com/wreq]. module Network.DO.Net(mkDOClient) where@@ -18,9 +19,12 @@ import Prelude as P import Network.DO.Commands+import Network.DO.Domain import Network.DO.Droplets.Commands import Network.DO.Droplets.Net+import Network.DO.IP import Network.DO.Net.Common+import Network.DO.Pairing import Network.DO.Types as DO hiding (URI) import Network.REST @@ -74,10 +78,17 @@ <*> queryList (Proxy :: Proxy Image) <*> queryList (Proxy :: Proxy Region) -mkDOClient :: (MonadIO m) => ToolConfiguration -> CofreeT (Product (CoDO (RESTT m)) (CoDropletCommands (RESTT m))) (Env ToolConfiguration) (RESTT m ())+mkDOClient :: (MonadIO m) => ToolConfiguration+ -> CofreeT (CoDO (RESTT m) :*: CoDropletCommands (RESTT m) :*: CoIPCommands (RESTT m) :*: CoDomainCommands (RESTT m)) (Env ToolConfiguration) (RESTT m ()) mkDOClient config = coiterT next start where next = Pair <$> genericCommands- <*> dropletCommandsInterpreter+ <*> (Pair+ <$> dropletCommandsInterpreter+ <*> (Pair+ <$> ipCommandsInterpreter+ <*> dnsCommandsInterpreter+ )+ ) start = env config (return ())
src/Network/DO/Net/Common.hs view
@@ -1,23 +1,17 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE FlexibleContexts, MultiParamTypeClasses, OverloadedStrings #-} module Network.DO.Net.Common where import Control.Comonad.Env.Class (ComonadEnv, ask)-import Control.Lens ((&), (.~))-import Data.Aeson as A+import Data.Aeson as A hiding (Result) import qualified Data.Aeson.Types as A-import Data.ByteString.Char8 (pack) import qualified Data.HashMap.Strict as H import Data.Maybe-import Data.Monoid import Data.Proxy import Data.Text (Text) import qualified Data.Vector as V-import Network.DO.Types as DO hiding (URI)+import Network.DO.Types as DO hiding (URI, error) import Network.REST import Network.URI (URI, parseURI)-import Network.Wreq hiding (Proxy) import Prelude as P rootURI :: String@@ -31,16 +25,13 @@ s </> s' = s ++ "/" ++ s' toURI :: String -> URI-toURI = fromJust . parseURI+toURI s = maybe (P.error $ "cannot parse URI from " ++ s) id $ parseURI s toList :: (FromJSON a) => Text -> Value -> [a] toList k (Object o) = let Array boxes = o H.! k in mapMaybe (A.parseMaybe parseJSON) (V.toList boxes) toList _ _ = [] -authorisation :: String -> Options-authorisation t = defaults & header "Authorization" .~ ["Bearer " <> pack t]- class Listable a where listEndpoint :: Proxy a -> String listField :: Proxy a -> Text@@ -51,3 +42,7 @@ in (droplets, w)) (authToken (ask w)) +-- |Extract a typed result from a JSON output+fromResponse :: (FromJSON a) => Text -> Either String Value -> Result a+fromResponse key (Right (Object b)) = either error (Right . id) $ A.parseEither parseJSON (b H.! key)+fromResponse _ v = error $ "cannot decode JSON value to a FloatingIP " ++ show v
src/Network/DO/Pairing.hs view
@@ -1,25 +1,42 @@-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE FunctionalDependencies #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-} module Network.DO.Pairing (Pairing(..) , PairingM(..)+ , (:+:), (:*:) , pairEffect , pairEffectM , pairEffect' , injr, injl+ , injrl, injrr+ , injrrl, injrrr ) where import Control.Comonad (Comonad, extract) import Control.Comonad.Trans.Cofree (CofreeT, unwrap) import Control.Monad.Trans.Free (FreeF (..), FreeT, liftF, runFreeT)-import Data.Functor.Sum import Data.Functor.Identity (Identity (..)) import Data.Functor.Product+import Data.Functor.Sum +type a :+: b = Sum a b++infixr 1 :+:++type a :*: b = Product a b++infixr 2 :*:+ class (Functor f, Functor g) => Pairing f g where pair :: (a -> b -> r) -> f a -> g b -> r @@ -38,19 +55,31 @@ instance (Monad m) => PairingM ((,) (m a)) ((->) a) m where pairM p (ma, b) g = ma >>= \ a -> p b (g a) -instance (Monad m, PairingM f h m, PairingM g k m) => PairingM (Sum f g) (Product h k) m where+instance (Monad m, PairingM f h m, PairingM g k m) => PairingM (f :+: g) (h :*: k) m where pairM p (InL f) (Pair h _) = pairM p f h pairM p (InR g) (Pair _ k) = pairM p g k -instance (Monad m, PairingM h f m, PairingM k g m) => PairingM (Product h k) (Sum f g) m where+instance (Monad m, PairingM h f m, PairingM k g m) => PairingM (h :*: k) (f :+: g) m where pairM p (Pair h _) (InL f) = pairM p h f pairM p (Pair _ k) (InR g) = pairM p k g -injl :: (Monad m, Functor f, Functor g) => f a -> FreeT (Sum f g) m a+injl :: (Monad m, Functor f, Functor g) => f a -> FreeT (f :+: g) m a injl = liftF . InL -injr :: (Monad m, Functor f, Functor g) => g a -> FreeT (Sum f g) m a+injr :: (Monad m, Functor f, Functor g) => g a -> FreeT (f :+: g) m a injr = liftF . InR++injrl :: (Monad m, Functor f, Functor g, Functor h) => g a -> FreeT (f :+: g :+: h) m a+injrl = liftF . InR . InL++injrr :: (Monad m, Functor f, Functor g, Functor h) => h a -> FreeT (f :+: g :+: h) m a+injrr = liftF . InR . InR++injrrl :: (Monad m, Functor f, Functor g, Functor h, Functor k) => h a -> FreeT (f :+: g :+: h :+: k) m a+injrrl = liftF . InR . InR . InL++injrrr :: (Monad m, Functor f, Functor g, Functor h, Functor k) => k a -> FreeT (f :+: g :+: h :+: k) m a+injrrr = liftF . InR . InR . InR pairEffect :: (Pairing f g, Comonad w, Monad m) => (a -> b -> r) -> CofreeT f w a -> FreeT g m b -> m r
src/Network/DO/Pretty.hs view
@@ -81,9 +81,35 @@ nest 5 (pretty szPrice_Hourly <> text "$/h, " <> pretty szPrice_Monthly <> text "$/mo" ) $$ nest 5 (hcat $ punctuate (char ',') $ map pretty szRegions) -instance Pretty ActionResult where+instance (Pretty r) => Pretty (ActionResult r) where pretty ActionResult{..} = brackets (integer actionId) <+> pretty actionStartedAt <+> text "->" <+> pretty actionCompletedAt $$- (nest 5 $ integer actionResourceId <+> text (show actionType) <> char ':' <+> text (show actionStatus))+ (nest 5 $ integer actionResourceId <+> pretty actionType <> char ':' <+> text (show actionStatus))++instance Pretty DropletActionType++instance Pretty FloatingIP where+ pretty FloatingIP{..} =+ case floatingDroplet of+ Nothing -> ipAndRegion <+> text "?"+ Just d -> ipAndRegion <+> integer (dropletId d) <> char '/' <> text (name d)+ where+ ipAndRegion = pretty floatingIp <> text "/" <> text (regionSlug floatingRegion) <+> text "->"++instance Pretty IPActionType++instance Pretty Domain where+ pretty Domain{..} = text (domain domainName) $$+ maybe mempty (nest 5 . vcat . map text .lines) zone_file++instance Pretty DomainRecord where+ pretty DomainRecord{..} = brackets (integer recordId) <+> pretty recordType <+> text recordName <+> text recordData+ <+> prettyInt recordPriority+ <+> prettyInt recordPort+ <+> prettyInt recordWeight+ where+ prettyInt = maybe (char '-') int++instance Pretty DNSType outputResult :: (Pretty a, MonadIO m) => a -> m () outputResult = liftIO . putStrLn . render . pretty
src/Network/DO/Types.hs view
@@ -17,10 +17,10 @@ import Data.IP import Data.List (elemIndex) import Data.Monoid ((<>))-import Data.Text (unpack)+import Data.Text (pack, unpack) import Data.Time (UTCTime) import GHC.Generics-+import qualified Text.Parsec as P type AuthToken = String @@ -73,7 +73,6 @@ ", regionAvailable = " <> show regionAvailable <> "}" - instance FromJSON Region where parseJSON (String s) = return $ RegionSlug (unpack s) parseJSON (Object o) = if H.null o@@ -85,7 +84,6 @@ <*> o .: "available" parseJSON e = failParse e - -- | String representation of size slugs -- This maps to corresponding expected JSON string value. sizeSlugs :: [String]@@ -201,6 +199,9 @@ parseJSON (String s) = return $ read $ unpack s parseJSON e = fail $ "cannot parse IP " <> show e +instance ToJSON IP where+ toJSON = String . pack . show+ instance FromJSON NetType where parseJSON (String s) = case s of "public" -> return Public@@ -370,23 +371,22 @@ parseJSON e = failParse e - -- * Droplets Actions -- | Type of action status -- This is returned when action is initiated or when status of some action is requested -data ActionResult = ActionResult { actionId :: Id- , actionStatus :: ActionStatus- , actionType :: ActionType- , actionStartedAt :: Maybe Date- , actionCompletedAt :: Maybe Date- , actionResourceId :: Id- , actionResourceType :: String- , actionRegionSlug :: Region- } deriving (Show)+data ActionResult result = ActionResult { actionId :: Id+ , actionStatus :: ActionStatus+ , actionType :: result+ , actionStartedAt :: Maybe Date+ , actionCompletedAt :: Maybe Date+ , actionResourceId :: Id+ , actionResourceType :: String+ , actionRegionSlug :: Region+ } deriving (Show) -instance FromJSON ActionResult where+instance (FromJSON r) => FromJSON (ActionResult r) where parseJSON (Object o) = ActionResult <$> o .: "id" <*> o .: "status"@@ -411,12 +411,12 @@ _ -> fail $ "unknown action status " ++ show s parseJSON v = fail $ "cannot parse action status " ++ show v -data ActionType = PowerOff+data DropletActionType = PowerOff | PowerOn | MakeSnapshot deriving (Show) -instance FromJSON ActionType where+instance FromJSON DropletActionType where parseJSON (String s) = case s of "power_off" -> return PowerOff "power_on" -> return PowerOn@@ -424,7 +424,7 @@ _ -> fail $ "unknown action type " ++ show s parseJSON v = fail $ "cannot parse action type " ++ show v -instance ToJSON ActionType where+instance ToJSON DropletActionType where toJSON PowerOff = String "power_off" toJSON PowerOn = String "power_on" toJSON MakeSnapshot = String "snapshot"@@ -444,9 +444,25 @@ -- |Type of Domain zones -- -- https://developers.digitalocean.com/documentation/v2/#domains-data Domain = Domain { domainName :: String- , domainTTL :: Int- , zone_file :: String++newtype DomainName = DomainName { domain :: String }++instance Show DomainName where+ show = domain++instance Read DomainName where+ readsPrec _ s = [(DomainName s,[])]++instance FromJSON DomainName where+ parseJSON (String s) = pure $ DomainName $ unpack s+ parseJSON e = failParse e++instance ToJSON DomainName where+ toJSON (DomainName n) = String (pack n)++data Domain = Domain { domainName :: DomainName+ , domainTTL :: Maybe Int+ , zone_file :: Maybe String } deriving (Show) instance FromJSON Domain where@@ -457,6 +473,13 @@ parseJSON e = failParse e +data DomainConfig = DomainConfig DomainName IP++instance ToJSON DomainConfig where+ toJSON (DomainConfig name ip) = object [ "name" .= name+ , "ip_address" .= ip+ ]+ -- | Enumeration of possible DNS records types data DNSType = A | CNAME | TXT | PTR | SRV | NS | AAAA | MX deriving (Show, Read, Generic)@@ -471,9 +494,9 @@ , recordType :: DNSType , recordName :: String , recordData :: String- , recordPriority :: Maybe Double+ , recordPriority :: Maybe Int , recordPort :: Maybe Int- , recordWeight :: Maybe Double+ , recordWeight :: Maybe Int } deriving (Show) @@ -488,6 +511,105 @@ <*> o .: "weight" parseJSON e = failParse e++instance ToJSON DomainRecord where+ toJSON DomainRecord{..} = object [ "type" .= recordType+ , "name" .= recordName+ , "data" .= recordData+ , "priority" .= recordPriority+ , "port" .= recordPort+ , "weight" .= recordWeight+ ]++parseRecord :: String -> Result DomainRecord+parseRecord s =+ case P.parse recordParser "" s of+ Left e -> Left (Error $ show e)+ Right r -> Right r+ where+ recordParser :: P.Parsec String s DomainRecord+ recordParser = do+ t <- typeParser+ P.spaces+ n <- nameParser+ P.spaces+ d <- dataParser+ (prio, port, wei) <- recordAttributes t+ return $ DomainRecord 0 t n d prio port wei++ typeParser :: P.Parsec String s DNSType+ typeParser = P.choice [ rtype A , rtype CNAME , rtype TXT , rtype PTR , rtype SRV , rtype NS , rtype AAAA , rtype MX ]++ rtype :: DNSType -> P.Parsec String s DNSType+ rtype t = P.string (show t) >> return t++ nameParser :: P.Parsec String s String+ nameParser = P.many1 (P.lower P.<|> P.char '.')++ dataParser :: P.Parsec String s String+ dataParser = P.many1 (P.alphaNum P.<|> P.oneOf [ '.' ])++ recordAttributes :: DNSType -> P.Parsec String s (Maybe Int, Maybe Int, Maybe Int)+ recordAttributes SRV = (,,) <$>+ (Just <$> number) <*>+ (Just <$> number) <*>+ (Just <$> number)+ recordAttributes MX = (,,) <$>+ (Just <$> number) <*>+ pure Nothing <*>+ pure Nothing+ recordAttributes _ = (,,) <$>+ pure Nothing <*>+ pure Nothing <*>+ pure Nothing+ number :: P.Parsec String s Int+ number = P.spaces >> read <$> P.many1 P.digit++-- | Floating IPs+-- https://developers.digitalocean.com/documentation/v2/#floating-ips++data FloatingIP = FloatingIP { floatingIp :: IP+ , floatingDroplet :: Maybe Droplet+ , floatingRegion :: Region+ } deriving (Show)++instance FromJSON FloatingIP where+ parseJSON (Object o) = FloatingIP+ <$> o .: "ip"+ <*> o .:? "droplet"+ <*> o .: "region"++ parseJSON e = failParse e++data FloatingIPTarget = TargetRegion Slug+ | TargetDroplet Id+ deriving (Show)++instance ToJSON FloatingIPTarget where+ toJSON (TargetRegion r) = object [ "region" .= r ]+ toJSON (TargetDroplet i) = object [ "droplet_id" .= i ]++data IPAction = AssignIP Id+ | UnassignIP+ deriving (Show, Read)++instance ToJSON IPAction where+ toJSON (AssignIP did) = object [ "type" .= ("assign" :: String)+ , "droplet_id" .= did+ ]+ toJSON UnassignIP = object [ "type" .= ("unassign" :: String)]++data IPActionType = Assign+ | Unassign+ deriving (Show)++instance FromJSON IPActionType where+ parseJSON (String s) = case s of+ "assign_ip" -> return Assign+ "unassign_ip" -> return Unassign+ _ -> fail $ "unknown action type " ++ show s+ parseJSON v = failParse v+ failParse :: (Show a1, Monad m) => a1 -> m a failParse e = fail $ "cannot parse " <> show e
src/Network/REST.hs view
@@ -1,11 +1,9 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveFunctor, ScopedTypeVariables #-} -- | Functor and low-level mechanism to interact with a server using Wreq and JSON values.-module Network.REST(module Network.REST.Commands- ,module Network.REST.Wreq+module Network.REST(module M ,ssh) where -import Network.REST.Commands-import Network.REST.Wreq+import Network.REST.Commands as M+import Network.REST.Conduit as M -- TODO temporary kludge import Network.SSH
src/Network/REST/Commands.hs view
@@ -1,8 +1,7 @@-{-# LANGUAGE DeriveFunctor #-}-{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DeriveFunctor, OverloadedStrings, ScopedTypeVariables #-} -- | Functor and low-level mechanism to interact with a server using "REST" API. ----- This module exposes @Net@ functor which can be implemented using various stack.+-- This module exposes @REST@ functor which can be implemented using various stack. -- In particular, we provide @Network.REST.Wreq@ module as a wreq-based implementation -- and a test-only implementation which is used to check correctness of higher-level -- code, in particular in relation with JSON serialization.@@ -10,8 +9,14 @@ import Control.Monad.Trans.Free (FreeT (..), liftF) import Data.Aeson (Value)+import Data.Monoid+import Data.Text (Text, pack) import Network.URI (URI)-import Network.Wreq (Options)++data Options = Header Text [ Text ]++authorisation :: String -> Options+authorisation t = Header "Authorization" [ "Bearer " <> pack t] data REST a = Get URI (Value -> a) | Post URI Value (Maybe Value -> a)
+ src/Network/REST/Conduit.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | Implementation of @REST@ interface based on <https://haskell-lang.org/library/http-client http-conduit>+-- standard library+module Network.REST.Conduit where++import Control.Concurrent (threadDelay)+import Control.Monad.Trans.Free+import Data.Aeson (Value, decode, eitherDecode)+import Data.CaseInsensitive (mk)+import Data.Functor (void)+import Data.Text.Encoding (encodeUtf8)+import Network.HTTP.Simple+import Network.REST.Commands+import Network.URI++-- | An implementation of @REST@ functor based on http-client and @IO@+runConduit :: RESTT IO r -> IO r+runConduit r = do+ mr <- runFreeT r+ step mr+ where+ asString :: URI -> String+ asString = ($ "") . uriToString id++ step (Pure value) = return value++ step (Free (WaitFor delay message k)) = do+ putStrLn message+ threadDelay delay+ runConduit k++ step (Free (Get uri k)) = do+ request <- parseRequest $ "GET " ++ asString uri+ response <- httpJSON request+ let value = getResponseBody response :: Value+ runConduit (k value)++ step (Free (GetWith opts uri k)) = do+ request <- parseRequest $ "GET " ++ asString uri+ response <- httpJSON $ options opts request+ let value = getResponseBody response :: Value+ runConduit (k value)++ step (Free (DeleteWith opts uri k)) = do+ request <- parseRequest $ "DELETE " ++ asString uri+ void $ httpLBS $ options opts request+ runConduit k++ step (Free (Post uri val k)) = do+ request <- parseRequest $ "POST " ++ asString uri+ response <- httpLbs $ setRequestBodyJSON val request+ let value = decode $ getResponseBody response+ runConduit (k value)++ step (Free (PostWith opts uri val k)) = do+ request <- parseRequest $ "POST " ++ asString uri+ response <- httpLbs $ options opts $ setRequestBodyJSON val request+ let value :: Either String Value = eitherDecode $ getResponseBody response+ runConduit (k value)+++options :: Options -> Request -> Request+options (Header h c) = setRequestHeader (mk $ encodeUtf8 h) (map encodeUtf8 c)
− src/Network/REST/Wreq.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--- | Implementation of @Net@ interface based on <http://wreq.io wreq>-module Network.REST.Wreq where--import Control.Concurrent (threadDelay)-import Control.Lens ((^.))-import Control.Monad.Trans.Free-import Data.Aeson (Value, decode, eitherDecode)-import Data.ByteString.Lazy (ByteString)-import Network.REST.Commands-import Network.URI-import Network.Wreq---- | An implementation of @Net@ functor based on wreq and @IO@-runWreq :: RESTT IO r -> IO r-runWreq r = do- mr <- runFreeT r- step mr- where- asString :: URI -> String- asString = ($ "") . uriToString id-- step (Pure value) = return value-- step (Free (WaitFor delay message k)) = do- putStrLn message- threadDelay delay- runWreq k-- step (Free (Get uri k)) = do- b <- asJSON =<< get (asString uri) :: IO (Response Value)- let value = b ^. responseBody- runWreq (k value)-- step (Free (GetWith opts uri k)) = do- b <- asJSON =<< getWith opts (asString uri) :: IO (Response Value)- let value = b ^. responseBody- runWreq (k value)-- step (Free (DeleteWith opts uri k)) = do- _ <- deleteWith opts (asString uri) :: IO (Response ByteString)- runWreq k-- step (Free (Post uri val k)) = do- resp <- post (asString uri) val :: IO (Response ByteString)- let value :: Maybe Value = decode $ resp ^. responseBody- runWreq (k value)-- step (Free (PostWith opts uri val k)) = do- resp <- postWith opts (asString uri) val :: IO (Response ByteString)- let value :: Either String Value = eitherDecode $ resp ^. responseBody- runWreq (k value)--
stack.yaml view
@@ -1,4 +1,4 @@ flags: {} packages: - '.'-resolver: lts-7.3+resolver: lts-8.8