packages feed

hdo 0.4 → 0.5

raw patch · 20 files changed

+617/−123 lines, 20 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Network.DO: ResourceBackend :: ResourceType
+ Network.DO: ResourceDroplet :: ResourceType
+ Network.DO: ResourceVolume :: ResourceType
+ Network.DO: Tag :: TagName -> TagResources -> Tag
+ Network.DO: TagDroplets :: Int -> Maybe Droplet -> TagDroplets
+ Network.DO: TagPair :: Id -> ResourceType -> TagPair
+ Network.DO: TagPairs :: [TagPair] -> TagPairs
+ Network.DO: TagResources :: TagDroplets -> TagVolumes -> TagResources
+ Network.DO: TagVolumes :: Int -> Maybe Volume -> TagVolumes
+ Network.DO: Volume :: Id -> Region -> [Id] -> String -> String -> Int -> Date -> Volume
+ Network.DO: [tagDropletsCount] :: TagDroplets -> Int
+ Network.DO: [tagDropletsLastTagged] :: TagDroplets -> Maybe Droplet
+ Network.DO: [tagDroplets] :: TagResources -> TagDroplets
+ Network.DO: [tagName] :: Tag -> TagName
+ Network.DO: [tagPairResourceId] :: TagPair -> Id
+ Network.DO: [tagPairResourceType] :: TagPair -> ResourceType
+ Network.DO: [tagPairsResources] :: TagPairs -> [TagPair]
+ Network.DO: [tagResources] :: Tag -> TagResources
+ Network.DO: [tagVolumesCount] :: TagVolumes -> Int
+ Network.DO: [tagVolumesLastTagged] :: TagVolumes -> Maybe Volume
+ Network.DO: [tagVolumes] :: TagResources -> TagVolumes
+ Network.DO: [volumeCreatedAt] :: Volume -> Date
+ Network.DO: [volumeDescription] :: Volume -> String
+ Network.DO: [volumeDropletIds] :: Volume -> [Id]
+ Network.DO: [volumeId] :: Volume -> Id
+ Network.DO: [volumeName] :: Volume -> String
+ Network.DO: [volumeRegion] :: Volume -> Region
+ Network.DO: [volumeSizeGigaBytes] :: Volume -> Int
+ Network.DO: createTag :: (Monad w) => TagName -> Command w (Result Tag)
+ Network.DO: data ResourceType
+ Network.DO: data Tag
+ Network.DO: data TagDroplets
+ Network.DO: data TagPair
+ Network.DO: data TagPairs
+ Network.DO: data TagResources
+ Network.DO: data TagVolumes
+ Network.DO: data Volume
+ Network.DO: deleteTag :: (Monad w) => TagName -> Command w (Result ())
+ Network.DO: listTags :: (Monad w) => Command w (Result [Tag])
+ Network.DO: resourceTypes :: [String]
+ Network.DO: retrieveTag :: (Monad w) => TagName -> Command w (Result Tag)
+ Network.DO: tagResources :: (Monad w) => TagName -> TagPairs -> Command w (Result ())
+ Network.DO: toJsonNetwork :: (ToJSON netmask) => IP -> netmask -> IP -> NetType -> Value
+ Network.DO: type TagName = String
+ Network.DO: untagResources :: (Monad w) => TagName -> TagPairs -> Command w (Result ())
- Network.DO: createDroplet :: (Monad w) => BoxConfiguration -> Command w (Either Error Droplet)
+ Network.DO: createDroplet :: (Monad w) => BoxConfiguration -> Command w (Result Droplet)
- Network.DO: deleteDomain :: (Monad w) => DomainName -> Command w (Maybe String)
+ Network.DO: deleteDomain :: (Monad w) => DomainName -> Command w (Result ())
- Network.DO: deleteFloatingIP :: (Monad w) => IP -> Command w (Maybe String)
+ Network.DO: deleteFloatingIP :: (Monad w) => IP -> Command w (Result ())
- Network.DO: deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Maybe String)
+ Network.DO: deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Result ())
- Network.DO: destroyDroplet :: (Monad w) => Integer -> Command w (Maybe String)
+ Network.DO: destroyDroplet :: (Monad w) => Integer -> Command w (Result ())
- Network.DO: listDomains :: (Monad w) => Command w [Domain]
+ Network.DO: listDomains :: (Monad w) => Command w (Result [Domain])
- Network.DO: listDropletSnapshots :: (Monad w) => Id -> Command w [Image]
+ Network.DO: listDropletSnapshots :: (Monad w) => Id -> Command w (Result [Image])
- Network.DO: listDroplets :: (Monad w) => Command w [Droplet]
+ Network.DO: listDroplets :: (Monad w) => Command w (Result [Droplet])
- Network.DO: listFloatingIPs :: (Monad w) => Command w [FloatingIP]
+ Network.DO: listFloatingIPs :: (Monad w) => Command w (Result [FloatingIP])
- Network.DO: listImages :: (Monad w) => Command w [Image]
+ Network.DO: listImages :: (Monad w) => Command w (Result [Image])
- Network.DO: listKeys :: (Monad w) => Command w [Key]
+ Network.DO: listKeys :: (Monad w) => Command w (Result [Key])
- Network.DO: listRecords :: (Monad w) => DomainName -> Command w [DomainRecord]
+ Network.DO: listRecords :: (Monad w) => DomainName -> Command w (Result [DomainRecord])
- Network.DO: listRegions :: (Monad w) => Command w [Region]
+ Network.DO: listRegions :: (Monad w) => Command w (Result [Region])
- Network.DO: listSizes :: (Monad w) => Command w [Size]
+ Network.DO: listSizes :: (Monad w) => Command w (Result [Size])
- Network.DO: showDroplet :: (Monad w) => Integer -> Command w (Either Error Droplet)
+ Network.DO: showDroplet :: (Monad w) => Integer -> Command w (Result Droplet)
- Network.DO: type Command w a = FreeT (DO :+: (DropletCommands :+: (IPCommands :+: DomainCommands))) (RESTT w) a
+ Network.DO: type Command w a = FreeT (DO :+: (DropletCommands :+: (IPCommands :+: (DomainCommands :+: TagsCommands)))) (RESTT w) a

Files

