@@ -27,11 +27,10 @@ class Auth(httpx.Auth):
2727 """
2828 Authenticator class for OpenStack connections.
2929 """
30- def __init__ (self , auth_url , application_credential_id , application_credential_secret , region_name ):
30+ def __init__ (self , auth_url , application_credential_id , application_credential_secret ):
3131 self .url = auth_url
3232 self ._application_credential_id = application_credential_id
3333 self ._application_credential_secret = application_credential_secret
34- self .region_name = region_name
3534 self ._token = None
3635 self ._user_id = None
3736 self ._lock = asyncio .Lock ()
@@ -154,11 +153,12 @@ class Cloud:
154153 """
155154 Object for interacting with OpenStack clouds.
156155 """
157- def __init__ (self , auth , transport , interface ):
156+ def __init__ (self , auth , transport , interface , region = None ):
158157 self ._auth = auth
159158 self ._transport = transport
160159 self ._interface = interface
161160 self ._endpoints = {}
161+ self ._region = region
162162 # A map of api name to client
163163 self ._clients = {}
164164
@@ -175,18 +175,7 @@ async def __aenter__(self):
175175 else :
176176 raise
177177 self ._endpoints = {
178- entry ["type" ]: next (
179- ep ["url" ]
180- for ep in entry ["endpoints" ]
181- if (
182- ep ["interface" ] == self ._interface
183- # NOTE(scott): Entrypoint has 'region_id' and 'region'
184- # fields whereas app cred has a 'region_name' field.
185- # This code assumes that app cred 'region_name' maps
186- # to catalog entry 'region' rather than 'region_id'.
187- and ep ["region" ] == self ._auth .region_name
188- )
189- )
178+ entry ["type" ]: self ._service_endpoint (entry )["url" ]
190179 for entry in response .json ()["catalog" ]
191180 if len (entry ["endpoints" ]) > 0
192181 }
@@ -229,6 +218,25 @@ def api_client(self, name, prefix = None):
229218 )
230219 return self ._clients [name ]
231220
221+ def _service_endpoint (self , endpoints ):
222+ """
223+ Filters the target cloud's catalog endpoints to find the relevant entry.
224+ """
225+ iface_endpoints = [ep for ep in endpoints if ep ["interface" ] == self ._interface ]
226+ # If there's no region_name field in the clouds.yaml we use the first endpoint which
227+ # matches the interface name for consistent behaviour with the OpenStack CLI.
228+ if not self ._region :
229+ return iface_endpoints [0 ]
230+ # Otherwise, further filter by region name
231+ region_endpoints = [ep for ep in iface_endpoints if ep ["region" ] == self ._region ]
232+ if len (region_endpoints ) != 1 :
233+ raise Exception (
234+ "Failed to find a unique catalog endpoints for"
235+ f" interface { region_endpoints [0 ]['interface' ]} "
236+ f" and region { region_endpoints [0 ]['region' ]} "
237+ )
238+ return region_endpoints [0 ]
239+
232240 @classmethod
233241 def from_clouds (cls , clouds , cloud , cacert ):
234242 config = clouds ["clouds" ][cloud ]
@@ -238,13 +246,13 @@ def from_clouds(cls, clouds, cloud, cacert):
238246 auth = Auth (
239247 auth_url ,
240248 config ["auth" ]["application_credential_id" ],
241- config ["auth" ]["application_credential_secret" ],
242- config ["region_name" ]
249+ config ["auth" ]["application_credential_secret" ]
243250 )
251+ region = config .get ("region_name" )
244252 # Create a default context using the verification from the config
245253 context = httpx .create_ssl_context (verify = config .get ("verify" , True ))
246254 # If a cacert was given, load it into the context
247255 if cacert is not None :
248256 context .load_verify_locations (cadata = cacert )
249257 transport = httpx .AsyncHTTPTransport (verify = context )
250- return cls (auth , transport , config .get ("interface" , "public" ))
258+ return cls (auth , transport , config .get ("interface" , "public" ), region )
0 commit comments