1010# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1111# License for the specific language governing permissions and limitations
1212# under the License.
13+ import enum
1314import os
1415import socket
1516from typing import Optional
1920from testcontainers .core .waiting_utils import wait_container_is_ready
2021
2122
23+ class ConnectionStringType (enum .Enum ):
24+ """
25+ Enumeration for specifying the type of connection string to generate for Azurite.
26+
27+ :cvar LOCALHOST: Represents a connection string for access from the host machine
28+ where the tests are running.
29+ :cvar NETWORK: Represents a connection string for access from another container
30+ within the same Docker network as the Azurite container.
31+ """
32+
33+ LOCALHOST = "localhost"
34+ NETWORK = "network"
35+
36+
2237class AzuriteContainer (DockerContainer ):
2338 """
2439 The example below spins up an Azurite container and
@@ -73,7 +88,46 @@ def __init__(
7388 self .with_exposed_ports (blob_service_port , queue_service_port , table_service_port )
7489 self .with_env ("AZURITE_ACCOUNTS" , f"{ self .account_name } :{ self .account_key } " )
7590
76- def get_connection_string (self ) -> str :
91+ def get_connection_string (
92+ self , connection_string_type : ConnectionStringType = ConnectionStringType .LOCALHOST
93+ ) -> str :
94+ """Retrieves the appropriate connection string for the Azurite container based on the specified access type.
95+
96+ This method acts as a dispatcher, returning a connection string optimized
97+ either for access from the host machine or for inter-container communication within the same Docker network.
98+
99+ :param connection_string_type: The type of connection string to generate.
100+ Use :attr:`ConnectionStringType.LOCALHOST` for connections
101+ from the machine running the tests (default), or
102+ :attr:`ConnectionStringType.NETWORK` for connections
103+ from other containers within the same Docker network.
104+ :type connection_string_type: ConnectionStringType
105+ :return: The generated Azurite connection string.
106+ :rtype: str
107+ :raises ValueError: If an unrecognized `connection_string_type` is provided.
108+ """
109+ match connection_string_type :
110+ case ConnectionStringType .LOCALHOST :
111+ return self .__get_local_connection_string ()
112+ case ConnectionStringType .NETWORK :
113+ return self .__get_external_connection_string ()
114+ case _:
115+ raise ValueError (
116+ f"unrecognized connection string type { connection_string_type } , "
117+ f"Supported values are ConnectionStringType.LOCALHOST or ConnectionStringType.NETWORK "
118+ )
119+
120+ def __get_local_connection_string (self ) -> str :
121+ """Generates a connection string for Azurite accessible from the local host machine.
122+
123+ This connection string uses the Docker host IP address (obtained via
124+ :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`)
125+ and the dynamically exposed ports of the Azurite container. This ensures that
126+ clients running on the host can connect successfully to the Azurite services.
127+
128+ :return: The Azurite connection string for local host access.
129+ :rtype: str
130+ """
77131 host_ip = self .get_container_host_ip ()
78132 connection_string = (
79133 f"DefaultEndpointsProtocol=http;AccountName={ self .account_name } ;AccountKey={ self .account_key } ;"
@@ -96,6 +150,75 @@ def get_connection_string(self) -> str:
96150
97151 return connection_string
98152
153+ def __get_external_connection_string (self ) -> str :
154+ """Generates a connection string for Azurite, primarily optimized for
155+ inter-container communication within a custom Docker network.
156+
157+ This method attempts to provide the most suitable connection string
158+ based on the container's network configuration:
159+
160+ - **For Inter-Container Communication (Recommended):** If the Azurite container is
161+ part of a custom Docker network and has network aliases configured,
162+ the connection string will use the first network alias as the hostname
163+ and the internal container ports (e.g., #$#`http://<alias>:<internal_port>/<account_name>`#$#).
164+ This is the most efficient and robust way for other containers
165+ in the same network to connect to Azurite, leveraging Docker's internal DNS.
166+
167+ - **Fallback for Non-Networked/Aliased Scenarios:** If the container is
168+ not on a custom network with aliases (e.g., running on the default
169+ bridge network without explicit aliases), the method falls back to
170+ using the Docker host IP (obtained via
171+ :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`)
172+ and the dynamically exposed ports (e.g., #$#`http://<host_ip>:<exposed_port>/<account_name>`#$#).
173+ While this connection string is technically "external" to the container,
174+ it primarily facilitates connections *from the host machine*.
175+
176+ :return: The generated Azurite connection string.
177+ :rtype: str
178+ """
179+ # Check if we're on a custom network and have network aliases
180+ if hasattr (self , "_network" ) and self ._network and hasattr (self , "_network_aliases" ) and self ._network_aliases :
181+ # Use the first network alias for inter-container communication
182+ host_ip = self ._network_aliases [0 ]
183+ # When using network aliases, use the internal container ports
184+ blob_port = self .blob_service_port
185+ queue_port = self .queue_service_port
186+ table_port = self .table_service_port
187+ else :
188+ # Use the Docker host IP for external connections
189+ host_ip = self .get_container_host_ip ()
190+ # When using host IP, use the exposed ports
191+ blob_port = (
192+ self .get_exposed_port (self .blob_service_port )
193+ if self .blob_service_port in self .ports
194+ else self .blob_service_port
195+ )
196+ queue_port = (
197+ self .get_exposed_port (self .queue_service_port )
198+ if self .queue_service_port in self .ports
199+ else self .queue_service_port
200+ )
201+ table_port = (
202+ self .get_exposed_port (self .table_service_port )
203+ if self .table_service_port in self .ports
204+ else self .table_service_port
205+ )
206+
207+ connection_string = (
208+ f"DefaultEndpointsProtocol=http;AccountName={ self .account_name } ;AccountKey={ self .account_key } ;"
209+ )
210+
211+ if self .blob_service_port in self .ports :
212+ connection_string += f"BlobEndpoint=http://{ host_ip } :{ blob_port } /{ self .account_name } ;"
213+
214+ if self .queue_service_port in self .ports :
215+ connection_string += f"QueueEndpoint=http://{ host_ip } :{ queue_port } /{ self .account_name } ;"
216+
217+ if self .table_service_port in self .ports :
218+ connection_string += f"TableEndpoint=http://{ host_ip } :{ table_port } /{ self .account_name } ;"
219+
220+ return connection_string
221+
99222 def start (self ) -> "AzuriteContainer" :
100223 super ().start ()
101224 self ._connect ()
0 commit comments