From ed4b056fd3c90eb5336b8d164857b05a902cd51e Mon Sep 17 00:00:00 2001 From: Vehbi Sinan Tunalioglu Date: Fri, 26 Apr 2024 22:15:46 +0800 Subject: [PATCH] feat: add list command to dump servers as per given configuration --- .hlint.yaml | 1 + package.yaml | 1 + src/Clompse/Cli.hs | 27 +++++ src/Clompse/Programs/ListServers.hs | 93 ++++++++++++++++ src/Clompse/Providers/Aws.hs | 157 +++++++++++++++++----------- src/Clompse/Providers/Do.hs | 26 +++++ src/Clompse/Providers/Hetzner.hs | 79 +++++++++----- 7 files changed, 297 insertions(+), 87 deletions(-) create mode 100644 src/Clompse/Programs/ListServers.hs diff --git a/.hlint.yaml b/.hlint.yaml index 07b737b..0d60aef 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -38,6 +38,7 @@ - QuasiQuotes - RecordWildCards - TemplateHaskell + - TupleSections - TypeApplications ################ diff --git a/package.yaml b/package.yaml index 55beb7b..2cf8a63 100644 --- a/package.yaml +++ b/package.yaml @@ -24,6 +24,7 @@ library: dependencies: - aeson - amazonka + - amazonka-core - amazonka-ec2 - amazonka-lightsail - autodocodec diff --git a/src/Clompse/Cli.hs b/src/Clompse/Cli.hs index 24c8fda..c95cf45 100644 --- a/src/Clompse/Cli.hs +++ b/src/Clompse/Cli.hs @@ -7,6 +7,7 @@ module Clompse.Cli where import qualified Autodocodec.Schema as ADC.Schema import Clompse.Config (Config, readConfigFile) import qualified Clompse.Meta as Meta +import qualified Clompse.Programs.ListServers as Programs import Control.Applicative ((<**>), (<|>)) import Control.Monad (join) import qualified Data.Aeson as Aeson @@ -40,6 +41,7 @@ cli = optProgram :: OA.Parser (IO ExitCode) optProgram = commandConfig + <|> commandList <|> commandVersion @@ -92,6 +94,31 @@ doConfigPrint fp = do Right cfg -> BLC.putStrLn (Aeson.encode cfg) >> pure ExitSuccess +-- ** list + + +-- | Definition for @list@ CLI command. +commandList :: OA.Parser (IO ExitCode) +commandList = OA.hsubparser (OA.command "list" (OA.info parser infomod) <> OA.metavar "list") + where + infomod = OA.fullDesc <> infoModHeader <> OA.progDesc "List servers." <> OA.footer "This command lists servers." + parser = + doList + <$> OA.strOption (OA.short 'c' <> OA.long "config" <> OA.metavar "CONFIG" <> OA.help "Configuration file to use.") + + +-- | @list@ CLI command program. +doList :: FilePath -> IO ExitCode +doList fp = do + eCfg <- readConfigFile fp + case eCfg of + Left err -> TIO.putStrLn ("Error reading configuration: " <> err) >> pure (ExitFailure 1) + Right cfg -> do + servers <- Programs.listServers cfg + BLC.putStrLn (Aeson.encode servers) + pure ExitSuccess + + -- ** version diff --git a/src/Clompse/Programs/ListServers.hs b/src/Clompse/Programs/ListServers.hs new file mode 100644 index 0000000..dcbe320 --- /dev/null +++ b/src/Clompse/Programs/ListServers.hs @@ -0,0 +1,93 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingVia #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} + +module Clompse.Programs.ListServers where + +import qualified Autodocodec as ADC +import Clompse.Config (CloudConnection (..), CloudProfile (..), Config (..)) +import qualified Clompse.Providers.Aws as Providers +import qualified Clompse.Providers.Aws as Providers.Aws +import qualified Clompse.Providers.Do as Providers.Do +import qualified Clompse.Providers.Hetzner as Providers.Hetzner +import Clompse.Types (Server) +import Control.Monad.Except (runExceptT) +import Control.Monad.IO.Class (MonadIO (..)) +import qualified Data.Aeson as Aeson +import qualified Data.Text as T +import qualified Data.Text.IO as TIO +import GHC.Generics (Generic) +import qualified System.IO +import qualified Zamazingo.Text as Z.Text + + +data ListServersResult = ListServersResult + { _listServersResultProfile :: !T.Text + , _listServersResultServers :: ![Server] + } + deriving (Eq, Generic, Show) + deriving (Aeson.FromJSON, Aeson.ToJSON) via (ADC.Autodocodec ListServersResult) + + +instance ADC.HasCodec ListServersResult where + codec = + _codec ADC. "List Servers Result" + where + _codec = + ADC.object "ListServersResult" $ + ListServersResult + <$> ADC.requiredField "profile" "Name of the cloud profile." ADC..= _listServersResultProfile + <*> ADC.requiredField "servers" "List of servers." ADC..= _listServersResultServers + + +listServers + :: MonadIO m + => Config + -> m [ListServersResult] +listServers Config {..} = + mapM listServersForCloudProfile _configCloudProfiles + + +listServersForCloudProfile + :: MonadIO m + => CloudProfile + -> m ListServersResult +listServersForCloudProfile CloudProfile {..} = do + _log ("Running Profile: " <> _cloudProfileName) + ListServersResult _cloudProfileName . concat <$> mapM listServersForCloudConnection _cloudProfileConnections + + +listServersForCloudConnection + :: MonadIO m + => CloudConnection + -> m [Server] +listServersForCloudConnection (CloudConnectionAws conn) = do + _log " Running AWS EC2..." + eServersEc2 <- runExceptT (Providers.Aws.awsEc2ListAllInstances conn) + serversEc2 <- case eServersEc2 of + Left e -> _log (" ERROR: " <> Z.Text.tshow e) >> pure [] + Right servers -> pure servers + _log " Running AWS Lightsail..." + eServersLightsail <- runExceptT (Providers.Aws.awsLightsailListAllInstances conn) + serversLightsail <- case eServersLightsail of + Left e -> _log (" ERROR: " <> Z.Text.tshow e) >> pure [] + Right servers -> pure servers + pure (fmap (uncurry Providers.ec2InstanceToServer) serversEc2 <> fmap (uncurry Providers.lightsailInstanceToServer) serversLightsail) +listServersForCloudConnection (CloudConnectionDo conn) = do + _log " Running DigitalOcean..." + eServers <- runExceptT (Providers.Do.doListDroplets conn) + case eServers of + Left e -> _log (" ERROR: " <> Z.Text.tshow e) >> pure [] + Right servers -> pure (fmap Providers.Do.toServer servers) +listServersForCloudConnection (CloudConnectionHetzner conn) = do + _log " Running Hetzner..." + eServers <- runExceptT (Providers.Hetzner.hetznerListServers conn) + case eServers of + Left e -> _log (" ERROR: " <> Z.Text.tshow e) >> pure [] + Right servers -> pure (fmap Providers.Hetzner.toServer servers) + + +_log :: MonadIO m => T.Text -> m () +_log = + liftIO . TIO.hPutStrLn System.IO.stderr diff --git a/src/Clompse/Providers/Aws.hs b/src/Clompse/Providers/Aws.hs index 21ed487..2a07d79 100644 --- a/src/Clompse/Providers/Aws.hs +++ b/src/Clompse/Providers/Aws.hs @@ -4,31 +4,37 @@ {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TupleSections #-} module Clompse.Providers.Aws where import qualified Amazonka as Aws import qualified Amazonka.Auth as Aws.Auth import qualified Amazonka.Data as Aws.Data +import qualified Amazonka.Data.Time as Aws.Data.Time import qualified Amazonka.EC2 as Aws.Ec2 import qualified Amazonka.EC2.Lens as Aws.Ec2.Lens import qualified Amazonka.EC2.Types as Aws.Ec2.Types +import qualified Amazonka.EC2.Types.CpuOptions as Aws.Ec2.Types.CpuOptions import qualified Amazonka.Lightsail as Aws.Lightsail import qualified Amazonka.Lightsail.Lens as Aws.Lightsail.Lens +import qualified Amazonka.Lightsail.Types as Aws.Lightsail.Types +import qualified Amazonka.Lightsail.Types.Disk as Aws.Lightsail.Types.Disk import qualified Autodocodec as ADC +import qualified Clompse.Types as Types import Conduit ((.|)) -import Control.Applicative ((<|>)) import qualified Control.Lens as L import Control.Monad.Except (MonadError (throwError)) import Control.Monad.IO.Class (MonadIO (liftIO)) import qualified Data.Aeson as Aeson import qualified Data.Conduit as C import qualified Data.Conduit.List as CL +import Data.Int (Int16, Int32) import qualified Data.List as L import Data.Maybe (catMaybes, fromMaybe) import qualified Data.Text as T import qualified Data.Text.Encoding as TE -import qualified Data.Text.IO as TIO +import GHC.Float (double2Int) import GHC.Generics (Generic) import qualified Zamazingo.Text as Z.Text @@ -116,7 +122,7 @@ awsEc2ListAllInstances :: MonadIO m => MonadError AwsError m => AwsConnection - -> m [Aws.Ec2.Instance] + -> m [(Aws.Region, Aws.Ec2.Instance)] awsEc2ListAllInstances cfg = do regions <- awsEc2ListAllRegions cfg concat <$> mapM (awsEc2ListAllInstancesForRegion cfg) regions @@ -127,7 +133,7 @@ awsEc2ListAllInstancesForRegion => MonadError AwsError m => AwsConnection -> Aws.Region - -> m [Aws.Ec2.Instance] + -> m [(Aws.Region, Aws.Ec2.Instance)] awsEc2ListAllInstancesForRegion cfg reg = do env <- (\x -> x {Aws.region = reg}) <$> _envFromConnection cfg let prog = @@ -135,7 +141,7 @@ awsEc2ListAllInstancesForRegion cfg reg = do .| CL.concatMap (L.view $ Aws.Ec2.Lens.describeInstancesResponse_reservations . L._Just) .| CL.concatMap (L.view $ Aws.Ec2.Lens.reservation_instances . L._Just) .| CL.consume - liftIO . Aws.runResourceT . C.runConduit $ prog + fmap (fmap (reg,)) . liftIO . Aws.runResourceT . C.runConduit $ prog -- *** Security Groups @@ -173,11 +179,11 @@ awsEc2ListAllInstancesWithSecurityGroups :: MonadIO m => MonadError AwsError m => AwsConnection - -> m [(Aws.Ec2.Instance, [Aws.Ec2.SecurityGroup])] + -> m [(Aws.Region, Aws.Ec2.Instance, [Aws.Ec2.SecurityGroup])] awsEc2ListAllInstancesWithSecurityGroups cfg = do - instances <- awsEc2ListAllInstances cfg + instancesWithRegions <- awsEc2ListAllInstances cfg securityGroups <- awsEc2ListAllSecurityGroups cfg - pure (fmap (\i -> (i, findSecurityGroups securityGroups i)) instances) + pure (fmap (\(r, i) -> (r, i, findSecurityGroups securityGroups i)) instancesWithRegions) where findSecurityGroups sgs i = let sids = catMaybes $ foldMap (fmap (L.^. Aws.Ec2.Lens.groupIdentifier_groupId)) (i L.^. Aws.Ec2.Lens.instance_securityGroups) @@ -230,7 +236,7 @@ awsLightsailListAllInstances :: MonadIO m => MonadError AwsError m => AwsConnection - -> m [Aws.Lightsail.Instance] + -> m [(Aws.Region, Aws.Lightsail.Instance)] awsLightsailListAllInstances cfg = do regions <- awsLightsailListAllRegions cfg concat <$> mapM (awsLightsailListAllInstancesForRegion cfg) regions @@ -241,80 +247,113 @@ awsLightsailListAllInstancesForRegion => MonadError AwsError m => AwsConnection -> Aws.Region - -> m [Aws.Lightsail.Instance] + -> m [(Aws.Region, Aws.Lightsail.Instance)] awsLightsailListAllInstancesForRegion cfg reg = do env <- (\x -> x {Aws.region = reg}) <$> _envFromConnection cfg let prog = Aws.paginate env Aws.Lightsail.newGetInstances .| CL.concatMap (L.view $ Aws.Lightsail.Lens.getInstancesResponse_instances . L._Just) .| CL.consume - liftIO . Aws.runResourceT . C.runConduit $ prog + fmap (fmap (reg,)) . liftIO . Aws.runResourceT . C.runConduit $ prog -- * Helpers -_envFromConnection - :: MonadIO m - => AwsConnection - -> m Aws.Env -_envFromConnection AwsConnection {..} = - Aws.newEnv (pure . Aws.Auth.fromKeys accessKeyId secretAccessKey) - where - accessKeyId = Aws.AccessKey (TE.encodeUtf8 _awsConnectionAccessKeyId) - secretAccessKey = Aws.SecretKey (TE.encodeUtf8 _awsConnectionSecretAccessKey) +-- ** EC2 -printAwsEc2InstanceWithSecurityGroup - :: MonadIO m - => (Aws.Ec2.Instance, [Aws.Ec2.SecurityGroup]) - -> m () -printAwsEc2InstanceWithSecurityGroup (i, sgs) = - let name = awsEc2InstanceName i - secs = T.intercalate " " (fmap awsEc2SecurityToText sgs) - in liftIO $ TIO.putStrLn (name <> ": " <> secs) +ec2InstanceToServer :: Aws.Region -> Aws.Ec2.Instance -> Types.Server +ec2InstanceToServer region i@Aws.Ec2.Instance' {..} = + Types.Server + { Types._serverId = instanceId + , Types._serverName = awsEc2InstanceName i + , Types._serverCpu = fromIntegral <$> (Aws.Ec2.Types.CpuOptions.coreCount =<< cpuOptions) + , Types._serverRam = Nothing + , Types._serverDisk = Nothing + , Types._serverState = ec2InstanceToServerState state + , Types._serverCreatedAt = Just (Aws.Data.Time.fromTime launchTime) + , Types._serverProvider = Types.ProviderAws + , Types._serverRegion = Aws.fromRegion region + , Types._serverType = Just (Aws.Ec2.fromInstanceType instanceType) + } + + +ec2InstanceToServerState :: Aws.Ec2.Types.InstanceState -> Types.State +ec2InstanceToServerState Aws.Ec2.Types.InstanceState' {..} = + case name of + Aws.Ec2.Types.InstanceStateName_Pending -> Types.StateCreating + Aws.Ec2.Types.InstanceStateName_Running -> Types.StateRunning + Aws.Ec2.Types.InstanceStateName_Stopping -> Types.StateStopping + Aws.Ec2.Types.InstanceStateName_Stopped -> Types.StateStopped + Aws.Ec2.Types.InstanceStateName_Shutting_down -> Types.StateStopping + Aws.Ec2.Types.InstanceStateName_Terminated -> Types.StateTerminating + _ -> Types.StateUnknown awsEc2InstanceName :: Aws.Ec2.Instance - -> T.Text + -> Maybe T.Text awsEc2InstanceName i = let mTag = L.find (\t -> T.toLower (t L.^. Aws.Ec2.Lens.tag_key) == "name") =<< (i L.^. Aws.Ec2.Lens.instance_tags) - in maybe (i L.^. Aws.Ec2.Lens.instance_instanceId) (L.^. Aws.Ec2.Lens.tag_value) mTag + in (L.^. Aws.Ec2.Lens.tag_value) <$> mTag + + +-- ** Lightsail -awsEc2SecurityToText - :: Aws.Ec2.SecurityGroup - -> T.Text -awsEc2SecurityToText sg = - let name = sg L.^. Aws.Ec2.Lens.securityGroup_groupName - perms = fromMaybe [] $ sg L.^. Aws.Ec2.Lens.securityGroup_ipPermissions - in name <> "=" <> T.intercalate "," (fmap awsEc2IpPermToText perms) +lightsailInstanceToServer :: Aws.Region -> Aws.Lightsail.Instance -> Types.Server +lightsailInstanceToServer region Aws.Lightsail.Types.Instance' {..} = + Types.Server + { Types._serverId = fromMaybe "" arn + , Types._serverName = name + , Types._serverCpu = lightsailInstanceCpu =<< hardware + , Types._serverRam = lightsailInstanceRam =<< hardware + , Types._serverDisk = lightsailInstanceDisk =<< hardware + , Types._serverState = maybe Types.StateUnknown lightsailInstanceToServerState state + , Types._serverCreatedAt = Aws.Data.Time.fromTime <$> createdAt + , Types._serverProvider = Types.ProviderAws + , Types._serverRegion = Aws.fromRegion region + , Types._serverType = bundleId + } -awsEc2IpPermToText - :: Aws.Ec2.IpPermission - -> T.Text -awsEc2IpPermToText x = - let proto = x L.^. Aws.Ec2.Lens.ipPermission_ipProtocol - portS = x L.^. Aws.Ec2.Lens.ipPermission_fromPort - portE = x L.^. Aws.Ec2.Lens.ipPermission_toPort - in proto <> "/" <> maybe "0" Z.Text.tshow portS <> "-" <> maybe "0" Z.Text.tshow portE +lightsailInstanceCpu :: Aws.Lightsail.Types.InstanceHardware -> Maybe Int16 +lightsailInstanceCpu Aws.Lightsail.Types.InstanceHardware' {..} = + fromIntegral <$> cpuCount -printAwsLightsailInstanceSecurity +lightsailInstanceRam :: Aws.Lightsail.Types.InstanceHardware -> Maybe Int32 +lightsailInstanceRam Aws.Lightsail.Types.InstanceHardware' {..} = + fromIntegral . double2Int . (1024 *) <$> ramSizeInGb + + +lightsailInstanceDisk :: Aws.Lightsail.Types.InstanceHardware -> Maybe Int32 +lightsailInstanceDisk Aws.Lightsail.Types.InstanceHardware' {..} = + sum . fmap (maybe 0 fromIntegral . Aws.Lightsail.Types.Disk.sizeInGb) <$> disks + + +lightsailInstanceToServerState :: Aws.Lightsail.Types.InstanceState -> Types.State +lightsailInstanceToServerState Aws.Lightsail.Types.InstanceState' {..} = + case name of + Just "pending" -> Types.StateCreating + Just "running" -> Types.StateRunning + Just "stopping" -> Types.StateStopping + Just "stopped" -> Types.StateStopped + Just "shutting-down" -> Types.StateTerminating + Just "terminated" -> Types.StateTerminated + _ -> Types.StateUnknown + + +-- ** Others + + +_envFromConnection :: MonadIO m - => Aws.Lightsail.Instance - -> m () -printAwsLightsailInstanceSecurity i = - let name = fromMaybe (error "Missing instance name") (i L.^. Aws.Lightsail.Lens.instance_name <|> i L.^. Aws.Lightsail.Lens.instance_arn) - ports = fromMaybe [] ((L.^. Aws.Lightsail.Lens.instanceNetworking_ports) =<< i L.^. Aws.Lightsail.Lens.instance_networking) - portsText = T.intercalate " " (fmap portToText ports) - in liftIO $ TIO.putStrLn (name <> ": " <> portsText) + => AwsConnection + -> m Aws.Env +_envFromConnection AwsConnection {..} = + Aws.newEnv (pure . Aws.Auth.fromKeys accessKeyId secretAccessKey) where - portToText p = - let pN = fromMaybe "" (p L.^. Aws.Lightsail.Lens.instancePortInfo_commonName) - pP = maybe "" Aws.Lightsail.fromNetworkProtocol (p L.^. Aws.Lightsail.Lens.instancePortInfo_protocol) - pS = p L.^. Aws.Lightsail.Lens.instancePortInfo_fromPort - pE = p L.^. Aws.Lightsail.Lens.instancePortInfo_toPort - in pN <> "=" <> pP <> "/" <> maybe "0" Z.Text.tshow pS <> "-" <> maybe "0" Z.Text.tshow pE + accessKeyId = Aws.AccessKey (TE.encodeUtf8 _awsConnectionAccessKeyId) + secretAccessKey = Aws.SecretKey (TE.encodeUtf8 _awsConnectionSecretAccessKey) diff --git a/src/Clompse/Providers/Do.hs b/src/Clompse/Providers/Do.hs index db7c237..7880977 100644 --- a/src/Clompse/Providers/Do.hs +++ b/src/Clompse/Providers/Do.hs @@ -7,6 +7,7 @@ module Clompse.Providers.Do where import qualified Autodocodec as ADC +import qualified Clompse.Types as Types import Control.Monad.Except (MonadError (throwError)) import Control.Monad.IO.Class (MonadIO) import qualified Data.Aeson as Aeson @@ -21,6 +22,7 @@ import GHC.Generics (Generic) import System.Exit (ExitCode (..)) import qualified System.Process.Typed as TP import qualified Zamazingo.Net as Z.Net +import qualified Zamazingo.Text as Z.Text -- * Connection @@ -402,6 +404,30 @@ doListFirewalls conn = -- * Helpers +toServer :: DoDroplet -> Types.Server +toServer DoDroplet {..} = + Types.Server + { _serverId = Z.Text.tshow _doDropletId + , _serverName = Just _doDropletName + , _serverCpu = Just _doDropletVcpus + , _serverRam = Just _doDropletMemory + , _serverDisk = Just _doDropletDisk + , _serverState = toServerState _doDropletStatus + , _serverCreatedAt = Just _doDropletCreatedAt + , _serverProvider = Types.ProviderDo + , _serverRegion = _doRegionSlug _doDropletRegion + , _serverType = Just _doDropletSizeSlug + } + + +toServerState :: T.Text -> Types.State +toServerState "new" = Types.StateCreating +toServerState "active" = Types.StateRunning +toServerState "off" = Types.StateStopped +toServerState "archive" = Types.StateArchived +toServerState _ = Types.StateUnknown + + doctl :: Aeson.FromJSON a => MonadIO m diff --git a/src/Clompse/Providers/Hetzner.hs b/src/Clompse/Providers/Hetzner.hs index e47745f..1da5c36 100644 --- a/src/Clompse/Providers/Hetzner.hs +++ b/src/Clompse/Providers/Hetzner.hs @@ -2,19 +2,21 @@ {-# LANGUAGE DerivingVia #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} module Clompse.Providers.Hetzner where import qualified Autodocodec as ADC +import qualified Clompse.Types as Types import Control.Monad.Except (MonadError) -import Control.Monad.IO.Class (MonadIO (liftIO)) +import Control.Monad.IO.Class (MonadIO) import qualified Data.Aeson as Aeson +import Data.Int (Int16, Int32) import qualified Data.List as List -import qualified Data.List.NonEmpty as NE import Data.Maybe (mapMaybe) import qualified Data.Text as T import qualified Data.Text.Encoding as TE -import qualified Data.Text.IO as TIO +import qualified Data.Time as Time import GHC.Generics (Generic) import qualified Hetzner.Cloud as Hetzner import qualified Zamazingo.Text as Z.Text @@ -99,31 +101,52 @@ hetznerListServersWithFirewalls conn = do -- * Helpers -printServerFirewall - :: MonadIO m - => (Hetzner.Server, [Hetzner.Firewall]) - -> m () -printServerFirewall (i, sgs) = - let name = Hetzner.serverName i - secs = T.intercalate " " (fmap firewallToText sgs) - in liftIO $ TIO.putStrLn (name <> ": " <> secs) - - -firewallToText - :: Hetzner.Firewall - -> T.Text -firewallToText fw = - let name = Hetzner.firewallName fw - rules = fmap ruleToText (Hetzner.firewallRules fw) - in name <> "=" <> T.intercalate "," rules - - -ruleToText :: Hetzner.FirewallRule -> T.Text -ruleToText rule = - let dir = Z.Text.tshow (Hetzner.firewallRuleDirection rule) - proto = Z.Text.tshow (Hetzner.firewallRuleProtocol rule) - ips = fmap (either Z.Text.tshow Z.Text.tshow) (NE.toList (Hetzner.firewallRuleIPs rule)) - in dir <> "+" <> proto <> "://" <> T.intercalate ";" ips +toServer :: Hetzner.Server -> Types.Server +toServer Hetzner.Server {..} = + Types.Server + { Types._serverId = toServerId serverID + , Types._serverName = Just serverName + , Types._serverCpu = Just (toServerCpu serverType) + , Types._serverRam = Just (toServerRam serverType) + , Types._serverDisk = Just (toServerDisk serverType) + , Types._serverState = toServerState serverStatus + , Types._serverCreatedAt = Just (Time.zonedTimeToUTC serverCreated) + , Types._serverProvider = Types.ProviderHetzner + , Types._serverRegion = Hetzner.datacenterName serverDatacenter + , Types._serverType = Just (Hetzner.serverTypeDescription serverType) + } + + +toServerId :: Hetzner.ServerID -> T.Text +toServerId (Hetzner.ServerID x) = + Z.Text.tshow x + + +toServerCpu :: Hetzner.ServerType -> Int16 +toServerCpu Hetzner.ServerType {..} = + fromIntegral serverCores + + +toServerRam :: Hetzner.ServerType -> Int32 +toServerRam Hetzner.ServerType {..} = + fromIntegral serverMemory * 1024 + + +toServerDisk :: Hetzner.ServerType -> Int32 +toServerDisk Hetzner.ServerType {..} = + fromIntegral serverDisk + + +toServerState :: Hetzner.ServerStatus -> Types.State +toServerState Hetzner.Initializing = Types.StateCreating +toServerState Hetzner.Starting = Types.StateCreating +toServerState Hetzner.Running = Types.StateRunning +toServerState Hetzner.Stopping = Types.StateStopping +toServerState Hetzner.Off = Types.StateStopped +toServerState Hetzner.Deleting = Types.StateTerminating +toServerState Hetzner.Rebuilding = Types.StateRebuilding +toServerState Hetzner.Migrating = Types.StateMigrating +toServerState Hetzner.StatusUnknown = Types.StateUnknown _tokenFromConnection :: HetznerConnection -> Hetzner.Token