hdo.cabal view
@@ -1,5 +1,5 @@ Name: hdo-Version: 0.4+Version: 0.5 Cabal-Version: >= 1.18 Maintainer: Arnaud Bailly <arnaud.oqube@gmail.com> Author: Arnaud Bailly@@ -26,9 +26,10 @@                  , 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.Tags, Network.DO.Tags.Commands, Network.DO.Tags.Net                  , Network.DO.Names, Network.DO.Pairing                  , Network.REST, Network.DO.Pretty-                 +   exposed-modules: Network.DO   Build-Depends: aeson                , base >= 4 && < 5
main/hdo.hs view
@@ -97,24 +97,28 @@                                                          = showDroplet (P.read dropletId) >>= outputResult parseCommandOptions ("droplets":"ssh":dropletIdOrName:[])                                                          =  (do-                                                                droplets <- findByIdOrName dropletIdOrName <$> listDroplets-                                                                case droplets of-                                                                 (did:_) -> dropletConsole did-                                                                 []      -> return (error $ "no droplet with id or name " <> dropletIdOrName)+                                                               droplets <- fmap (findByIdOrName dropletIdOrName) <$> listDroplets+                                                               case droplets of+                                                                 Right (did:_) -> dropletConsole did+                                                                 Right []      -> return $ error ("no droplet with id or name " <> dropletIdOrName)+                                                                 Left err      -> return $ Left err                                                             ) >>= outputResult+ 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+  regions <- either (const []) id <$> 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@@ -124,4 +128,11 @@     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 ("tags":"list":_) = listTags >>= outputResult+parseCommandOptions ("tags":"create":name:[]) = createTag name >>= outputResult+parseCommandOptions ("tags":"delete":name:[]) = deleteTag name >>= outputResult+parseCommandOptions ("tags":"tag":name:rid:rtype:[]) = tagResources name (TagPairs [TagPair (P.read rid) (P.read rtype)]) >>= outputResult+parseCommandOptions ("tags":"untag":name:rid:rtype:[]) = untagResources name (TagPairs [TagPair (P.read rid) (P.read rtype)]) >>= outputResult+ parseCommandOptions e                          = fail $ "I don't know how to interpret commands " ++ unwords e ++ "\n" ++ usage
src/Network/DO.hs view
@@ -14,6 +14,9 @@   -- * Domains Commands   listDomains, createDomain, deleteDomain,   listRecords, createRecord, deleteRecord,+  -- * Tags Commands+  listTags, createTag, retrieveTag, deleteTag,+  tagResources, untagResources,   -- * Utilities   runDOEnv, runDO, runDODebug, getAuthFromEnv, outputResult,   generateName,@@ -22,41 +25,43 @@ 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.DO.Types             hiding(name, tagResources) import           Network.REST import           System.Environment           (getEnv) import           System.IO.Error              (isDoesNotExistError)+import qualified Network.DO.Commands          as C+import qualified Network.DO.Domain            as C+import qualified Network.DO.Droplets.Commands as C+import qualified Network.DO.IP.Commands       as C+import qualified Network.DO.Tags.Commands     as C -type Command w a = FreeT (C.DO :+: C.DropletCommands :+: C.IPCommands :+: C.DomainCommands) (RESTT w) a+type Command w a = FreeT (C.DO :+: C.DropletCommands :+: C.IPCommands :+: C.DomainCommands :+: C.TagsCommands) (RESTT w) a -listKeys :: (Monad w) => Command w [Key]++listKeys :: (Monad w) => Command w (Result [Key]) listKeys = injl C.listKeys -listSizes :: (Monad w) => Command w [Size]+listSizes :: (Monad w) => Command w (Result [Size]) listSizes = injl C.listSizes -listImages  :: (Monad w) => Command w  [Image]+listImages  :: (Monad w) => Command w  (Result [Image]) listImages = injl C.listImages -listRegions :: (Monad w) => Command w [Region]+listRegions :: (Monad w) => Command w (Result [Region]) listRegions = injl C.listRegions -listFloatingIPs :: (Monad w) => Command w [FloatingIP]+listFloatingIPs :: (Monad w) => Command w (Result [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 :: (Monad w) => IP -> Command w (Result ()) deleteFloatingIP = injrrl . C.deleteFloatingIP  assignFloatingIP :: (Monad w) => IP -> Id -> Command w (Result (ActionResult IPActionType))@@ -65,34 +70,34 @@ 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+listDomains :: (Monad w) => Command w (Result [Domain])+listDomains = injrrrl C.listDomains  createDomain :: (Monad w) => DomainName -> IP -> Command w (Result Domain)-createDomain dname ip = injrrr $ C.createDomain dname ip+createDomain dname ip = injrrrl $ C.createDomain dname ip -deleteDomain :: (Monad w) => DomainName -> Command w (Maybe String)-deleteDomain = injrrr . C.deleteDomain+deleteDomain :: (Monad w) => DomainName -> Command w (Result ())+deleteDomain = injrrrl . C.deleteDomain -listRecords :: (Monad w) => DomainName -> Command w [DomainRecord]-listRecords = injrrr . C.listRecords+listRecords :: (Monad w) => DomainName -> Command w (Result [DomainRecord])+listRecords = injrrrl . C.listRecords  createRecord :: (Monad w) => DomainName -> DomainRecord -> Command w (Result DomainRecord)-createRecord dname ip = injrrr $ C.createRecord dname ip+createRecord dname ip = injrrrl $ C.createRecord dname ip -deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Maybe String)-deleteRecord dname rid = injrrr $ C.deleteRecord dname rid+deleteRecord :: (Monad w) => DomainName -> Id -> Command w (Result ())+deleteRecord dname rid = injrrrl $ C.deleteRecord dname rid -listDroplets :: (Monad w) => Command w [Droplet]+listDroplets :: (Monad w) => Command w (Result [Droplet]) listDroplets = injrl C.listDroplets -createDroplet :: (Monad w) => BoxConfiguration -> Command w (Either Error Droplet)+createDroplet :: (Monad w) => BoxConfiguration -> Command w (Result Droplet) createDroplet = injrl . C.createDroplet -showDroplet :: (Monad w) => Integer -> Command w (Either Error Droplet)+showDroplet :: (Monad w) => Integer -> Command w (Result Droplet) showDroplet = injrl . C.showDroplet -destroyDroplet :: (Monad w) => Integer -> Command w (Maybe String)+destroyDroplet :: (Monad w) => Integer -> Command w (Result ()) destroyDroplet = injrl . C.destroyDroplet  dropletAction :: (Monad w) => Id -> Action -> Command w (Result (ActionResult DropletActionType))@@ -104,8 +109,28 @@ getAction :: (Monad w) => Id -> Id -> Command w (Result (ActionResult DropletActionType)) getAction  did = injrl . C.getAction did -listDropletSnapshots :: (Monad w) => Id -> Command w [Image]+listDropletSnapshots :: (Monad w) => Id -> Command w (Result [Image]) listDropletSnapshots = injrl . C.listDropletSnapshots++listTags :: (Monad w) => Command w (Result [Tag])+listTags = injrrrr C.listTags++createTag :: (Monad w) => TagName -> Command w (Result Tag)+createTag = injrrrr . C.createTag++retrieveTag :: (Monad w) => TagName -> Command w (Result Tag)+retrieveTag = injrrrr . C.retrieveTag++deleteTag :: (Monad w) => TagName -> Command w (Result ())+deleteTag = injrrrr . C.deleteTag++tagResources :: (Monad w) => TagName -> TagPairs -> Command w (Result ())+tagResources name = injrrrr . C.tagResources name++untagResources :: (Monad w) => TagName -> TagPairs -> Command w (Result ())+untagResources name = injrrrr . C.untagResources name++  -- | Run DO actions, extracting authentication token from environment variable `AUTH_TOKEN`. runDOEnv :: Command IO a -> IO a
src/Network/DO/Commands.hs view
@@ -10,33 +10,33 @@ import           Prelude                      as P  -- functor for DO DSL-data DO a = ListKeys ([Key] -> a)-          | ListSizes ([Size] -> a)-          | ListImages ([Image] -> a)-          | ListRegions ([Region] -> a)+data DO a = ListKeys (Result [Key] -> a)+          | ListSizes (Result [Size] -> a)+          | ListImages (Result [Image] -> a)+          | ListRegions (Result [Region] -> a)           deriving (Functor)  -- free transformer to embed effects type DOT = FreeT DO  -- smart constructors-listKeys :: DO [Key]+listKeys :: DO (Result [Key]) listKeys = ListKeys P.id -listSizes :: DO [Size]+listSizes :: DO (Result [Size]) listSizes = ListSizes P.id -listImages  :: DO [Image]+listImages  :: DO (Result [Image]) listImages = ListImages P.id -listRegions :: DO [Region]+listRegions :: DO (Result [Region]) 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)-                     , listRegionsH :: (m [Region], k)+data CoDO m k = CoDO { listKeysH    :: (m (Result [Key]), k)+                     , listSizesH   :: (m (Result [Size]), k)+                     , listImagesH  :: (m (Result [Image]), k)+                     , listRegionsH :: (m (Result [Region]), k)                      } deriving Functor  -- Cofree closure of CoDO functor
src/Network/DO/Domain/Commands.hs view
@@ -10,43 +10,43 @@ import           Network.DO.Types import           Prelude                      as P -data DomainCommands a = ListDomains ([Domain] -> a)+data DomainCommands a = ListDomains (Result [Domain] -> a)                       | CreateDomain DomainName IP (Result Domain -> a)-                      | DeleteDomain DomainName (Maybe String -> a)-                      | ListRecords DomainName ([DomainRecord] -> a)+                      | DeleteDomain DomainName (Result () -> a)+                      | ListRecords DomainName (Result [DomainRecord] -> a)                       | CreateRecord DomainName DomainRecord (Result DomainRecord -> a)-                      | DeleteRecord DomainName Id (Maybe String -> a)+                      | DeleteRecord DomainName Id (Result () -> a)                       deriving (Functor)  type DomainCommandsT = FreeT DomainCommands  -- smart constructors-listDomains :: DomainCommands [Domain]+listDomains :: DomainCommands (Result [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 :: DomainName -> DomainCommands (Result ()) deleteDomain ip = DeleteDomain ip P.id -listRecords :: DomainName -> DomainCommands [DomainRecord]+listRecords :: DomainName -> DomainCommands (Result [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 :: DomainName -> Id -> DomainCommands (Result ()) deleteRecord dname rid = DeleteRecord dname rid P.id  -- dual type, for creating interpreters data CoDomainCommands m k =-  CoDomainCommands { listDomainsH  :: (m [Domain], k)+  CoDomainCommands { listDomainsH  :: (m (Result [Domain]), k)                    , createDomainH :: DomainName -> IP -> (m (Result Domain), k)-                   , deleteDomainH :: DomainName -> (m (Maybe String), k)-                   , listRecordsH  :: DomainName -> (m [DomainRecord], k)+                   , deleteDomainH :: DomainName -> (m (Result ()), k)+                   , listRecordsH  :: DomainName -> (m (Result [DomainRecord]), k)                    , createRecordH :: DomainName -> DomainRecord -> (m (Result DomainRecord), k)-                   , deleteRecordH :: DomainName -> Id -> (m (Maybe String), k)+                   , deleteRecordH :: DomainName -> Id -> (m (Result ()), k)                    } deriving Functor  -- Cofree closure of CoDomainCommands functor
src/Network/DO/Domain/Net.hs view
@@ -32,7 +32,7 @@   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)+doCreateDomain w name ip = maybe (errMissingToken, w)   runQuery   (authToken (ask w))   where@@ -43,21 +43,21 @@       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+doDeleteDomain :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> (RESTT m (Result ()), w a)+doDeleteDomain w name = maybe (errMissingToken, w)+                  (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ domainsEndpoint </> show name) (toJSON ()) >> return (Right ())                           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)+doListRecords :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> (RESTT m (Result [DomainRecord]), w a)+doListRecords w name = maybe (errMissingToken, 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)+  maybe (errMissingToken,  w)   runQuery   (authToken (ask w))   where@@ -65,10 +65,10 @@                      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 :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> DomainName -> Id -> (RESTT m (Result ()), 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+  maybe (errMissingToken, w)+  (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ domainsEndpoint </> show name </> "records" </> show rid ) (toJSON ()) >> return (Right ())           in (r, w))   (authToken (ask w)) 
src/Network/DO/Droplets/Commands.hs view
@@ -15,12 +15,12 @@ import           Prelude                      as P  -- | Available commands for droplets-data DropletCommands a = ListDroplets ([Droplet] -> a)+data DropletCommands a = ListDroplets (Result [Droplet] -> a)                        | CreateDroplet BoxConfiguration (Result Droplet -> a)-                       | DestroyDroplet Id (Maybe String -> a)+                       | DestroyDroplet Id (Result () -> a)                        | DropletAction Id Action (Result (ActionResult DropletActionType) -> a)                        | GetAction Id Id (Result (ActionResult DropletActionType) -> a)-                       | ListSnapshots Id ([Image] -> a)+                       | ListSnapshots Id (Result [Image] -> a)                        | Console Droplet (Result () -> a)                        | ShowDroplet Id (Result Droplet -> a)                        deriving (Functor)@@ -29,7 +29,7 @@ type DropletCommandsT = FreeT DropletCommands  -- smart constructors-listDroplets :: DropletCommands [Droplet]+listDroplets :: DropletCommands (Result [Droplet]) listDroplets = ListDroplets P.id  createDroplet :: BoxConfiguration -> DropletCommands (Result Droplet)@@ -38,7 +38,7 @@ showDroplet :: Id -> DropletCommands (Result Droplet) showDroplet did = ShowDroplet did P.id -destroyDroplet :: Id -> DropletCommands (Maybe String)+destroyDroplet :: Id -> DropletCommands (Result ()) destroyDroplet did = DestroyDroplet did P.id  dropletAction :: Id -> Action -> DropletCommands (Result (ActionResult DropletActionType))@@ -50,17 +50,17 @@ getAction :: Id -> Id -> DropletCommands (Result (ActionResult DropletActionType)) getAction did actId = GetAction did actId P.id -listDropletSnapshots :: Id -> DropletCommands [Image]+listDropletSnapshots :: Id -> DropletCommands (Result [Image]) listDropletSnapshots did = ListSnapshots did P.id   -- | Comonadic interpreter for @DropletCommands@-data CoDropletCommands m k = CoDropletCommands { listDropletsH   :: (m [Droplet], k)+data CoDropletCommands m k = CoDropletCommands { listDropletsH   :: (m (Result [Droplet]), k)                                                , createDropletH  :: BoxConfiguration -> (m (Result Droplet), k)-                                               , destroyDropletH :: Id -> (m (Maybe String), k)+                                               , destroyDropletH :: Id -> (m (Result ()), k)                                                , actionDropletH  :: Id -> Action -> (m (Result (ActionResult DropletActionType)), k)                                                , getActionH      :: Id -> Id -> (m (Result (ActionResult DropletActionType)), k)-                                               , listSnapshotsH  :: Id -> (m [Image], k)+                                               , listSnapshotsH  :: Id -> (m (Result [Image]), k)                                                , consoleH        :: Droplet -> (m (Result ()), k)                                                , showDropletH    :: Id -> (m (Result Droplet), k)                                                } deriving Functor
src/Network/DO/Droplets/Net.hs view
@@ -28,15 +28,15 @@   listEndpoint _ = dropletsEndpoint   listField _    = "droplets" -doListSnapshots :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> (RESTT m [Image], w a)+doListSnapshots :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> (RESTT m (Result [Image]), w a) doListSnapshots w dropletId =-  maybe (return [], w)+  maybe (errMissingToken, w)   (\ t -> let snapshots = toList "snapshots" <$> getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId </> "snapshots")           in (snapshots, w))   (authToken (ask w))  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)+doCreate w config = maybe (errMissingToken, w)   runQuery   (authToken (ask w))   where@@ -49,28 +49,28 @@                        err      -> return err                  in (droplets, w) -doDestroyDroplet :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> (RESTT m (Maybe String), w a)-doDestroyDroplet w dropletId = maybe (return $ Just "no authentication token defined", w)-                               (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId) >> return Nothing+doDestroyDroplet :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> Id -> (RESTT m (Result ()), w a)+doDestroyDroplet w dropletId = maybe (errMissingToken, w)+                               (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId) (toJSON ()) >> return (Right ())                                        in (r, w))                                (authToken (ask w))  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)+doAction w dropletId action = maybe (errMissingToken, w)                               (\ t -> let r = postJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId </> "actions") (toJSON action)                                               >>= return . fromResponse "action"                                       in (r, w))                               (authToken (ask w))  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)+doGetAction w dropletId actionId = maybe (errMissingToken, w)                                    (\ t -> let r = getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId </> "actions" </> show actionId)                                                    >>= 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)+doShowDroplet w dropletId = maybe (errMissingToken, w)                             (\ t -> let r = fromResponse "droplet" . Right <$> getJSONWith (authorisation t) (toURI $ dropletsEndpoint </> show dropletId)                                     in (r, w))                             (authToken (ask w))
src/Network/DO/IP/Commands.hs view
@@ -11,9 +11,9 @@ import           Prelude                      as P  -- functor for DO DSL-data IPCommands a = ListFloatingIPs ([FloatingIP] -> a)+data IPCommands a = ListFloatingIPs (Result [FloatingIP] -> a)                   | CreateIP FloatingIPTarget (Result FloatingIP -> a)-                  | DeleteIP IP (Maybe String -> a)+                  | DeleteIP IP (Result () -> a)                   | ActionIP IP IPAction (Result (ActionResult IPActionType) -> a)           deriving (Functor) @@ -21,13 +21,13 @@ type IPCommandsT = FreeT IPCommands  -- smart constructors-listFloatingIPs :: IPCommands [FloatingIP]+listFloatingIPs :: IPCommands (Result [FloatingIP]) listFloatingIPs = ListFloatingIPs P.id  createFloatingIP :: FloatingIPTarget -> IPCommands (Result FloatingIP) createFloatingIP target = CreateIP target P.id -deleteFloatingIP :: IP -> IPCommands (Maybe String)+deleteFloatingIP :: IP -> IPCommands (Result ()) deleteFloatingIP ip = DeleteIP ip P.id  floatingIPAction :: IP -> IPAction -> IPCommands (Result (ActionResult IPActionType))@@ -35,9 +35,9 @@  -- dual type, for creating interpreters data CoIPCommands m k =-  CoIPCommands { listFloatingIPsH  :: (m [FloatingIP], k)+  CoIPCommands { listFloatingIPsH  :: (m (Result [FloatingIP]), k)                , createFloatingIPH :: FloatingIPTarget -> (m (Result FloatingIP), k)-               , deleteIPH         :: IP -> (m (Maybe String), k)+               , deleteIPH         :: IP -> (m (Result ()), k)                , actionIPH         :: IP -> IPAction -> (m (Result (ActionResult IPActionType)), k)                } deriving Functor 
src/Network/DO/IP/Net.hs view
@@ -32,22 +32,22 @@   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)+doCreateIP w config = maybe (errMissingToken, w)   runQuery   (authToken (ask w))   where-    runQuery t = let opts             = authorisation t-                     ip               = postJSONWith opts (toURI floatingIpsEndpoint) (toJSON config) >>= return . fromResponse "floating_ip"+    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+doDeleteIP :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> IP -> (RESTT m (Result ()), w a)+doDeleteIP w ip = maybe (errMissingToken, w)+                  (\ t -> let r = deleteJSONWith (authorisation t) (toURI $ floatingIpsEndpoint </> show ip) (toJSON ()) >> return (Right ())                           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)+doAction w ip action = maybe (errMissingToken, w)                        (\ t -> let r = postJSONWith (authorisation t) (toURI $ floatingIpsEndpoint </> show ip </> "actions") (toJSON action)                                        >>= return . fromResponse "action"                                in (r, w))
src/Network/DO/Net.hs view
@@ -23,6 +23,7 @@ import           Network.DO.Droplets.Commands import           Network.DO.Droplets.Net import           Network.DO.IP+import           Network.DO.Tags import           Network.DO.Net.Common import           Network.DO.Pairing import           Network.DO.Types             as DO hiding (URI)@@ -79,7 +80,7 @@                   <*> queryList (Proxy :: Proxy Region)  mkDOClient :: (MonadIO m) => ToolConfiguration-           -> CofreeT (CoDO (RESTT m) :*: CoDropletCommands (RESTT m) :*: CoIPCommands (RESTT m) :*: CoDomainCommands (RESTT m)) (Env ToolConfiguration) (RESTT m ())+           -> CofreeT (CoDO (RESTT m) :*: CoDropletCommands (RESTT m) :*: CoIPCommands (RESTT m) :*: CoDomainCommands (RESTT m) :*: CoTagsCommands (RESTT m)) (Env ToolConfiguration) (RESTT m ()) mkDOClient config = coiterT next start   where     next = Pair@@ -88,7 +89,10 @@                  <$> dropletCommandsInterpreter                  <*> (Pair                       <$> ipCommandsInterpreter-                      <*> dnsCommandsInterpreter+                      <*> (Pair+                           <$> dnsCommandsInterpreter+                           <*> tagsCommandsInterpreter+                          )                      )                )     start = env config (return ())
src/Network/DO/Net/Common.hs view
@@ -27,22 +27,38 @@ toURI :: String -> URI 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 _  _         = []+toList :: (FromJSON a) => Text -> Value -> Result [a]+toList k (Object o) = if H.member "message" o+  then error . show $ o H.! "message"+  else case H.lookup k o of+    Just (Array boxes) ->+      Right $ mapMaybe (A.parseMaybe parseJSON) (V.toList boxes)+    _ ->+      error $ "cannot decode JSON value to a list of " ++ show k +toList k  _         =+  error $ "cannot decode JSON value to a list of " ++ show k+ class Listable a where   listEndpoint :: Proxy a -> String   listField :: Proxy a -> Text -queryList :: (ComonadEnv ToolConfiguration w, Monad m, Listable b, FromJSON b) => Proxy b -> w a -> (RESTT m [b], w a)-queryList p w = maybe (return [], w)-                (\ t -> let droplets = toList (listField p) <$> getJSONWith (authorisation t) (toURI (listEndpoint p))-                        in (droplets, w))+queryList :: (ComonadEnv ToolConfiguration w, Monad m, Listable b, FromJSON b) => Proxy b -> w a -> (RESTT m (Result [b]), w a)+queryList p w = maybe (errMissingToken, w)+                (\ t -> let resources = toList (listField p) <$> getJSONWith (authorisation t) (toURI (listEndpoint p))+                        in (resources, w))                 (authToken (ask w)) +errMissingToken :: (Monad m) => m (Result a)+errMissingToken = return $ error "no authentication token defined"+ -- |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+fromResponse key (Right (Object b)) = if H.member "message" b+  then error . show $ (b H.! "message")+  else case H.lookup key b of+    Just val -> either error Right $ A.parseEither parseJSON val+    _        -> error $ "cannot decode JSON value to a " ++ show key ++ ": " ++ show b++fromResponse key v =+  error $ "cannot decode JSON value to a " ++ show key ++ ": " ++ show v
src/Network/DO/Pairing.hs view
@@ -19,6 +19,7 @@                , injr, injl                , injrl, injrr                , injrrl, injrrr+               , injrrrl, injrrrr                ) where  import           Control.Comonad              (Comonad, extract)@@ -80,6 +81,13 @@  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++injrrrl :: (Monad m, Functor f, Functor g, Functor h, Functor k, Functor l) => k a -> FreeT (f :+: g :+: h :+: k :+: l) m a+injrrrl = liftF . InR . InR . InR . InL++injrrrr :: (Monad m, Functor f, Functor g, Functor h, Functor k, Functor l) => l a -> FreeT (f :+: g :+: h :+: k :+: l) m a+injrrrr = liftF . InR . 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
@@ -111,6 +111,18 @@  instance Pretty DNSType +instance Pretty Tag where+  pretty Tag{..} = text tagName $$+                   nest 5 (text "droplets" <+> (brackets $ int $ tagDropletsCount $ tagDroplets tagResources)) $$+                   nest 10 (maybe (text "<none>") pretty (tagDropletsLastTagged $ tagDroplets tagResources)) $$+                   nest 5 (text "volumes" <+> (brackets $ int $ tagVolumesCount $ tagVolumes tagResources))    $$+                   nest 10 (maybe (text "<none>") pretty (tagVolumesLastTagged $ tagVolumes tagResources))++instance Pretty Volume where+  pretty Volume{..} = integer volumeId $$+                      nest 5 (text volumeName <+> brackets (int volumeSizeGigaBytes <> text "GiB") <+> pretty volumeRegion) $$+                      nest 5 (hcat $ punctuate (char ',') $ integer <$> volumeDropletIds)+ outputResult :: (Pretty a, MonadIO m) => a -> m  () outputResult = liftIO . putStrLn . render . pretty 
+ src/Network/DO/Tags.hs view
@@ -0,0 +1,6 @@+module Network.DO.Tags(module Network.DO.Tags.Commands+                      ,module Network.DO.Tags.Net+                      ) where++import           Network.DO.Tags.Commands+import           Network.DO.Tags.Net
+ src/Network/DO/Tags/Commands.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}++module Network.DO.Tags.Commands(TagsCommands,+                                TagsCommandsT, CoTagsCommandsT,+                                CoTagsCommands(..),+                                TagName,+                                createTag, retrieveTag, listTags,+                                tagResources, untagResources,+                                deleteTag) where++import Prelude++import Control.Comonad.Trans.Cofree+import Control.Monad.Trans.Free+import Network.DO.Pairing+import Network.DO.Types hiding (TagResources, tagResources, name)++-- | Available commands for tags+data TagsCommands a = CreateTag TagName (Result Tag -> a)+                    | RetrieveTag TagName (Result Tag -> a)+                    | DeleteTag TagName (Result () -> a)+                    | ListTags (Result [Tag] -> a)+                    | TagResources TagName TagPairs (Result () -> a)+                    | UntagResources TagName TagPairs (Result () -> a)+                    deriving (Functor)++-- | free transformer to embed effects+type TagsCommandsT = FreeT TagsCommands++-- smart constructors+createTag :: TagName -> TagsCommands (Result Tag)+createTag name = CreateTag name Prelude.id++retrieveTag :: TagName -> TagsCommands (Result Tag)+retrieveTag name = RetrieveTag name Prelude.id++deleteTag :: TagName -> TagsCommands (Result ())+deleteTag name = DeleteTag name Prelude.id++listTags :: TagsCommands (Result [Tag])+listTags = ListTags Prelude.id++tagResources :: TagName -> TagPairs -> TagsCommands (Result ())+tagResources name pairs = TagResources name pairs Prelude.id++untagResources :: TagName -> TagPairs -> TagsCommands (Result ())+untagResources name pairs = TagResources name pairs Prelude.id++-- | Comonadic interpreter for @Tagscommands@+data CoTagsCommands m k = CoTagsCommands { createTagH      :: TagName -> (m (Result Tag), k)+                                         , retrieveTagH    :: TagName -> (m (Result Tag), k)+                                         , deleteTagH      :: TagName -> (m (Result ()), k)+                                         , listTagsH       :: (m (Result [Tag]), k)+                                         , tagResourcesH   :: TagName -> TagPairs -> (m (Result ()), k)+                                         , untagResourcesH :: TagName -> TagPairs -> (m (Result ()), k)+                                         } deriving Functor++-- | Cofree closure of CoTagsCommands functor+type CoTagsCommandsT m = CofreeT (CoTagsCommands m)++-- Pair DSL with interpreter within some monadic context+instance (Monad m) => PairingM (CoTagsCommands m) TagsCommands m where+  pairM f (CoTagsCommands create _ _ _ _ _)   (CreateTag name k)       = pairM f (create name) k+  pairM f (CoTagsCommands _ retrieve _ _ _ _) (RetrieveTag name k)     = pairM f (retrieve name) k+  pairM f (CoTagsCommands _ _ delete _ _ _)   (DeleteTag name k)       = pairM f (delete name) k+  pairM f (CoTagsCommands _ _ _ list _ _)     (ListTags k)             = pairM f list k+  pairM f (CoTagsCommands _ _ _ _ tag _)      (TagResources name pairs k)   = pairM f (tag name pairs) k+  pairM f (CoTagsCommands _ _ _ _ _ untag)    (UntagResources name pairs k) = pairM f (untag name pairs) k
+ src/Network/DO/Tags/Net.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE DoAndIfThenElse #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Network interpreter for Tags specific API+module Network.DO.Tags.Net(tagsCommandsInterpreter) where++import Prelude                      as P hiding (error)++import Control.Comonad.Env.Class    (ComonadEnv, ask)+import Control.Monad.Trans          (MonadIO)+import Data.Aeson                   as A hiding (Result, pairs)+import Data.Proxy+import Network.DO.Tags.Commands+import Network.DO.Net.Common+import Network.DO.Types             as DO hiding (URI, name)+import Network.REST+++-- | Resource identifier for tags+tagsURI :: String+tagsURI = "tags"++-- | Root endpoint for tags+tagsEndpoint :: String+tagsEndpoint = rootURI </> apiVersion </> tagsURI+++-- Derive the listTags endpoint for free, see @Network.DO.Net.Common@+--+-- https://developers.digitalocean.com/documentation/v2/#list-all-tags+--+instance Listable Tag where+  listEndpoint _ = tagsEndpoint+  listField    _ = "tags"++-- | Create a new Tag+--+-- https://developers.digitalocean.com/documentation/v2/#create-a-new-tag+--+doCreateTag :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> TagName -> (RESTT m (Result Tag), w a)+doCreateTag w name = maybe (errMissingToken, w) runQuery (authToken (ask w))+  where+    runQuery t = let opts  = authorisation t+                     body  = toJSON . object $ ["name" .= name]+                     query = fromResponse "tag" <$> postJSONWith opts (toURI tagsEndpoint) body+                 in (query, w)++-- | Retrieve a Tag+--+-- https://developers.digitalocean.com/documentation/v2/#retrieve-a-tag+--+doRetrieveTag :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> TagName -> (RESTT m (Result Tag), w a)+doRetrieveTag w name = maybe (errMissingToken, w) runQuery (authToken (ask w))+  where+    runQuery t = let opts  = authorisation t+                     query = fromResponse "tag" . Right <$> getJSONWith opts (toURI $ tagsEndpoint </> name)+                 in (query, w)++-- | Delete a Tag+--+-- https://developers.digitalocean.com/documentation/v2/#delete-a-tag+--+doDeleteTag :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> TagName -> (RESTT m (Result ()), w a)+doDeleteTag w name = maybe (errMissingToken, w) runQuery (authToken (ask w))+  where+    runQuery t = let opts  = authorisation t+                     body  = toJSON ()+                     query = Right () <$ deleteJSONWith opts (toURI $ tagsEndpoint </> name) body+                 in (query, w)++-- | Tag one or several other resources+--+-- https://developers.digitalocean.com/documentation/v2/#tag-a-resource+--+doTagResources :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> TagName -> TagPairs-> (RESTT m (Result ()), w a)+doTagResources w name pairs = maybe (errMissingToken, w) runQuery (authToken (ask w))+  where+    runQuery t = let opts  = authorisation t+                     body  = toJSON pairs+                     query = Right () <$ postJSONWith opts (toURI $ tagsEndpoint </> name </> "resources") body+                 in (query, w)++-- | Untag one or several other resources+--+-- https://developers.digitalocean.com/documentation/v2/#untag-a-resource+--+doUntagResources :: (ComonadEnv ToolConfiguration w, Monad m) => w a -> TagName -> TagPairs -> (RESTT m (Result ()), w a)+doUntagResources w name pairs = maybe (errMissingToken, w) runQuery (authToken (ask w))+  where+    runQuery t = let opts  = authorisation t+                     body  = toJSON pairs+                     query = Right () <$ deleteJSONWith opts (toURI $ tagsEndpoint </> name </> "resources") body+                 in (query, w)+++-- | DSL Interpreter for TagsCommands into IO via the REST DSL+tagsCommandsInterpreter :: (MonadIO m, ComonadEnv ToolConfiguration w) => w a -> CoTagsCommands (RESTT m) (w a)+tagsCommandsInterpreter = CoTagsCommands+                          <$> doCreateTag+                          <*> doRetrieveTag+                          <*> doDeleteTag+                          <*> queryList (Proxy :: Proxy Tag)+                          <*> doTagResources+                          <*> doUntagResources
src/Network/DO/Types.hs view
@@ -13,9 +13,10 @@ import           Data.Aeson        as A hiding (Error, Result) import           Data.Aeson.Types  as A hiding (Error, Result) import           Data.Default+import           Data.Maybe        (isNothing) import qualified Data.HashMap.Lazy as H import           Data.IP-import           Data.List         (elemIndex)+import           Data.List         (elemIndex, concat) import           Data.Monoid       ((<>)) import           Data.Text         (pack, unpack) import           Data.Time         (UTCTime)@@ -156,12 +157,19 @@   parseJSON (Number n) = jsonBytes (truncate n)   parseJSON e          = failParse e +instance ToJSON (Bytes a) where+  toJSON Bytes{..} = toJSON bytesSize++ newtype Date = Date { theDate :: UTCTime } deriving Show  instance FromJSON Date where   parseJSON d@(String _) = Date <$> parseJSON d   parseJSON e            = failParse e +instance ToJSON Date where+  toJSON Date{..} = toJSON theDate+ data Status = New             | Active             | Off@@ -170,13 +178,20 @@  instance FromJSON Status where   parseJSON (String s) = case s of-                          "new" -> return New-                          "active" -> return Active-                          "off" -> return Off-                          "archive" -> return Archive-                          _        -> fail $ "cannot parse " <> unpack s+                          "new"      -> return New+                          "active"   -> return Active+                          "off"      -> return Off+                          "archive"  -> return Archive+                          _          -> fail $ "cannot parse " <> unpack s   parseJSON e          = failParse e +instance ToJSON Status where+  toJSON New     = "new"+  toJSON Active  = "active"+  toJSON Off     = "off"+  toJSON Archive = "archive"++ data NetType = Public | Private deriving (Show, Eq)  -- | Type of a single Network definition@@ -209,6 +224,10 @@                           e         -> failParse e   parseJSON e          = failParse e +instance ToJSON NetType where+  toJSON Public  = "public"+  toJSON Private = "private"+ data V4 data V6 @@ -219,15 +238,30 @@                   <*> (n .: "gateway")                   <*> (n .: "type") +toJsonNetwork :: (ToJSON netmask) => IP -> netmask -> IP -> NetType -> Value+toJsonNetwork ip_address netmask gateway netType = object [ "ip_address" .= ip_address+                                                          , "netmask"    .= netmask+                                                          , "gateway"    .= gateway+                                                          , "type"       .= netType+                                                          ]+ instance FromJSON (Network V4) where   parseJSON (Object n) = jsonNetwork NetworkV4 n   parseJSON e          = failParse e +instance ToJSON (Network V4) where+  toJSON NetworkV4{..} = toJsonNetwork ip_address netmask gateway netType+  toJSON NetworkV6{..} = toJsonNetwork ip_address netmask_v6 gateway netType+ instance FromJSON (Network V6) where   parseJSON (Object n) = jsonNetwork NetworkV6 n   parseJSON e          = fail $ "cannot parse network v6 " <> show e +instance ToJSON (Network V6) where+  toJSON NetworkV4{..} = toJsonNetwork ip_address netmask gateway netType+  toJSON NetworkV6{..} = toJsonNetwork ip_address netmask_v6 gateway netType + -- | Type of Networks configured for a @Droplet@ -- -- A network is either a list of IPv4 and IPv6 NICs definitions, or no network. We need this@@ -246,6 +280,12 @@                               <*> (n .: "v6")   parseJSON e          = fail $ "cannot parse network v6 " <> show e +instance ToJSON Networks where+  toJSON NoNetworks = Null+  toJSON Networks{..} = object [ "v4" .= v4+                               , "v6" .= v6+                               ]+ -- | (Partial) Type of Droplets -- -- https://developers.digitalocean.com/documentation/v2/#droplets@@ -281,7 +321,23 @@                          <*> o .: "networks"   parseJSON e          = fail $ "cannot parse network v6 " <> show e +instance ToJSON Droplet where+  toJSON Droplet{..} = object [ "id"           .= dropletId+                              , "name"         .= name+                              , "memory"       .= memory+                              , "vcpus"        .= vcpus+                              , "disk"         .= disk+                              , "locked"       .= locked+                              , "created_at"   .= created_at+                              , "status"       .= status+                              , "backup_ids"   .= backup_ids+                              , "snapshot_ids" .= snapshot_ids+                              , "region"       .= region+                              , "size_slug"    .= size_slug+                              , "networks"     .= networks+                              ] + data ImageType = Snapshot                | Temporary                | Backup@@ -611,5 +667,183 @@   parseJSON v          = failParse v  +-- | Type of Resources+data ResourceType+  = ResourceDroplet+  | ResourceVolume+  | ResourceBackend+  deriving (Eq, Ord, Enum)++resourceTypes :: [String]+resourceTypes = ["droplet", "volume", "backend"]++instance Show ResourceType where+  show r = resourceTypes !! fromEnum r++instance Read ResourceType where+  readsPrec _ sz = case elemIndex sz resourceTypes of+                    Just i  -> return (toEnum i, "")+                    Nothing -> fail $ "cannot parse " <> sz++instance FromJSON ResourceType where+  parseJSON (String v) =+    case elemIndex (unpack v) resourceTypes of+      Just i ->+        return $ toEnum i+      Nothing ->+        failParse ("cannot parse " <> v)++  parseJSON e = failParse e++instance ToJSON ResourceType where+  toJSON = toJSON . show++-- | Type of Block Storage (Volume)+--+-- https://developers.digitalocean.com/documentation/v2/#block-storage+data Volume = Volume+  { volumeId            :: Id      -- ^ The unique identifier for the Block Storage Volume.+  , volumeRegion        :: Region  -- ^ The region that the Block Storage Volume is located in.+  , volumeDropletIds    :: [Id]    -- ^ An array containing the IDs of the Droplets the volume is attached to.+  , volumeName          :: String  -- ^ A human-redable name for the Block Storage Volume.+  , volumeDescription   :: String  -- ^ An optional free-form text field to describe a Block Storage Volume.+  , volumeSizeGigaBytes :: Int     -- ^ The size of the Block Storage Volume in GiB (1024^3)+  , volumeCreatedAt     :: Date    -- ^ A time value that represents when the Block Storage Volume was created.+  } deriving (Show)++instance FromJSON Volume where+  parseJSON (Object o) = Volume+                         <$> o .: "id"+                         <*> o .: "region"+                         <*> o .: "droplet_ids"+                         <*> o .: "name"+                         <*> o .: "description"+                         <*> o .: "size_gigabytes"+                         <*> o .: "created_at"++  parseJSON e          = failParse e++instance ToJSON Volume where+  toJSON Volume{..} = object [ "id"             .= volumeId+                             , "region"         .= volumeRegion+                             , "droplet_ids"    .= volumeDropletIds+                             , "name"           .= volumeName+                             , "description"    .= volumeDescription+                             , "size_gigabytes" .= volumeSizeGigaBytes+                             , "created_at"     .= volumeSizeGigaBytes+                             ]+++-- | Type of Tags+--+-- https://developers.digitalocean.com/documentation/v2/#tags++type TagName = String++data Tag = Tag+  { tagName      :: TagName       -- ^ The tag name+  , tagResources :: TagResources  -- ^ An embedded object containing key value pairs of resource type and resource statistics+  } deriving (Show)++data TagResources = TagResources+  { tagDroplets :: TagDroplets  -- ^ Statistics about the droplets resources+  , tagVolumes  :: TagVolumes   -- ^ Statistics about the volumes resources+  -- NOTE backend resources seem to exist too but I can't find any representation of them :|+  } deriving (Show)++data TagDroplets = TagDroplets+  { tagDropletsCount      :: Int            -- ^ The number of tagged droplets+  , tagDropletsLastTagged :: Maybe Droplet  -- ^ The last tagged droplet+  } deriving (Show)++data TagVolumes = TagVolumes+  { tagVolumesCount      :: Int           -- ^ The number of tagged volumes+  , tagVolumesLastTagged :: Maybe Volume  -- ^ The last tagged volume+  } deriving (Show)++data TagPairs = TagPairs+  { tagPairsResources :: [TagPair] -- ^ An array of objects containing resource_id and resource_type attributes+  } deriving (Show)++data TagPair = TagPair+  { tagPairResourceId   :: Id            -- ^ The identifier of a resource+  , tagPairResourceType :: ResourceType  -- ^ The type of the resource+  } deriving (Show)+++instance FromJSON Tag where+  parseJSON (Object o) = Tag+                         <$> o .: "name"+                         <*> o .: "resources"++  parseJSON e          = failParse e++instance FromJSON TagResources where+  parseJSON (Object o) = TagResources+                         <$> o .:? "droplets" .!= TagDroplets 0 Nothing+                         <*> o .:? "volumes"  .!= TagVolumes  0 Nothing++  parseJSON e          = failParse e++instance FromJSON TagDroplets where+  parseJSON (Object o) = TagDroplets+                         <$> o .:  "count"+                         <*> o .:? "last_tagged"++  parseJSON e          = failParse e++instance FromJSON TagVolumes where+  parseJSON (Object o) = TagVolumes+                         <$> o .:  "count"+                         <*> o .:? "last_tagged"++  parseJSON e          = failParse e++instance FromJSON TagPairs where+  parseJSON (Object o) = TagPairs+                         <$> o .: "resources"++  parseJSON e         = failParse e++instance FromJSON TagPair where+  parseJSON (Object o) = TagPair+                         <$> o .: "resource_id"+                         <*> o .: "resource_type"++  parseJSON e          = failParse e++instance ToJSON Tag where+  toJSON Tag{..} = object [ "name"      .= tagName+                          , "resources" .= tagResources+                          ]++instance ToJSON TagResources where+  toJSON TagResources{..} = object $ concat+    [ if isNothing (tagDropletsLastTagged tagDroplets) then [] else ["droplets" .= tagDroplets]+    , if isNothing (tagVolumesLastTagged tagVolumes)   then [] else ["volumes"  .= tagVolumes]+    ]++instance ToJSON TagDroplets where+  toJSON TagDroplets{..} = object [ "count"       .= tagDropletsCount+                                  , "last_tagged" .= tagDropletsLastTagged+                                  ]++instance ToJSON TagVolumes where+  toJSON TagVolumes{..} = object [ "count"       .= tagVolumesCount+                                 , "last_tagged" .= tagVolumesLastTagged+                                 ]++instance ToJSON TagPairs where+  toJSON TagPairs{..} = object [ "resouces" .= tagPairsResources+                               ]++instance ToJSON TagPair where+  toJSON TagPair{..} = object [ "resource_id"   .= tagPairResourceId+                              , "resource_type" .= tagPairResourceType+                              ]+++-- Utility+-- failParse :: (Show a1, Monad m) => a1 -> m a failParse e = fail $ "cannot parse " <> show e
src/Network/REST/Commands.hs view
@@ -23,7 +23,7 @@            | WaitFor Int String a            | GetWith Options URI (Value -> a)            | PostWith Options URI Value (Either String Value -> a)-           | DeleteWith Options URI a+           | DeleteWith Options URI Value a            deriving (Functor)  type RESTT = FreeT REST@@ -41,8 +41,8 @@ postJSONWith :: (Monad m) => Options ->  URI -> Value -> RESTT m (Either String Value) postJSONWith opts uri json = liftF $ PostWith opts uri json id -deleteJSONWith :: (Monad m) => Options -> URI -> RESTT m ()-deleteJSONWith opts uri = liftF $ DeleteWith opts uri ()+deleteJSONWith :: (Monad m) => Options -> URI -> Value -> RESTT m ()+deleteJSONWith opts uri json = liftF $ DeleteWith opts uri json ()  waitFor :: (Monad m) => Int -> String -> RESTT m () waitFor delay message = liftF $ WaitFor delay message ()
src/Network/REST/Conduit.hs view
@@ -70,10 +70,10 @@         let value = getResponseBody response :: Value         runConduit debug (k value) -      step (Free (DeleteWith opts uri k)) = do+      step (Free (DeleteWith opts uri val k)) = do         request <- parseRequest $ "DELETE " ++ asString uri-        debugRequest debug (RequestLog request)-        void $ httpLBS $ options opts request+        debugRequest debug (RequestLogWithBody request (Body val))+        void $ httpLBS $ options opts $ setRequestBodyJSON val request         runConduit debug k        step (Free (Post uri val k)) = do