-- |
-- Module      : Amazonka.Env
-- Copyright   : (c) 2013-2021 Brendan Hay
-- License     : Mozilla Public License, v. 2.0.
-- Maintainer  : Brendan Hay <brendan.g.hay+amazonka@gmail.com>
-- Stability   : provisional
-- Portability : non-portable (GHC extensions)
--
-- Environment and AWS specific configuration needed to perform AWS
-- requests.
module Amazonka.Env
  ( -- * Creating the Environment
    newEnv,
    newEnvNoAuth,
    newEnvWith,
    Env' (..),
    Env,
    EnvNoAuth,
    envAuthMaybe,

    -- * Overriding Default Configuration
    authenticate,
    override,
    configure,

    -- * Scoped Actions
    within,
    once,
    timeout,

    -- * 'Env' Lenses
    -- $envLenses
    envRegion,
    envLogger,
    envRetryCheck,
    envOverride,
    envManager,
    envAuth,

    -- * Retry HTTP Exceptions
    retryConnectionFailure,
  )
where

import Amazonka.Auth
import Amazonka.Lens ((.~), (?~))
import Amazonka.Prelude
import Amazonka.Types
import Control.Lens (Lens)
import qualified Data.Function as Function
import Data.Monoid (Dual (..), Endo (..))
import qualified Network.HTTP.Client as Client
import qualified Network.HTTP.Conduit as Client.Conduit

type Env = Env' Identity

type EnvNoAuth = Env' Proxy

-- | Creates a new environment with a new 'Manager' without debug logging
-- and uses 'getAuth' to expand/discover the supplied 'Credentials'.
-- Lenses can be used to further configure the resulting 'Env'.
--
-- /Since:/ @1.5.0@ - The region is now retrieved from the @AWS_REGION@ environment
-- variable (identical to official SDKs), or defaults to @us-east-1@.
-- You can override the 'Env' region by using 'envRegion', or the current operation's
-- region by using 'within'.
--
-- /Since:/ @1.3.6@ - The default logic for retrying 'HttpException's now uses
-- 'retryConnectionFailure' to retry specific connection failure conditions up to 3 times.
-- Previously only service specific errors were automatically retried.
-- This can be reverted to the old behaviour by resetting the 'Env' using
-- 'envRetryCheck' lens to @(\\_ _ -> False)@.
--
-- Throws 'AuthError' when environment variables or IAM profiles cannot be read.
--
-- /See:/ 'newEnvWith'.
newEnv ::
  MonadIO m =>
  -- | Credential discovery mechanism.
  Credentials ->
  m Env
