{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleContexts #-}
module Hasklepias.CohortApp (cohortMain) where
import Aws (Configuration (..))
import qualified Aws
import qualified Aws.S3 as S3
import Blammo.Logging.Simple
import Codec.Compression.GZip
( CompressionLevel,
compress,
decompress,
)
import Cohort.Cohort
( Cohort,
CohortSpec,
SubjId (..),
Subject (..),
eventsToSubject,
)
import qualified Cohort.Core as CCore
import qualified Cohort.Output as COutput
import Control.Exception (throwIO)
import Control.Monad (when)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Resource
import Data.Aeson (ToJSON (..), Value (..), encode)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.Conduit (runConduit, (.|))
import Data.Conduit.Binary (sinkLbs)
import Data.Foldable (foldrM)
import Data.Generics.Product (HasField (field))
import qualified Data.HashMap.Strict as HM
import qualified Data.Ini as Ini
import Data.List.NonEmpty (NonEmpty (..))
import Data.Map.Strict (Map, keys)
import Data.String
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import EventDataTheory
( Event,
EventLineAble,
Eventable,
FromJSONEvent,
LineParseError (..),
ToJSONEvent,
defaultParseEventLineOption,
parseEventLinesL',
)
import GHC.Arr (accumArray, assocs)
import Hasklepias.CohortApp.CohortAppCLI
import Lens.Micro (set, (<&>))
import Lens.Micro.Extras (view)
import Network.HTTP.Conduit
( RequestBody (..),
newManager,
responseBody,
tlsManagerSettings,
)
import Options.Applicative
import System.Directory
( doesFileExist,
getHomeDirectory,
)
import System.Exit
import System.FilePath ((</>))
cohortMain :: (CohortConstraints t m a b) => Map Text (CohortSpec t m a) -> IO ()
cohortMain :: forall t m a b.
CohortConstraints t m a b =>
Map Text (CohortSpec t m a) -> IO ()
cohortMain Map Text (CohortSpec t m a)
specs = do
CohortCLIOpts
opts <- forall a. ParserInfo a -> IO a
execParser ParserInfo CohortCLIOpts
cliParserInfo
let cfg :: CohortSettings t m a
cfg = forall t m a.
Map Text (CohortSpec t m a)
-> CohortCLIOpts -> CohortSettings t m a
MkCohortSettings Map Text (CohortSpec t m a)
specs CohortCLIOpts
opts
forall t m a b. CohortApp t m a b -> CohortSettings t m a -> IO b
runCohortApp forall t m a b. CohortConstraints t m a b => CohortApp t m a ()
cohortApp CohortSettings t m a
cfg
type CohortConstraints t m a b = (Eventable t m a, EventLineAble t m a b, ToJSONEvent t m a, FromJSONEvent t m a)
data CohortSettings t m a = MkCohortSettings
{ forall t m a. CohortSettings t m a -> Map Text (CohortSpec t m a)
cohortSpecs :: Map Text (CohortSpec t m a),
forall t m a. CohortSettings t m a -> CohortCLIOpts
cliOpts :: CohortCLIOpts
}
type CohortApp t m a = ReaderT (CohortSettings t m a) (LoggingT IO)
runCohortApp :: CohortApp t m a b -> CohortSettings t m a -> IO b
runCohortApp :: forall t m a b. CohortApp t m a b -> CohortSettings t m a -> IO b
runCohortApp CohortApp t m a b
app = forall (m :: * -> *) a. MonadUnliftIO m => LoggingT m a -> m a
runSimpleLoggingT forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r (m :: * -> *) a. ReaderT r m a -> r -> m a
runReaderT CohortApp t m a b
app
cohortApp :: (CohortConstraints t m a b) => CohortApp t m a ()
cohortApp :: forall t m a b. CohortConstraints t m a b => CohortApp t m a ()
cohortApp = forall t m a b.
CohortConstraints t m a b =>
CohortApp t m a [Subject t m a]
readData forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall t m a.
[Subject t m a] -> CohortApp t m a (Map Text (Cohort a))
evalCohortMap forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall a t m. ToJSON a => Map Text (Cohort a) -> CohortApp t m a ()
writeData
evalCohortMap :: [Subject t m a] -> CohortApp t m a (Map Text (Cohort a))
evalCohortMap :: forall t m a.
[Subject t m a] -> CohortApp t m a (Map Text (Cohort a))
evalCohortMap [] = do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logError Message
"No valid subject data to process. Check logs for line parse errors."
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. ExitCode -> IO a
exitWith (Int -> ExitCode
ExitFailure Int
1)
evalCohortMap [Subject t m a]
subjs = do
Map Text (CohortSpec t m a)
sm <- forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks forall t m a. CohortSettings t m a -> Map Text (CohortSpec t m a)
cohortSpecs
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo forall a b. (a -> b) -> a -> b
$ Text
"Building cohorts" Text -> [SeriesElem] -> Message
:# [Key
"cohorts" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall k a. Map k a -> [k]
keys Map Text (CohortSpec t m a)
sm]
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ forall t m a. CohortSpecMap t m a -> [Subject t m a] -> CohortMap a
CCore.evalCohortMap Map Text (CohortSpec t m a)
sm [Subject t m a]
subjs
writeData :: (ToJSON a) => Map Text (Cohort a) -> CohortApp t m a ()
writeData :: forall a t m. ToJSON a => Map Text (Cohort a) -> CohortApp t m a ()
writeData Map Text (Cohort a)
d = do
CohortCLIOpts
opts <- forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks forall t m a. CohortSettings t m a -> CohortCLIOpts
cliOpts
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo forall a b. (a -> b) -> a -> b
$ Text
"Writing data to" Text -> [SeriesElem] -> Message
:# [Key
"location" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. Show a => a -> String
show (CohortCLIOpts -> (OutputFlag, Output)
output CohortCLIOpts
opts)]
let compfun :: ByteString -> ByteString
compfun = case CohortCLIOpts -> OutputCompression
outCompress CohortCLIOpts
opts of
OutputCompression
Compress -> ByteString -> ByteString
compress
OutputCompression
NoCompress -> forall a. a -> a
id
let writefun :: ByteString -> ReaderT (CohortSettings t m a) (LoggingT IO) ()
writefun = case forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ CohortCLIOpts -> (OutputFlag, Output)
output CohortCLIOpts
opts of
Output
StdOutput -> forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> IO ()
BL.putStr
FileOutput String
x -> forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ByteString -> IO ()
BL.writeFile String
x
S3Output CredentialsCfg
cred Text
b Text
k -> \ByteString
d' -> do
Text
hash <- forall t m a.
CredentialsCfg
-> Text -> Text -> ByteString -> CohortApp t m a Text
putS3Object CredentialsCfg
cred Text
b Text
k ByteString
d'
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo forall a b. (a -> b) -> a -> b
$ Text
"Put request complete" Text -> [SeriesElem] -> Message
:# [Key
"etag" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. Show a => a -> String
show Text
hash]
forall {t} {m} {a}.
ByteString -> ReaderT (CohortSettings t m a) (LoggingT IO) ()
writefun forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
compfun forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ToJSON a => a -> ByteString
encode forall a b. (a -> b) -> a -> b
$ forall a. ToJSON a => CohortMap a -> CohortMapJSON
COutput.toCohortJSON Map Text (Cohort a)
d
readData :: (CohortConstraints t m a b) => CohortApp t m a [Subject t m a]
readData :: forall t m a b.
CohortConstraints t m a b =>
CohortApp t m a [Subject t m a]
readData = do
CohortCLIOpts
opts <- forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks forall t m a. CohortSettings t m a -> CohortCLIOpts
cliOpts
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo forall a b. (a -> b) -> a -> b
$ Text
"Reading data from" Text -> [SeriesElem] -> Message
:# [Key
"location" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. Show a => a -> String
show (CohortCLIOpts -> (InputFlag, Input)
input CohortCLIOpts
opts)]
let decompFun :: ByteString -> ByteString
decompFun = case CohortCLIOpts -> InputDecompression
inDecompress CohortCLIOpts
opts of
InputDecompression
Decompress -> ByteString -> ByteString
decompress
InputDecompression
NoDecompress -> forall a. a -> a
id
ByteString
bs <-
ByteString -> ByteString
BL.toStrict forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString
decompFun forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> case forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ CohortCLIOpts -> (InputFlag, Input)
input CohortCLIOpts
opts of
Input
StdInput -> forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO ByteString
BL.getContents
FileInput String
x -> forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ String -> IO ByteString
BL.readFile String
x
S3Input CredentialsCfg
cred Text
b Text
k -> do
(ByteString
d, ObjectMetadata
meta) <- forall t m a.
CredentialsCfg
-> Text -> Text -> CohortApp t m a (ByteString, ObjectMetadata)
getS3Object CredentialsCfg
cred Text
b Text
k
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logInfo forall a b. (a -> b) -> a -> b
$
Text
"Get request complete"
Text -> [SeriesElem] -> Message
:# [ Key
"object-last-modified" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. Show a => a -> String
show (ObjectMetadata -> UTCTime
S3.omLastModified ObjectMetadata
meta),
Key
"etag" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. Show a => a -> String
show (ObjectMetadata -> Text
S3.omETag ObjectMetadata
meta)
]
forall (f :: * -> *) a. Applicative f => a -> f a
pure ByteString
d
forall t m a b.
CohortConstraints t m a b =>
ByteString -> CohortApp t m a [Subject t m a]
parseSubjects ByteString
bs
parseSubjects :: (CohortConstraints t m a b) => BS.ByteString -> CohortApp t m a [Subject t m a]
parseSubjects :: forall t m a b.
CohortConstraints t m a b =>
ByteString -> CohortApp t m a [Subject t m a]
parseSubjects ByteString
bs = do
ParseEventLineOption
ivopt <- forall (m :: * -> *) r a. Monad m => (r -> a) -> ReaderT r m a
asks (CohortCLIOpts -> ParseEventLineOption
intervalOpt forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall t m a. CohortSettings t m a -> CohortCLIOpts
cliOpts)
let ([LineParseError]
errs, [(Text, Event t m a)]
bss) = forall m t a b.
(Eventable t m a, EventLineAble t m a b, FromJSONEvent t m a) =>
ParseEventLineOption
-> ByteString -> ([LineParseError], [(Text, Event t m a)])
parseEventLinesL' ParseEventLineOption
ivopt ByteString
bs
let ss :: [Subject t m a]
ss = forall t m a. [(Text, Event t m a)] -> [Subject t m a]
eventsToSubject [(Text, Event t m a)]
bss
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_
( \(MkLineParseError (Natural
n, String
e)) ->
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logError forall a b. (a -> b) -> a -> b
$ Text
"parse-error" Text -> [SeriesElem] -> Message
:# [Key
"line-number" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Natural
n, Key
"error" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= String
e]
)
[LineParseError]
errs
forall (f :: * -> *) a. Applicative f => a -> f a
pure [Subject t m a]
ss
getAwsCredentialsIniFile :: CredentialsCfg -> CohortApp t m a (Maybe Aws.Credentials)
getAwsCredentialsIniFile :: forall t m a. CredentialsCfg -> CohortApp t m a (Maybe Credentials)
getAwsCredentialsIniFile (MkCredentialsCfg Text
prof) = do
String
credfile <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ (String -> String -> String
</> String
".aws/credentials") forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO String
getHomeDirectory
Bool
chk <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ String -> IO Bool
doesFileExist String
credfile
if Bool
chk
then do
Either String Ini
ini <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ String -> IO (Either String Ini)
Ini.readIniFile String
credfile
case Either String Ini
ini of
Left String
_ -> do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logDebug forall a b. (a -> b) -> a -> b
$ Text
"cred-file-parse" Text -> [SeriesElem] -> Message
:# [Key
"cred-file" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= String
credfile]
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Credentials file must be in format required by aws cli."
Right Ini
d -> do
let prof_cred :: Maybe (HashMap Text Text)
prof_cred = forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup Text
prof forall a b. (a -> b) -> a -> b
$ Ini -> HashMap Text (HashMap Text Text)
Ini.unIni Ini
d
let creds :: Maybe (Text, Text)
creds = (,) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup Text
"aws_access_key_id" forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Maybe (HashMap Text Text)
prof_cred) forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> (forall k v. (Eq k, Hashable k) => k -> HashMap k v -> Maybe v
HM.lookup Text
"aws_secret_access_key" forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< Maybe (HashMap Text Text)
prof_cred)
case Maybe (Text, Text)
creds of
Maybe (Text, Text)
Nothing -> do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logDebug forall a b. (a -> b) -> a -> b
$ Text
"cred-file-missing-var" Text -> [SeriesElem] -> Message
:# [Key
"profile" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Text
prof]
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a. Maybe a
Nothing
Just (Text
i, Text
k) -> forall a. a -> Maybe a
Just forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (io :: * -> *).
MonadIO io =>
ByteString -> ByteString -> io Credentials
Aws.makeCredentials (Text -> ByteString
TE.encodeUtf8 Text
i) (Text -> ByteString
TE.encodeUtf8 Text
k)
else do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logError forall a b. (a -> b) -> a -> b
$ Text
"cred-file-does-not-exist" Text -> [SeriesElem] -> Message
:# [Key
"cred-file" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= String
credfile]
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a. Maybe a
Nothing
getAwsConfig :: CredentialsCfg -> CohortApp t m a Aws.Configuration
getAwsConfig :: forall t m a. CredentialsCfg -> CohortApp t m a Configuration
getAwsConfig CredentialsCfg
cfg = do
Maybe Credentials
credfromenv <- forall (io :: * -> *). MonadIO io => io (Maybe Credentials)
Aws.loadCredentialsFromEnv
Credentials
creds <- case Maybe Credentials
credfromenv of
Maybe Credentials
Nothing -> do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logDebug forall a b. (a -> b) -> a -> b
$ Text
"aws-credentials-env" Text -> [SeriesElem] -> Message
:# [Key
"cred-env" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= Value
Null]
Maybe Credentials
credfromfile <- forall t m a. CredentialsCfg -> CohortApp t m a (Maybe Credentials)
getAwsCredentialsIniFile CredentialsCfg
cfg
case Maybe Credentials
credfromfile of
Maybe Credentials
Nothing -> do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logDebug forall a b. (a -> b) -> a -> b
$ Text
"aws-credentials-file" Text -> [SeriesElem] -> Message
:# [Key
"cred-cfg" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. ToJSON a => a -> Value
toJSON CredentialsCfg
cfg]
Maybe Credentials
credim <- forall (io :: * -> *). MonadIO io => io (Maybe Credentials)
Aws.loadCredentialsFromInstanceMetadata
case Maybe Credentials
credim of
Maybe Credentials
Nothing -> do
forall (m :: * -> *).
(HasCallStack, MonadLogger m) =>
Message -> m ()
logError forall a b. (a -> b) -> a -> b
$ Text
"credentials-not-found" Text -> [SeriesElem] -> Message
:# [Key
"cred-cfg" forall kv v. (KeyValue kv, ToJSON v) => Key -> v -> kv
.= forall a. ToJSON a => a -> Value
toJSON CredentialsCfg
cfg]
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"Could not load Aws credentials from any source."
Just Credentials
cr -> forall (f :: * -> *) a. Applicative f => a -> f a
pure Credentials
cr
Just Credentials
cr -> forall (f :: * -> *) a. Applicative f => a -> f a
pure Credentials
cr
forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$
Aws.Configuration
{ timeInfo :: TimeInfo
timeInfo = TimeInfo
Aws.Timestamp,
credentials :: Credentials
credentials = Credentials
creds,
logger :: Logger
logger = LogLevel -> Logger
Aws.defaultLog LogLevel
Aws.Warning,
proxy :: Maybe Proxy
proxy = forall a. Maybe a
Nothing
}
getS3Object :: CredentialsCfg -> BucketName -> ObjectKey -> CohortApp t m a (BL.ByteString, S3.ObjectMetadata)
getS3Object :: forall t m a.
CredentialsCfg
-> Text -> Text -> CohortApp t m a (ByteString, ObjectMetadata)
getS3Object CredentialsCfg
credcfg Text
b Text
k = do
Configuration
cfg <- forall t m a. CredentialsCfg -> CohortApp t m a Configuration
getAwsConfig CredentialsCfg
credcfg
let s3cfg :: S3Configuration NormalQuery
s3cfg = forall config. DefaultServiceConfiguration config => config
Aws.defServiceConfig :: S3.S3Configuration Aws.NormalQuery
Manager
mgr <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ ManagerSettings -> IO Manager
newManager ManagerSettings
tlsManagerSettings
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT forall a b. (a -> b) -> a -> b
$ do
GetObjectResponse
rsp <- forall r a.
Transaction r a =>
Configuration
-> ServiceConfiguration r NormalQuery
-> Manager
-> r
-> ResourceT IO a
Aws.pureAws Configuration
cfg S3Configuration NormalQuery
s3cfg Manager
mgr forall a b. (a -> b) -> a -> b
$ Text -> Text -> GetObject
S3.getObject Text
b Text
k
ByteString
d <- forall (m :: * -> *) r. Monad m => ConduitT () Void m r -> m r
runConduit forall a b. (a -> b) -> a -> b
$ forall body. Response body -> body
responseBody (GetObjectResponse
-> Response (ConduitM () ByteString (ResourceT IO) ())
S3.gorResponse GetObjectResponse
rsp) forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| forall (m :: * -> *) o.
Monad m =>
ConduitT ByteString o m ByteString
sinkLbs
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ByteString
d, GetObjectResponse -> ObjectMetadata
S3.gorMetadata GetObjectResponse
rsp)
putS3Object :: CredentialsCfg -> BucketName -> ObjectKey -> BL.ByteString -> CohortApp t m a Text
putS3Object :: forall t m a.
CredentialsCfg
-> Text -> Text -> ByteString -> CohortApp t m a Text
putS3Object CredentialsCfg
credcfg Text
b Text
k ByteString
o = do
Configuration
cfg <- forall t m a. CredentialsCfg -> CohortApp t m a Configuration
getAwsConfig CredentialsCfg
credcfg
let s3cfg :: S3Configuration NormalQuery
s3cfg = forall config. DefaultServiceConfiguration config => config
Aws.defServiceConfig :: S3.S3Configuration Aws.NormalQuery
Manager
mgr <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ ManagerSettings -> IO Manager
newManager ManagerSettings
tlsManagerSettings
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. MonadUnliftIO m => ResourceT m a -> m a
runResourceT forall a b. (a -> b) -> a -> b
$ do
S3.PutObjectResponse {porETag :: PutObjectResponse -> Text
S3.porETag = Text
etag} <- forall r a.
Transaction r a =>
Configuration
-> ServiceConfiguration r NormalQuery
-> Manager
-> r
-> ResourceT IO a
Aws.pureAws Configuration
cfg S3Configuration NormalQuery
s3cfg Manager
mgr forall a b. (a -> b) -> a -> b
$ Text -> Text -> RequestBody -> PutObject
S3.putObject Text
b Text
k (ByteString -> RequestBody
RequestBodyLBS ByteString
o)
forall (f :: * -> *) a. Applicative f => a -> f a
pure Text
etag