Skip to content

Support setting endpoints from environment variables#1013

Open
ynishinaka wants to merge 2 commits into
brendanhay:mainfrom
ynishinaka:endpoint-env
Open

Support setting endpoints from environment variables#1013
ynishinaka wants to merge 2 commits into
brendanhay:mainfrom
ynishinaka:endpoint-env

Conversation

@ynishinaka

Copy link
Copy Markdown

Partially solves #980.

The added customEndpoints loads environment variables AWS_ENDPOINT_URL and AWS_ENDPOINT_URL_<SERVICE> if any to provide a function to override default endpoints.

Intended to be used as follows:

main = do
  env <- AWS.overrideService <$>
    AWS.customEndpoints <*>
    AWS.newEnv AWS.discover

@chreekat

Copy link
Copy Markdown
Contributor

Nice to see I'm not the only person who could use this. :)

I think this is a good idea, but I don't think user code should have to be modified to support it. It should be default behavior as it is with other SDKs. One way to do that would be to make customEndpoints the default value for overrides in newEnvNoAuthFromManager, instead of the current default value id.

The only question I would have is whether there should be a grace period where a user should opt in by setting some var like AMAZONKA_USE_ENDPOINT_VARS=1 in the environment.

The reason for a grace period is that suddenly supporting the vars is a change in behavior and could break some installations.

Something like:

  1. amazonka 2.x: forward-compat runtime warning "In the next version, amazonka will support service-specific endpoint env variables by default. Enable them now, and silence this warning, by setting AMAZONKA_USE_ENDPOINT_VARS=1 in your environment."

  2. amazonka 2.(x+1): support them by default, drop the warning, ignore AMAZONKA_USE_ENDPOINT_VARS.

On the other hand, maybe a grace period isn't necessary.

@chreekat

chreekat commented May 15, 2025

Copy link
Copy Markdown
Contributor

Here's my patch that enables the endpoints by default:

diff --git a/lib/amazonka/src/Amazonka/Env.hs b/lib/amazonka/src/Amazonka/Env.hs
index 516d8fb0727..bf0f4b72044 100644
--- a/lib/amazonka/src/Amazonka/Env.hs
+++ b/lib/amazonka/src/Amazonka/Env.hs
@@ -43,12 +43,15 @@ module Amazonka.Env
 where
 
 import Amazonka.Core.Lens.Internal (Lens)
+import Amazonka.Data.Text (toText)
 import Amazonka.Env.Hooks (Hooks, addLoggingHooks, noHooks)
 import Amazonka.Logger (Logger)
 import Amazonka.Prelude
 import Amazonka.Types hiding (timeout)
+import qualified Amazonka.Endpoint as Endpoint
 import qualified Amazonka.Types as Service (Service (..))
 import qualified Data.Function as Function
+import qualified Data.List as List
 import qualified Data.Text as Text
 import qualified Network.HTTP.Client as Client
 import qualified Network.HTTP.Conduit as Client.Conduit
@@ -160,17 +163,47 @@ newEnvNoAuth =
 newEnvNoAuthFromManager :: MonadIO m => Client.Manager -> m EnvNoAuth
 newEnvNoAuthFromManager manager = do
   mRegion <- lookupRegion
+  endpointOverrides <- customEndpoints
   pure
     Env
       { region = fromMaybe NorthVirginia mRegion,
         logger = \_ _ -> pure (),
         hooks = addLoggingHooks noHooks,
         retryCheck = retryConnectionFailure 3,
-        overrides = id,
+        overrides = endpointOverrides,
         manager,
         auth = Proxy
       }
 
+-- | Retrieve custom endpoints from environment variables:
+--
+-- * @AWS_ENDPOINT_URL@
+-- * @AWS_ENDPOINT_URL_<SERVICE>@
+--
+-- The latter takes precedence over the former.
+customEndpoints :: (MonadIO m) => m (Service -> Service)
+customEndpoints = do
+  environment <- liftIO Environment.getEnvironment
+  let globalUrl = lookup "AWS_ENDPOINT_URL" environment >>= Client.parseUrlThrow
+  let serviceUrls = mapMaybe (\(k, v) -> (,v) <$> removePrefix "AWS_ENDPOINT_URL_" k) environment
+  let override s =
+        case lookup (Text.unpack . Text.toLower . toText $ Service.abbrev s) serviceUrls of
+          Just x -> setEndpointMaybe (Client.parseUrlThrow x) s
+          Nothing -> setEndpointMaybe globalUrl s
+  pure override
+  where
+    removePrefix :: String -> String -> Maybe String
+    removePrefix prefix s =
+      if prefix `List.isPrefixOf` s
+        then Just $ drop (length prefix) s
+        else Nothing
+
+    setEndpointMaybe :: Maybe Client.Request -> Service -> Service
+    setEndpointMaybe mreq s =
+      case mreq of
+        Just req -> Endpoint.setEndpoint (Client.secure req) (Client.host req) (Client.port req) s
+        Nothing -> s
+
 -- | Get "the" 'Auth' from an 'Env'', if we can.
 authMaybe :: Foldable withAuth => Env' withAuth -> Maybe Auth
 authMaybe = foldr (const . Just) Nothing . auth

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants