digitalocean-kzs (empty) → 0.1.0.0
raw patch · 15 files changed
+701/−0 lines, 15 filesdep +HTTPdep +aesondep +basesetup-changed
Dependencies added: HTTP, aeson, base, bytestring, digitalocean-kzs, doctest, hspec, http-conduit, http-types, text, transformers
Files
- LICENSE +20/−0
- Setup.hs +2/−0
- digitalocean-kzs.cabal +113/−0
- src/DigitalOcean.hs +23/−0
- src/DigitalOcean/Account.hs +56/−0
- src/DigitalOcean/Base.hs +56/−0
- src/DigitalOcean/Droplet.hs +67/−0
- src/DigitalOcean/Image.hs +79/−0
- src/DigitalOcean/NewDroplet.hs +78/−0
- src/DigitalOcean/Region.hs +56/−0
- src/DigitalOcean/SSHKey.hs +55/−0
- src/DigitalOcean/Size.hs +63/−0
- test/DigitalOceanSpec.hs +11/−0
- test/Spec.hs +1/−0
- test/doctests.hs +21/−0
+ LICENSE view
@@ -0,0 +1,20 @@+The MIT License (MIT)++Copyright (c) 2014 Ryotaro NAKAMURA++Permission is hereby granted, free of charge, to any person obtaining a copy of+this software and associated documentation files (the "Software"), to deal in+the Software without restriction, including without limitation the rights to+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of+the Software, and to permit persons to whom the Software is furnished to do so,+subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ digitalocean-kzs.cabal view
@@ -0,0 +1,113 @@+-- Initial digitalocean-kzs.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/++-- The name of the package.+name: digitalocean-kzs++-- The package version. See the Haskell package versioning policy (PVP) +-- for standards guiding when and how versions should be incremented.+-- http://www.haskell.org/haskellwiki/Package_versioning_policy+-- PVP summary: +-+------- breaking API changes+-- | | +----- non-breaking API additions+-- | | | +--- code changes with no API change+version: 0.1.0.0++-- A short (one-line) description of the package.+synopsis: digitalocean api for haskell+description: DigitalOcean api for haskell+ +-- A longer description of the package.+-- description: ++-- URL for the project homepage or repository.+homepage: https://github.com/KazumaSATO/digitalocean-kzs++-- The license under which the package is released.+license: MIT++-- The file containing the license text.+license-file: LICENSE++-- The package author(s).+author: Ryotaro NAKAMURA++-- An email address to which users can send suggestions, bug reports, and +-- patches.+maintainer: r.nakamura1989@gmail.com++-- A copyright notice.+-- copyright: ++category: Web++build-type: Simple++-- Extra files to be distributed with the package, such as examples or a +-- README.+-- extra-source-files: ++-- Constraint on the version of Cabal needed to build this package.+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/KazumaSATO/digitalocean-kzs+ +library+ + -- Modules exported by the library. + exposed-modules:+ DigitalOcean+ DigitalOcean.Account+ DigitalOcean.Base+ DigitalOcean.Image+ DigitalOcean.Droplet+ DigitalOcean.Region+ DigitalOcean.SSHKey+ DigitalOcean.Size+ DigitalOcean.NewDroplet+ + -- Modules included in this library but not exported. + -- other-modules:+ + -- LANGUAGE extensions used by modules in this package. + -- other-extensions:++ + -- Other library packages from which modules are imported.+ build-depends:+ base >=4.7 && <4.8,+ aeson >= 0.6 && < 0.9,+ text >= 0.10 && < 2,+ bytestring >= 0.10 && < 0.11,+ transformers >= 0.3 && < 0.5,+ HTTP >= 4000 && < 4001,+ http-conduit >= 2.1 && < 2.2,+ http-types >= 0.8.6+ + -- Directories containing source files.+ hs-source-dirs: src+ Ghc-Options: -Wall+ -- Base language which the package is written in. + default-language: Haskell2010++Test-Suite spec+ Type: exitcode-stdio-1.0+ Default-Language: Haskell2010+ Hs-Source-Dirs: test+ Ghc-Options: -Wall+ Main-Is: Spec.hs+ Other-Modules: DigitalOceanSpec+ Build-Depends: base+ , hspec >= 1.3+ , digitalocean-kzs++Test-Suite doctest+ Type: exitcode-stdio-1.0+ Default-Language: Haskell2010+ HS-Source-Dirs: test+ Ghc-Options: -threaded -Wall+ Main-Is: doctests.hs+ Build-Depends: base+ , doctest >= 0.9.3+
+ src/DigitalOcean.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module DigitalOcean (+ module DigitalOcean.Base,+ module DigitalOcean.Account,+ module DigitalOcean.Image,+ module DigitalOcean.Droplet,+ module DigitalOcean.Region,+ module DigitalOcean.Size,+ module DigitalOcean.SSHKey,+ module DigitalOcean.NewDroplet+ ) where+import DigitalOcean.Base+import DigitalOcean.Account+import DigitalOcean.Droplet+import DigitalOcean.Image+import DigitalOcean.Region+import DigitalOcean.Size+import DigitalOcean.SSHKey+import DigitalOcean.NewDroplet+
+ src/DigitalOcean/Account.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module DigitalOcean.Account (+ Account(..),+ account+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)++import DigitalOcean.Base(Authentication, requestGet)++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)++data Account = Account {+ dropletLimit :: Integer,+ email :: String,+ uuid :: String,+ emailVerified :: Bool+} deriving (Show, Read)++newtype RawAccount = RawAccount Account++instance FromJSON RawAccount where+ parseJSON (Object v) = RawAccount <$> v .: "account"+ parseJSON _ = mzero++instance FromJSON Account where+ parseJSON (Object v) =+ Account <$>+ (v .: "droplet_limit") <*>+ (v .: "email") <*>+ (v .: "uuid") <*>+ (v .: "email_verified")+ parseJSON _ = mzero++-- | Get User Information+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- mayBeAcnt <- account $ Authentication tkn+-- print $ show $ isJust mayBeAcnt+-- :}+-- "True"+account :: Authentication -> (MonadIO m) => m (Maybe Account)+account a = liftM toAccount $ liftM decode (requestGet "account" a)++toAccount :: Maybe RawAccount -> Maybe Account+toAccount = \ra -> case ra of Just(RawAccount a) -> Just a+ Nothing -> Nothing
+ src/DigitalOcean/Base.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++module DigitalOcean.Base (+ Authentication(..),+ requestGet,+ requestPost+ ) where++import Control.Applicative ((<$>))++import Control.Monad.IO.Class+import Data.Aeson ( ToJSON, encode)+import qualified Data.ByteString.Lazy.Char8 as BS+import Data.ByteString.Lazy(ByteString)+import Network.HTTP.Conduit+import Network.HTTP.Types.Header+import Data.ByteString.Char8(pack)+import Network.HTTP.Types.Method(methodPost, methodGet, Method)++-- Authentication "api token"+data Authentication = Authentication {+ token :: String+} deriving (Show)+++url :: String+url = "https://api.digitalocean.com/v2"++simpleHttp' :: MonadIO m => String -> Authentication -> Method -> Maybe(ByteString) -> m BS.ByteString+simpleHttp' url' auth reqMethod reqBody = liftIO $ withManager $ \man -> do + initReq <- liftIO $ parseUrl url'+ let req = initReq {+ method = reqMethod,+ requestHeaders = [(hContentType, Data.ByteString.Char8.pack "application/json"),+ (hAuthorization, Data.ByteString.Char8.pack $ "Bearer " ++ (token auth))],+ responseTimeout = Just 10000000, -- 10 seconds+ requestBody = case reqBody of Just b -> RequestBodyLBS b+ Nothing -> requestBody initReq+ }+ responseBody <$> httpLbs req man+++--dumpP :: (ToJSON j) => String -> Authentication -> j -> (MonadIO m) => m (BS.ByteString)+--dumpP suburl auth body = simpleHttp' (url ++ "/" ++ suburl) auth methodPost $ Just (encode body)++--dumpG :: String -> Authentication -> (MonadIO m) => m (BS.ByteString)+--dumpG suburl auth = simpleHttp' (url ++ "/" ++ suburl) auth methodGet Nothing++requestGet :: String -> Authentication -> (MonadIO m) => m BS.ByteString+requestGet suburl auth = simpleHttp' (url ++ "/" ++ suburl) auth methodGet Nothing+++requestPost :: (ToJSON b) => String -> Authentication -> b -> (MonadIO m) => m BS.ByteString+requestPost suburl auth body = simpleHttp' (url ++ "/" ++ suburl) auth methodPost $ Just (encode body)
+ src/DigitalOcean/Droplet.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module DigitalOcean.Droplet(+ Droplet(..),+ droplets+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)+import DigitalOcean.Base++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)++data Droplet = Droplet {+ did :: Integer,+ dname :: String,+ dmemory :: Integer,+ dvcpus :: Integer,+ disk :: Integer,+ dlocked :: Bool,+ createdAtv2 :: String,+ dstatus :: String,+ backupIds :: [Integer]+} deriving (Show, Read)++newtype Droplets = Droplets [Droplet]++instance FromJSON Droplets where+ parseJSON (Object v) = Droplets <$> v.: "droplets"+ parseJSON _ = mzero+ ++instance FromJSON Droplet where+ parseJSON (Object v) =+ Droplet <$>+ (v .: "id") <*>+ (v .: "name") <*>+ (v .: "memory") <*>+ (v .: "vcpus") <*>+ (v .: "disk") <*>+ (v .: "locked") <*>+ (v .: "created_at") <*>+ (v .: "status") <*>+ (v .: "backup_ids")+ parseJSON _ = mzero+++-- | List all Droplets+-- :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeDroplets <- droplets $ Authentication tkn+-- print $ show $ isJust maybeDroplets+-- :}+-- "True"+droplets :: Authentication -> (MonadIO m) => m (Maybe [Droplet])+droplets a = liftM toList $ liftM decode (requestGet "droplets?page=1&per_page=100" a)++toList :: Maybe Droplets -> Maybe [Droplet]+toList = \ds -> case ds of Just(Droplets l) -> Just l+ Nothing -> Nothing
+ src/DigitalOcean/Image.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE OverloadedStrings #-}++module DigitalOcean.Image(+ Image(..),+ images,+ listAllDistributionImages+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)+import DigitalOcean.Base(Authentication, requestGet)++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)+++data Image = Image {+ imgId :: Integer,+ imgName :: String,+ imgDistribution :: String,+ imgType :: String,+ imgSlug :: Maybe String,+ imgPublic :: Bool,+ imgRegions :: [String],+ imgMinDiskSize :: Integer,+ imgCreatedAt :: String+} deriving(Show, Read)++-- | List all Images+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeImages <- images $ Authentication tkn+-- print $ show $ isJust maybeImages+-- :}+-- "True"+images :: Authentication -> (MonadIO m) => m (Maybe [Image])+images a = liftM toList $ liftM decode (requestGet "images?page=1&per_page=1" a)++-- | List all Distribution Images+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeImages <- listAllDistributionImages $ Authentication tkn+-- print $ show $ isJust maybeImages+-- :}+-- "True"+listAllDistributionImages :: Authentication -> (MonadIO m) => m (Maybe [Image])+listAllDistributionImages a = liftM toList $ liftM decode (requestGet "images?page=1&per_page=100&type=distribution" a)++toList :: Maybe Images -> Maybe [Image]+toList = \ds -> case ds of Just(Images l) -> Just l+ Nothing -> Nothing+++newtype Images = Images [Image]++instance FromJSON Images where+ parseJSON (Object v) = Images <$> v.: "images"+ parseJSON _ = mzero++instance FromJSON Image where+ parseJSON (Object v) =+ Image <$>+ (v .: "id") <*>+ (v .: "name") <*>+ (v .: "distribution") <*>+ (v .: "type") <*>+ (v .: "slug") <*>+ (v .: "public") <*>+ (v .: "regions") <*>+ (v .: "min_disk_size") <*>+ (v .: "created_at")+ parseJSON _ = mzero
+ src/DigitalOcean/NewDroplet.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+module DigitalOcean.NewDroplet(+ newDroplet+) where++import Control.Applicative ((<$>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), ToJSON(..), (.=), object, decode)++import DigitalOcean.Droplet(Droplet(..))+import DigitalOcean.Base++-- $setup+-- >>> {-# LANGUAGE OverloadedStrings #-}+-- >>> import Data.ByteString.Char8+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)+++instance ToJSON NewDropletRequest where+ toJSON v = object ["name" .= ndname v,+ "region" .= region v,+ "size" .= size v,+ "image" .= image v,+ "ssh_keys" .= ndsshKeys v,+ "backups" .= backups v,+ "ipv6" .= ipv6 v,+ "user_data" .= userData v,+ "private_networking" .= privateNetworking v+ ]++data NewDropletRequest = NewDropletRequest {+ ndname :: String,+ region :: String, + size :: String,+ image :: Integer,+ ndsshKeys :: [String],+ backups :: Bool,+ ipv6 :: Bool,+ userData :: Maybe(String),+ privateNetworking :: Bool+} deriving (Show, Read)++newtype OuterDroplet = OuterDroplet Droplet deriving(Show)++instance FromJSON OuterDroplet where+ parseJSON (Object v) = OuterDroplet <$> v .: "droplet"+ parseJSON _ = mzero+++-- | create new Droplet+-- :{+-- do+-- let d = NewDropletRequest {+-- ndname="hoge",+-- region="nyc3",+-- size="512mb",+-- image=12241402,+-- ndsshKeys=[],+-- backups = False,+-- ipv6=True,+-- userData = Nothing,+-- privateNetworking=False+-- }+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeNewDroplet <- newDroplet ( Authentication tkn) d+-- print $ show $ isJust maybeNewDroplet +-- :}+-- "True"+newDroplet :: Authentication -> NewDropletRequest -> (MonadIO m) => m (Maybe Droplet)+newDroplet a d = liftM toDroplet $ liftM decode (requestPost "droplets" a d)++toDroplet :: Maybe OuterDroplet -> Maybe Droplet+toDroplet = \od -> case od of Just(OuterDroplet d) -> Just d+ Nothing -> Nothing
+ src/DigitalOcean/Region.hs view
@@ -0,0 +1,56 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE OverloadedStrings #-}++module DigitalOcean.Region(+ Region(..),+ regions+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)+import DigitalOcean.Base(Authentication, requestGet)++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)++data Region = Region {+ regionName :: String,+ slug :: String,+ available :: Bool+} deriving (Show, Read)+++-- | List all Regions+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeRegions <- regions $ Authentication tkn+-- print $ show $ isJust maybeRegions+-- :}+-- "True"+regions :: Authentication -> (MonadIO m) => m (Maybe [Region])+regions a = liftM toList $ liftM decode (requestGet "regions" a)+++newtype Regions = Regions [Region]++instance FromJSON Regions where+ parseJSON (Object v) = Regions <$> v.: "regions"+ parseJSON _ = mzero++instance FromJSON Region where+ parseJSON (Object v) =+ Region <$>+ (v .: "name") <*>+ (v .: "slug") <*>+ (v .: "available")+ parseJSON _ = mzero+ ++toList :: Maybe Regions -> Maybe [Region]+toList = \rs -> case rs of Just(Regions l) -> Just l+ Nothing -> Nothing
+ src/DigitalOcean/SSHKey.hs view
@@ -0,0 +1,55 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE OverloadedStrings #-}++module DigitalOcean.SSHKey (+ SSHKey(..),+ listAllKeys+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)+import DigitalOcean.Base(Authentication, requestGet)++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)++data SSHKey = SSHKey {+ id :: Integer,+ fingerprint :: String,+ publicKey :: String,+ sshkeyName :: String+} deriving (Show, Read)++newtype SSHKeys = SSHKeys [SSHKey]++instance FromJSON SSHKeys where+ parseJSON (Object v) = SSHKeys <$> v .: "ssh_keys"+ parseJSON _ = mzero++instance FromJSON SSHKey where+ parseJSON (Object v) =+ SSHKey <$>+ (v .: "id") <*>+ (v .: "fingerprint") <*>+ (v .: "public_key") <*>+ (v .: "name")+ parseJSON _ = mzero++-- | List all Keys+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeKeys <- listAllKeys $ Authentication tkn+-- print $ show $ isJust maybeKeys+-- :}+-- "True"+listAllKeys :: Authentication -> (MonadIO m) => m (Maybe [SSHKey])+listAllKeys a = liftM toList $ liftM decode (requestGet "account/keys" a)++toList :: Maybe SSHKeys -> Maybe [SSHKey]+toList = \sks -> case sks of Just(SSHKeys l) -> Just l+ Nothing -> Nothing
+ src/DigitalOcean/Size.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}++module DigitalOcean.Size(+ Size(..),+ sizes+ ) where++import Control.Applicative ((<$>), (<*>))+import Control.Monad (liftM, mzero)+import Control.Monad.IO.Class+import Data.Aeson ((.:), FromJSON(..), Value(..), decode)+import DigitalOcean.Base(Authentication, requestGet)++-- $setup+-- >>> import System.Environment+-- >>> import DigitalOcean.Base(Authentication(..))+-- >>> import Data.Maybe(isJust)++data Size = Size {+ sizSlug :: String,+ sizAvailable :: Bool,+ sizTransfer :: Integer,+ sizPriceMonthly :: Float,+ sizHourly :: Float,+ sizMemory :: Integer,+ sizVcpus :: Integer,+ sizDisk :: Integer+} deriving(Show, Read)++newtype Sizes = Sizes [Size]++instance FromJSON Sizes where+ parseJSON (Object v) = Sizes <$> v.: "sizes"+ parseJSON _ = mzero++-- | List all Sizes+-- >>> :{+-- do+-- tkn <- getEnv "DIGITAL_OCEAN_PERSONAL_ACCESS_TOKEN"+-- maybeSizes <- sizes $ Authentication tkn+-- print $ show $ isJust maybeSizes+-- :}+-- "True"+sizes :: Authentication -> (MonadIO m) => m (Maybe [Size])+sizes a = liftM toList $ liftM decode (requestGet "sizes" a)++instance FromJSON Size where+ parseJSON (Object v) =+ Size <$>+ (v .: "slug") <*>+ (v .: "available") <*>+ (v .: "transfer") <*>+ (v .: "price_monthly") <*>+ (v .: "price_hourly") <*>+ (v .: "memory") <*>+ (v .: "vcpus") <*>+ (v .: "disk")+ parseJSON _ = mzero+ +toList :: Maybe Sizes -> Maybe [Size]+toList = \ss -> case ss of Just(Sizes l) -> Just l+ Nothing -> Nothing
+ test/DigitalOceanSpec.hs view
@@ -0,0 +1,11 @@+module DigitalOceanSpec where++import Test.Hspec++spec :: Spec+spec = do+ describe "TODO" $ do+ it "is empty" $ do+ "foo" `shouldBe` "foo"+ +
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/doctests.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Test.DocTest++main :: IO ()+main = do+ doctest ["-isrc", "src/DigitalOcean.hs"]+{-+ doctest [+ "-isrc",+ "src/DigitalOcean/Droplet.hs",+ "src/DigitalOcean/Account.hs",+ "src/DigitalOcean/SSHKey.hs",+ "src/DigitalOcean/Image.hs",+ "src/DigitalOcean/Region.hs",+ "src/DigitalOcean/Size.hs"+-}+-- "src/DigitalOcean/NewDroplet.hs"+-- ]