newEnv :: Credentials -> m Env
newEnv Credentials
c =
  IO Manager -> m Manager
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (ManagerSettings -> IO Manager
Client.newManager ManagerSettings
Client.Conduit.tlsManagerSettings)
    m Manager -> (Manager -> m Env) -> m Env
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Credentials -> Env' Proxy -> m Env
forall (m :: * -> *) (withAuth :: * -> *).
(MonadIO m, Foldable withAuth) =>
Credentials -> Env' withAuth -> m Env
authenticate Credentials
c (Env' Proxy -> m Env)
-> (Manager -> Env' Proxy) -> Manager -> m Env
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Manager -> Env' Proxy
newEnvWith

-- | Generate an environment without credentials, which may only make
-- unsigned requests.
--
-- This is useful for the STS
-- <https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html AssumeRoleWithWebIdentity>
-- operation, which needs to make an unsigned request to pass the
-- token from an identity provider.
newEnvNoAuth :: MonadIO m => m EnvNoAuth
newEnvNoAuth :: m (Env' Proxy)
newEnvNoAuth =
  Manager -> Env' Proxy
newEnvWith (Manager -> Env' Proxy) -> m Manager -> m (Env' Proxy)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO Manager -> m Manager
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (ManagerSettings -> IO Manager
Client.newManager ManagerSettings
Client.Conduit.tlsManagerSettings)

-- | Construct a default 'EnvNoAuth' from a HTTP 'Client.Manager'.
newEnvWith :: Client.Manager -> EnvNoAuth
newEnvWith :: Manager -> Env' Proxy
newEnvWith Manager
m =
  Env :: forall (withAuth :: * -> *).
Region
-> Logger
-> (Int -> HttpException -> Bool)
-> Dual (Endo Service)
-> Manager
-> withAuth Auth
-> Env' withAuth
Env
    { $sel:_envRegion:Env :: Region
_envRegion = Region
NorthVirginia,
      $sel:_envLogger:Env :: Logger
_envLogger = \LogLevel
_ ByteStringBuilder
_ -> () -> IO ()
forall (f :: * -> *) a. Applicative f => a -> f a
pure (),
      $sel:_envRetryCheck:Env :: Int -> HttpException -> Bool
_envRetryCheck = Int -> Int -> HttpException -> Bool
retryConnectionFailure Int
3,
      $sel:_envOverride:Env :: Dual (Endo Service)
_envOverride = Dual (Endo Service)
forall a. Monoid a => a
mempty,
      $sel:_envManager:Env :: Manager
_envManager = Manager
m,
      $sel:_envAuth:Env :: Proxy Auth
_envAuth = Proxy Auth
forall k (t :: k). Proxy t
Proxy
    }

-- | /See:/ 'newEnv'
--
-- Throws 'AuthError' when environment variables or IAM profiles cannot be read.
authenticate ::
  (MonadIO m, Foldable withAuth) =>
  -- | Credential discovery mechanism.
  Credentials ->
  -- | Previous environment.
  Env' withAuth ->
  m Env
authenticate :: Credentials -> Env' withAuth -> m Env
authenticate Credentials
c env :: Env' withAuth
env@Env {withAuth Auth
Dual (Endo Service)
Manager
Region
Int -> HttpException -> Bool
Logger
_envAuth :: withAuth Auth
_envManager :: Manager
_envOverride :: Dual (Endo Service)
_envRetryCheck :: Int -> HttpException -> Bool
_envLogger :: Logger
_envRegion :: Region
$sel:_envAuth:Env :: forall (withAuth :: * -> *). Env' withAuth -> withAuth Auth
$sel:_envManager:Env :: forall (withAuth :: * -> *). Env' withAuth -> Manager
$sel:_envOverride:Env :: forall (withAuth :: * -> *). Env' withAuth -> Dual (Endo Service)
$sel:_envRetryCheck:Env :: forall (withAuth :: * -> *).
Env' withAuth -> Int -> HttpException -> Bool
$sel:_envLogger:Env :: forall (withAuth :: * -> *). Env' withAuth -> Logger
$sel:_envRegion:Env :: forall (withAuth :: * -> *). Env' withAuth -> Region
..} = do
  (Auth
a, Region -> Maybe Region -> Region
forall a. a -> Maybe a -> a
fromMaybe Region
NorthVirginia -> Region
r) <- Env' withAuth -> Credentials -> m (Auth, Maybe Region)
forall (m :: * -> *) (withAuth :: * -> *).
(MonadIO m, Foldable withAuth) =>
Env' withAuth -> Credentials -> m (Auth, Maybe Region)
getAuth Env' withAuth
env Credentials
c

  Env -> m Env
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Env -> m Env) -> Env -> m Env
forall a b. (a -> b) -> a -> b
$ Env :: forall (withAuth :: * -> *).
Region
-> Logger
-> (Int -> HttpException -> Bool)
-> Dual (Endo Service)
-> Manager
-> withAuth Auth
-> Env' withAuth
Env {$sel:_envRegion:Env :: Region
_envRegion = Region
r, $sel:_envAuth:Env :: Identity Auth
_envAuth = Auth -> Identity Auth
forall a. a -> Identity a
Identity Auth
a, Dual (Endo Service)
Manager
Int -> HttpException -> Bool
Logger
_envManager :: Manager
_envOverride :: Dual (Endo Service)
_envRetryCheck :: Int -> HttpException -> Bool
_envLogger :: Logger
$sel:_envManager:Env :: Manager
$sel:_envOverride:Env :: Dual (Endo Service)
$sel:_envRetryCheck:Env :: Int -> HttpException -> Bool
$sel:_envLogger:Env :: Logger
..}

-- | Get "the" 'Auth' from an 'Env'', if we can.
envAuthMaybe :: Foldable withAuth => Env' withAuth -> Maybe Auth
envAuthMaybe :: Env' withAuth -> Maybe Auth
envAuthMaybe = (Auth -> Maybe Auth -> Maybe Auth)
-> Maybe Auth -> withAuth Auth -> Maybe Auth
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Maybe Auth -> Maybe Auth -> Maybe Auth
forall a b. a -> b -> a
const (Maybe Auth -> Maybe Auth -> Maybe Auth)
-> (Auth -> Maybe Auth) -> Auth -> Maybe Auth -> Maybe Auth
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Auth -> Maybe Auth
forall a. a -> Maybe a
Just) Maybe Auth
forall a. Maybe a
Nothing (withAuth Auth -> Maybe Auth)
-> (Env' withAuth -> withAuth Auth) -> Env' withAuth -> Maybe Auth
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Env' withAuth -> withAuth Auth
forall (withAuth :: * -> *). Env' withAuth -> withAuth Auth
_envAuth

-- | Retry the subset of transport specific errors encompassing connection
-- failure up to the specific number of times.
retryConnectionFailure :: Int -> Int -> Client.HttpException -> Bool
retryConnectionFailure :: Int -> Int -> HttpException -> Bool
retryConnectionFailure Int
limit Int
n = \case
  Client.InvalidUrlException {} -> Bool
False
  Client.HttpExceptionRequest Request
_ HttpExceptionContent
ex
    | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
limit -> Bool
False
    | Bool
otherwise ->
      case HttpExceptionContent
ex of
        HttpExceptionContent
Client.NoResponseDataReceived -> Bool
True
        HttpExceptionContent
Client.ConnectionTimeout -> Bool
True
        HttpExceptionContent
Client.ConnectionClosed -> Bool
True
        Client.ConnectionFailure {} -> Bool
True
        Client.InternalException {} -> Bool
True
        HttpExceptionContent
_other -> Bool
False

-- | Provide a function which will be added to the existing stack
-- of overrides applied to all service configurations.
override :: (Service -> Service) -> Env -> Env
override :: (Service -> Service) -> Env -> Env
override Service -> Service
f Env
env = Env
env {$sel:_envOverride:Env :: Dual (Endo Service)
_envOverride = Env -> Dual (Endo Service)
forall (withAuth :: * -> *). Env' withAuth -> Dual (Endo Service)
_envOverride Env
env Dual (Endo Service) -> Dual (Endo Service) -> Dual (Endo Service)
forall a. Semigroup a => a -> a -> a
<> Endo Service -> Dual (Endo Service)
forall a. a -> Dual a
Dual ((Service -> Service) -> Endo Service
forall a. (a -> a) -> Endo a
Endo Service -> Service
f)}

-- | Configure a specific service. All requests belonging to the
-- supplied service will use this configuration instead of the default.
--
-- It's suggested you modify the default service configuration,
-- such as @Amazonka.DynamoDB.dynamoDB@.
configure :: Service -> Env -> Env
configure :: Service -> Env -> Env
configure Service
s = (Service -> Service) -> Env -> Env
override Service -> Service
f
  where
    f :: Service -> Service
f Service
x
      | (Abbrev -> Abbrev -> Bool)
-> (Service -> Abbrev) -> Service -> Service -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
Function.on Abbrev -> Abbrev -> Bool
forall a. Eq a => a -> a -> Bool
(==) Service -> Abbrev
_serviceAbbrev Service
s Service
x = Service
s
      | Bool
otherwise = Service
x

-- | Scope an action within the specific 'Region'.
within :: Region -> Env -> Env
within :: Region -> Env -> Env
within Region
r Env
env = Env
env {$sel:_envRegion:Env :: Region
_envRegion = Region
r}

-- | Scope an action such that any retry logic for the 'Service' is
-- ignored and any requests will at most be sent once.
once :: Env -> Env
once :: Env -> Env
once = (Service -> Service) -> Env -> Env
override ((Retry -> Identity Retry) -> Service -> Identity Service
Lens' Service Retry
serviceRetry ((Retry -> Identity Retry) -> Service -> Identity Service)
-> ((Int -> Identity Int) -> Retry -> Identity Retry)
-> (Int -> Identity Int)
-> Service
-> Identity Service
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Int -> Identity Int) -> Retry -> Identity Retry
Lens' Retry Int
retryAttempts ((Int -> Identity Int) -> Service -> Identity Service)
-> Int -> Service -> Service
forall s t a b. ASetter s t a b -> b -> s -> t
.~ Int
0)

-- | Scope an action such that any HTTP response will use this timeout value.
--
-- Default timeouts are chosen by considering:
--
-- * This 'timeout', if set.
--
-- * The related 'Service' timeout for the sent request if set. (Usually 70s)
--
-- * The 'envManager' timeout if set.
--
-- * The default 'ClientRequest' timeout. (Approximately 30s)
timeout :: Seconds -> Env -> Env
timeout :: Seconds -> Env -> Env
timeout Seconds
n = (Service -> Service) -> Env -> Env
override ((Maybe Seconds -> Identity (Maybe Seconds))
-> Service -> Identity Service
Lens' Service (Maybe Seconds)
serviceTimeout ((Maybe Seconds -> Identity (Maybe Seconds))
 -> Service -> Identity Service)
-> Seconds -> Service -> Service
forall s t a b. ASetter s t a (Maybe b) -> b -> s -> t
?~ Seconds
n)

-- $envLenses
--
-- We provide lenses for 'Env'', though you are of course free to use
-- the @generic-lens@ package.

envRegion :: Lens' (Env' withAuth) Region
envRegion :: (Region -> f Region) -> Env' withAuth -> f (Env' withAuth)
envRegion Region -> f Region
f Env' withAuth
env = Region -> f Region
f (Env' withAuth -> Region
forall (withAuth :: * -> *). Env' withAuth -> Region
_envRegion Env' withAuth
env) f Region -> (Region -> Env' withAuth) -> f (Env' withAuth)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Region
r -> Env' withAuth
env {$sel:_envRegion:Env :: Region
_envRegion = Region
r}

