cloudy-0.1.0.0: src/Cloudy/Cmd/Scaleway/Utils.hs
module Cloudy.Cmd.Scaleway.Utils where
import Cloudy.Scaleway (Zone (..), zoneFromText, PageNum (PageNum))
import Data.Foldable (asum, foldl')
import Data.Maybe (fromMaybe)
import Data.Text (Text, unpack)
import Network.HTTP.Client.TLS (newTlsManager)
import Servant.API (AuthProtect, Headers (Headers), Header, HList (..), ResponseHeader (Header))
import Servant.Client (BaseUrl (BaseUrl), Scheme (Https), ClientM, ClientError, mkClientEnv, runClientM)
import Servant.Client.Core (mkAuthenticatedRequest, AuthenticatedRequest, AuthClientData, Request, addHeader)
createAuthReq :: Text -> AuthenticatedRequest (AuthProtect "auth-token")
createAuthReq secretKey = mkAuthenticatedRequest secretKey createAuthTokenHeader
createAuthTokenHeader :: Text -> Request -> Request
createAuthTokenHeader authData = addHeader "X-Auth-Token" authData
type instance AuthClientData (AuthProtect "auth-token") = Text
scalewayBaseUrl :: BaseUrl
scalewayBaseUrl = BaseUrl Https "api.scaleway.com" 443 ""
runScalewayClientM :: (forall x. ClientError -> IO x) -> ClientM a -> IO a
runScalewayClientM errHandler action = do
manager <- newTlsManager
let clientEnv = mkClientEnv manager scalewayBaseUrl
res <- runClientM action clientEnv
case res of
Left err -> errHandler err
Right a -> pure a
defaultZone :: Zone
defaultZone = NL1
getZone :: Maybe Text -> Maybe Text -> IO Zone
getZone maybeZoneFromConfFile maybeZoneFromCliOpts =
case (maybeZoneFromConfFile, maybeZoneFromCliOpts) of
(_, Just zoneFromCliOpts) ->
case zoneFromText zoneFromCliOpts of
Nothing ->
error . unpack $
"Could not parse zone specified in --zone option on cli: " <> zoneFromCliOpts
Just zone -> pure zone
(Just zoneFromConfFile, _) ->
case zoneFromText zoneFromConfFile of
Nothing ->
error . unpack $
"Could not parse zone specified in scaleway.defaultZone in config file: " <> zoneFromConfFile
Just zone -> pure zone
(Nothing, Nothing) -> pure defaultZone
getMaybeOrDefault :: Foldable t => a -> t (Maybe a) -> a
getMaybeOrDefault defVal maybes = fromMaybe defVal (asum maybes)
defaultInstanceType :: Text
defaultInstanceType = "PLAY2-NANO"
getInstanceType :: Maybe Text -> Maybe Text -> Text
getInstanceType maybeInstanceTypeFromConfFile maybeInstanceTypeFromCliOpts =
getMaybeOrDefault
defaultInstanceType
[maybeInstanceTypeFromCliOpts, maybeInstanceTypeFromConfFile]
defaultImageId :: Text
defaultImageId = "ubuntu_noble"
getImageId :: Maybe Text -> Maybe Text -> Text
getImageId maybeImageIdFromConfFile maybeImageIdFromCliOpts =
getMaybeOrDefault
defaultImageId
[maybeImageIdFromCliOpts, maybeImageIdFromConfFile]
fetchPagedApi ::
Monad m =>
(Maybe PageNum -> m (Headers '[Header "x-total-count" Int] a)) ->
(a -> a -> a) ->
(a -> Int) ->
m a
fetchPagedApi fetchPage combineResults countResultsOnPage = do
Headers page1Res headers <- fetchPage (Just $ PageNum 1)
let page1Count = countResultsOnPage page1Res
totalCount <-
case headers of
HCons h HNil ->
case h of
Header totalCount -> pure totalCount
_ -> error "fetchPagedApi: could not find or decode header x-total-count for some reason"
if page1Count >= totalCount
then pure page1Res
else do
allRes <-
unfoldM
(\(currTotal, pageNumToFetch) ->
if currTotal >= totalCount
then pure Nothing
else do
Headers pageRes _ <- fetchPage (Just $ PageNum pageNumToFetch)
let newTotal = currTotal + countResultsOnPage pageRes
nextPageNum = pageNumToFetch + 1
pure $ Just (pageRes, (newTotal, nextPageNum))
)
(page1Count, 2)
pure $ foldl' combineResults page1Res allRes
unfoldM :: Monad m => (s -> m (Maybe (a, s))) -> s -> m [a]
unfoldM f s = do
mres <- f s
case mres of
Nothing -> return []
Just (a, s') -> fmap (a :) (unfoldM f s')