hetzner 0.1.1.0 → 0.1.2.0
raw patch · 4 files changed
+204/−5 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Hetzner.Cloud: LoadBalancerID :: Int -> LoadBalancerID
+ Hetzner.Cloud: Network :: ZonedTime -> NetworkID -> IPv4Range -> LabelMap -> [LoadBalancerID] -> Text -> [Route] -> [ServerID] -> [Subnet] -> Network
+ Hetzner.Cloud: NetworkID :: Int -> NetworkID
+ Hetzner.Cloud: NewNetwork :: IPv4Range -> [Label] -> Text -> [Route] -> [Subnet] -> NewNetwork
+ Hetzner.Cloud: Route :: IPv4Range -> IPv4 -> Route
+ Hetzner.Cloud: Subnet :: IPv4 -> IPv4Range -> Region -> SubnetType -> Subnet
+ Hetzner.Cloud: SubnetCloud :: SubnetType
+ Hetzner.Cloud: SubnetServer :: SubnetType
+ Hetzner.Cloud: SubnetVSwitch :: SubnetType
+ Hetzner.Cloud: [networkCreated] :: Network -> ZonedTime
+ Hetzner.Cloud: [networkID] :: Network -> NetworkID
+ Hetzner.Cloud: [networkIPRange] :: Network -> IPv4Range
+ Hetzner.Cloud: [networkLabels] :: Network -> LabelMap
+ Hetzner.Cloud: [networkLoadBalancers] :: Network -> [LoadBalancerID]
+ Hetzner.Cloud: [networkName] :: Network -> Text
+ Hetzner.Cloud: [networkRoutes] :: Network -> [Route]
+ Hetzner.Cloud: [networkServers] :: Network -> [ServerID]
+ Hetzner.Cloud: [networkSubnets] :: Network -> [Subnet]
+ Hetzner.Cloud: [newNetworkIPRange] :: NewNetwork -> IPv4Range
+ Hetzner.Cloud: [newNetworkLabels] :: NewNetwork -> [Label]
+ Hetzner.Cloud: [newNetworkName] :: NewNetwork -> Text
+ Hetzner.Cloud: [newNetworkRoutes] :: NewNetwork -> [Route]
+ Hetzner.Cloud: [newNetworkSubnets] :: NewNetwork -> [Subnet]
+ Hetzner.Cloud: [routeDestination] :: Route -> IPv4Range
+ Hetzner.Cloud: [routeGateway] :: Route -> IPv4
+ Hetzner.Cloud: [subnetGateway] :: Subnet -> IPv4
+ Hetzner.Cloud: [subnetIPRange] :: Subnet -> IPv4Range
+ Hetzner.Cloud: [subnetRegion] :: Subnet -> Region
+ Hetzner.Cloud: [subnetType] :: Subnet -> SubnetType
+ Hetzner.Cloud: createNetwork :: Token -> NewNetwork -> IO Network
+ Hetzner.Cloud: data Network
+ Hetzner.Cloud: data NewNetwork
+ Hetzner.Cloud: data Route
+ Hetzner.Cloud: data Subnet
+ Hetzner.Cloud: data SubnetType
+ Hetzner.Cloud: defaultNewNetwork :: Text -> IPv4Range -> NewNetwork
+ Hetzner.Cloud: deleteNetwork :: Token -> NetworkID -> IO ()
+ Hetzner.Cloud: getNetwork :: Token -> NetworkID -> IO Network
+ Hetzner.Cloud: getNetworks :: Token -> Maybe Int -> IO (WithMeta "networks" [Network])
+ Hetzner.Cloud: getTokenFromEnv :: IO (Maybe Token)
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.LoadBalancerID
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.Network
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.NetworkID
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.Route
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.Subnet
+ Hetzner.Cloud: instance Data.Aeson.Types.FromJSON.FromJSON Hetzner.Cloud.SubnetType
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.LoadBalancerID
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.NetworkID
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.NewNetwork
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.Route
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.Subnet
+ Hetzner.Cloud: instance Data.Aeson.Types.ToJSON.ToJSON Hetzner.Cloud.SubnetType
+ Hetzner.Cloud: instance Data.String.IsString Hetzner.Cloud.Token
+ Hetzner.Cloud: instance GHC.Classes.Eq Hetzner.Cloud.LoadBalancerID
+ Hetzner.Cloud: instance GHC.Classes.Eq Hetzner.Cloud.NetworkID
+ Hetzner.Cloud: instance GHC.Classes.Eq Hetzner.Cloud.SubnetType
+ Hetzner.Cloud: instance GHC.Classes.Eq Hetzner.Cloud.Token
+ Hetzner.Cloud: instance GHC.Classes.Ord Hetzner.Cloud.LoadBalancerID
+ Hetzner.Cloud: instance GHC.Classes.Ord Hetzner.Cloud.NetworkID
+ Hetzner.Cloud: instance GHC.Classes.Ord Hetzner.Cloud.Token
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.LoadBalancerID
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.Network
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.NetworkID
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.Route
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.Subnet
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.SubnetType
+ Hetzner.Cloud: instance GHC.Show.Show Hetzner.Cloud.Token
+ Hetzner.Cloud: newtype LoadBalancerID
+ Hetzner.Cloud: newtype NetworkID
+ Hetzner.Cloud: updateNetwork :: Token -> NetworkID -> Text -> [Label] -> IO Network
Files
- changelog.md +6/−0
- hetzner.cabal +1/−1
- readme.md +6/−0
- src/Hetzner/Cloud.hs +191/−4
changelog.md view
@@ -1,3 +1,9 @@+## 0.1.2.0+* New function: `getTokenFromEnv`. This function allows the user+ to obtain a token from the `HETZNER_API_TOKEN` environment variable.+* Support for (private) networks.+* New instances for the `Token` type: `IsString`, `Show`, `Eq`, `Ord`.+ ## 0.1.1.0 * Added support for volumes. * Fixed parsing of HTTP 204 responses.
hetzner.cabal view
@@ -2,7 +2,7 @@ category: Cloud synopsis: Hetzner Cloud client library. description: Hetzner Cloud client library. Check the readme and documentation for more details.-version: 0.1.1.0+version: 0.1.2.0 cabal-version: >= 1.10 build-type: Simple author: Daniel Casanueva (daniel.casanueva `at` proton.me)
readme.md view
@@ -1,6 +1,12 @@ # Hetzner Cloud Haskell Library +[](https://github.com/Daniel-Diaz/hetzner/actions/workflows/build.yml)++## Distributions+ [](https://hackage.haskell.org/package/hetzner)+[](http://stackage.org/lts/package/hetzner)+[](http://stackage.org/nightly/package/hetzner) ## Description
src/Hetzner/Cloud.hs view
@@ -37,6 +37,7 @@ module Hetzner.Cloud ( -- * Tokens Token (..)+ , getTokenFromEnv -- * Server metadata , Metadata (..) , getMetadata@@ -71,12 +72,27 @@ , Image (..) , getImages , getImage+ -- ** Load Balancers+ , LoadBalancerID (..) -- ** Locations , City (..) , LocationID (..) , Location (..) , getLocations , getLocation+ -- ** Networks+ , NetworkID (..)+ , Route (..)+ , SubnetType (..)+ , Subnet (..)+ , Network (..)+ , NewNetwork (..)+ , defaultNewNetwork+ , getNetworks+ , getNetwork+ , createNetwork+ , deleteNetwork+ , updateNetwork -- ** Pricing , Price (..) , PriceInLocation (..)@@ -159,15 +175,16 @@ import Control.Concurrent (threadDelay) import GHC.TypeLits (Symbol, KnownSymbol, symbolVal) import Data.Proxy-import Data.String (fromString)+import Data.String (IsString, fromString) import GHC.Fingerprint (Fingerprint (..)) import Data.Void import Control.Applicative (liftA2) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Foldable (forM_) import Data.Maybe (isNothing, fromMaybe)+import System.Environment qualified as System -- ip-import Net.IPv4 (IPv4)+import Net.IPv4 (IPv4, IPv4Range) import Net.IPv6 (IPv6, IPv6Range) -- bytestring import Data.ByteString (ByteString)@@ -208,8 +225,15 @@ -- will have as scope the project where the token was made. -- -- You can obtain one through the [Hetzner Cloud Console](https://console.hetzner.cloud).-newtype Token = Token ByteString+newtype Token = Token ByteString deriving (Show, Eq, Ord) +instance IsString Token where+ fromString = Token . fromString++-- | Lookup 'Token' from the environment variable @HETZNER_API_TOKEN@.+getTokenFromEnv :: IO (Maybe Token)+getTokenFromEnv = fmap fromString <$> System.lookupEnv "HETZNER_API_TOKEN"+ -- | An error returned by Hetzner. data Error = Error { -- | Error code.@@ -533,7 +557,7 @@ instance (KnownSymbol key, FromJSON a) => FromJSON (WithKey key a) where parseJSON = let key = symbolVal (Proxy @key)- in JSON.withObject ("WithKey " ++ key) $ \o ->+ in JSON.withObject ("WithKey:" ++ key) $ \o -> WithKey <$> o .: fromString key -- | A value together with response metadata.@@ -828,6 +852,13 @@ cloudQuery "GET" ("/images/" <> fromString (show i)) noBody token Nothing ----------------------------------------------------------------------------------------------------+-- Load Balancers+----------------------------------------------------------------------------------------------------++-- | Load balancer identifier+newtype LoadBalancerID = LoadBalancerID Int deriving (Eq, Ord, Show, FromJSON, ToJSON)++---------------------------------------------------------------------------------------------------- -- Locations ---------------------------------------------------------------------------------------------------- @@ -884,6 +915,162 @@ getLocation :: Token -> LocationID -> IO Location getLocation token (LocationID i) = withoutKey @"location" <$> cloudQuery "GET" ("/locations/" <> fromString (show i)) noBody token Nothing++----------------------------------------------------------------------------------------------------+-- Networks+----------------------------------------------------------------------------------------------------++-- | Network identifier.+newtype NetworkID = NetworkID Int deriving (Eq, Ord, Show, FromJSON, ToJSON)++-- | A route that sends all packets for a given destination to+-- a given gateway.+data Route = Route+ { routeDestination :: IPv4Range+ , routeGateway :: IPv4+ } deriving Show++instance FromJSON Route where+ parseJSON = JSON.withObject "Route" $ \o -> Route+ <$> o .: "destination"+ <*> o .: "gateway"++instance ToJSON Route where+ toJSON route = JSON.object+ [ "destination" .= routeDestination route+ , "gateway" .= routeGateway route+ ]++-- | Types of subnetworks.+data SubnetType = SubnetCloud | SubnetServer | SubnetVSwitch deriving (Eq, Show)++instance FromJSON SubnetType where+ parseJSON = JSON.withText "SubnetType" $ \t -> case t of+ "cloud" -> pure SubnetCloud+ "server" -> pure SubnetServer+ "vswitch" -> pure SubnetVSwitch+ _ -> fail $ "Invalid subnet type: " ++ Text.unpack t++instance ToJSON SubnetType where+ toJSON t = case t of+ SubnetCloud -> "cloud"+ SubnetServer -> "server"+ SubnetVSwitch -> "vswitch"++-- | Subnets divide the IP range of a parent 'Network'.+data Subnet = Subnet+ { subnetGateway :: IPv4+ , subnetIPRange :: IPv4Range+ , subnetRegion :: Region+ , subnetType :: SubnetType+ } deriving Show++instance FromJSON Subnet where+ parseJSON = JSON.withObject "Subnet" $ \o -> Subnet+ <$> o .: "gateway"+ <*> o .: "ip_range"+ <*> o .: "network_zone"+ <*> o .: "type"++instance ToJSON Subnet where+ toJSON subnet = JSON.object+ [ "gateway" .= subnetGateway subnet+ , "ip_range" .= subnetIPRange subnet+ , "network_zone" .= subnetRegion subnet+ , "type" .= subnetType subnet+ ]++-- | A private network.+data Network = Network+ { networkCreated :: ZonedTime+ , networkID :: NetworkID+ , networkIPRange :: IPv4Range+ , networkLabels :: LabelMap+ , networkLoadBalancers :: [LoadBalancerID]+ , networkName :: Text+ , networkRoutes :: [Route]+ , networkServers :: [ServerID]+ , networkSubnets :: [Subnet]+ } deriving Show++instance FromJSON Network where+ parseJSON = JSON.withObject "Network" $ \o -> Network+ <$> o .: "created"+ <*> o .: "id"+ <*> o .: "ip_range"+ <*> o .: "labels"+ <*> o .: "load_balancers"+ <*> o .: "name"+ <*> o .: "routes"+ <*> o .: "servers"+ <*> o .: "subnets"++-- | Network creation configuration to be used with 'createNetwork'.+data NewNetwork = NewNetwork+ { newNetworkIPRange :: IPv4Range+ , newNetworkLabels :: [Label]+ , newNetworkName :: Text+ , newNetworkRoutes :: [Route]+ , newNetworkSubnets :: [Subnet]+ }++instance ToJSON NewNetwork where+ toJSON nnetwork = JSON.object+ [ "ip_range" .= newNetworkIPRange nnetwork+ , "labels" .= toLabelMap (newNetworkLabels nnetwork)+ , "name" .= newNetworkName nnetwork+ , "routes" .= newNetworkRoutes nnetwork+ , "subnets" .= newNetworkSubnets nnetwork+ ]++-- | Default network configuration for new networks.+defaultNewNetwork+ :: Text -- ^ Network name.+ -> IPv4Range -- ^ IP range of the network.+ -> NewNetwork+defaultNewNetwork name iprange = NewNetwork+ { newNetworkIPRange = iprange+ , newNetworkLabels = []+ , newNetworkName = name+ , newNetworkRoutes = []+ , newNetworkSubnets = []+ }++-- | Get networks.+getNetworks+ :: Token+ -> Maybe Int -- ^ Page.+ -> IO (WithMeta "networks" [Network])+getNetworks = cloudQuery "GET" "/networks" noBody++-- | Get a single network.+getNetwork :: Token -> NetworkID -> IO Network+getNetwork token (NetworkID i) = withoutKey @"network" <$>+ cloudQuery "GET" ("/networks/" <> fromString (show i)) noBody token Nothing++-- | Create a new network.+createNetwork :: Token -> NewNetwork -> IO Network+createNetwork token new = withoutKey @"network" <$>+ cloudQuery "POST" "/networks" (Just new) token Nothing++-- | Delete a network.+deleteNetwork :: Token -> NetworkID -> IO ()+deleteNetwork token (NetworkID i) =+ cloudQuery "DELETE" ("/networks/" <> fromString (show i)) noBody token Nothing++-- | Update name and labels of a network.+updateNetwork+ :: Token+ -> NetworkID -- ^ Network to update.+ -> Text -- ^ New name for the network.+ -> [Label] -- ^ New labels for the network.+ -> IO Network+updateNetwork token (NetworkID i) name labels = withoutKey @"network" <$>+ let body = JSON.object+ [ "labels" .= toLabelMap labels+ , "name" .= name+ ]+ in cloudQuery "PUT" ("/networks/" <> fromString (show i)) (Just body) token Nothing ---------------------------------------------------------------------------------------------------- -- Pricing