envLogger :: Lens' (Env' withAuth) Logger
envLogger :: (Logger -> f Logger) -> Env' withAuth -> f (Env' withAuth)
envLogger Logger -> f Logger
f Env' withAuth
env = Logger -> f Logger
f (Env' withAuth -> Logger
forall (withAuth :: * -> *). Env' withAuth -> Logger
_envLogger Env' withAuth
env) f Logger -> (Logger -> Env' withAuth) -> f (Env' withAuth)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Logger
l -> Env' withAuth
env {$sel:_envLogger:Env :: Logger
_envLogger = Logger
l}

envRetryCheck :: Lens' (Env' withAuth) (Int -> Client.HttpException -> Bool)
envRetryCheck :: ((Int -> HttpException -> Bool)
 -> f (Int -> HttpException -> Bool))
-> Env' withAuth -> f (Env' withAuth)
envRetryCheck (Int -> HttpException -> Bool) -> f (Int -> HttpException -> Bool)
f Env' withAuth
env =
  (Int -> HttpException -> Bool) -> f (Int -> HttpException -> Bool)
f (Env' withAuth -> Int -> HttpException -> Bool
forall (withAuth :: * -> *).
Env' withAuth -> Int -> HttpException -> Bool
_envRetryCheck Env' withAuth
env) f (Int -> HttpException -> Bool)
-> ((Int -> HttpException -> Bool) -> Env' withAuth)
-> f (Env' withAuth)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Int -> HttpException -> Bool
rc -> Env' withAuth
env {$sel:_envRetryCheck:Env :: Int -> HttpException -> Bool
_envRetryCheck = Int -> HttpException -> Bool
rc}

envOverride :: Lens' (Env' withAuth) (Dual (Endo Service))
envOverride :: (Dual (Endo Service) -> f (Dual (Endo Service)))
-> Env' withAuth -> f (Env' withAuth)
envOverride Dual (Endo Service) -> f (Dual (Endo Service))
f Env' withAuth
env = Dual (Endo Service) -> f (Dual (Endo Service))
f (Env' withAuth -> Dual (Endo Service)
forall (withAuth :: * -> *). Env' withAuth -> Dual (Endo Service)
_envOverride Env' withAuth
env) f (Dual (Endo Service))
-> (Dual (Endo Service) -> Env' withAuth) -> f (Env' withAuth)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Dual (Endo Service)
o -> Env' withAuth
env {$sel:_envOverride:Env :: Dual (Endo Service)
_envOverride = Dual (Endo Service)
o}

envManager :: Lens' (Env' withAuth) Client.Manager
envManager :: (Manager -> f Manager) -> Env' withAuth -> f (Env' withAuth)
envManager Manager -> f Manager
f Env' withAuth
env = Manager -> f Manager
f (Env' withAuth -> Manager
forall (withAuth :: * -> *). Env' withAuth -> Manager
_envManager Env' withAuth
env) f Manager -> (Manager -> Env' withAuth) -> f (Env' withAuth)
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \Manager
m -> Env' withAuth
env {$sel:_envManager:Env :: Manager
_envManager = Manager
m}

envAuth ::
  Lens (Env' withAuth) (Env' withAuth') (withAuth Auth) (withAuth' Auth)
envAuth :: (withAuth Auth -> f (withAuth' Auth))
-> Env' withAuth -> f (Env' withAuth')
envAuth withAuth Auth -> f (withAuth' Auth)
f Env' withAuth
env = withAuth Auth -> f (withAuth' Auth)
f (Env' withAuth -> withAuth Auth
forall (withAuth :: * -> *). Env' withAuth -> withAuth Auth
_envAuth Env' withAuth
env) f (withAuth' Auth)
-> (withAuth' Auth -> Env' withAuth') -> f (Env' withAuth')
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \withAuth' Auth
a -> Env' withAuth
env {$sel:_envAuth:Env :: withAuth' Auth
_envAuth = withAuth' Auth
a}