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,45 @@ 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+ if connection_string_type == ConnectionStringType .LOCALHOST :
110+ return self .__get_local_connection_string ()
111+ elif connection_string_type == ConnectionStringType .NETWORK :
112+ return self .__get_external_connection_string ()
113+ else :
114+ raise ValueError (
115+ f"unrecognized connection string type { connection_string_type } , "
116+ f"Supported values are ConnectionStringType.LOCALHOST or ConnectionStringType.NETWORK "
117+ )
118+
119+ def __get_local_connection_string (self ) -> str :
120+ """Generates a connection string for Azurite accessible from the local host machine.
121+
122+ This connection string uses the Docker host IP address (obtained via
123+ :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`)
124+ and the dynamically exposed ports of the Azurite container. This ensures that
125+ clients running on the host can connect successfully to the Azurite services.
126+
127+ :return: The Azurite connection string for local host access.
128+ :rtype: str
129+ """
77130 host_ip = self .get_container_host_ip ()
78131 connection_string = (
79132 f"DefaultEndpointsProtocol=http;AccountName={ self .account_name } ;AccountKey={ self .account_key } ;"
@@ -96,6 +149,75 @@ def get_connection_string(self) -> str:
96149
97150 return connection_string
98151
152+ def __get_external_connection_string (self ) -> str :
153+ """Generates a connection string for Azurite, primarily optimized for
154+ inter-container communication within a custom Docker network.
155+
156+ This method attempts to provide the most suitable connection string
157+ based on the container's network configuration:
158+
159+ - **For Inter-Container Communication (Recommended):** If the Azurite container is
160+ part of a custom Docker network and has network aliases configured,
161+ the connection string will use the first network alias as the hostname
162+ and the internal container ports (e.g., #$#`http://<alias>:<internal_port>/<account_name>`#$#).
163+ This is the most efficient and robust way for other containers
164+ in the same network to connect to Azurite, leveraging Docker's internal DNS.
165+
166+ - **Fallback for Non-Networked/Aliased Scenarios:** If the container is
167+ not on a custom network with aliases (e.g., running on the default
168+ bridge network without explicit aliases), the method falls back to
169+ using the Docker host IP (obtained via
170+ :meth:`testcontainers.core.container.DockerContainer.get_container_host_ip`)
171+ and the dynamically exposed ports (e.g., #$#`http://<host_ip>:<exposed_port>/<account_name>`#$#).
172+ While this connection string is technically "external" to the container,
173+ it primarily facilitates connections *from the host machine*.
174+
175+ :return: The generated Azurite connection string.
176+ :rtype: str
177+ """
178+ # Check if we're on a custom network and have network aliases
179+ if hasattr (self , "_network" ) and self ._network and hasattr (self , "_network_aliases" ) and self ._network_aliases :
180+ # Use the first network alias for inter-container communication
181+ host_ip = self ._network_aliases [0 ]
182+ # When using network aliases, use the internal container ports
183+ blob_port = self .blob_service_port
184+ queue_port = self .queue_service_port
185+ table_port = self .table_service_port
186+ else :
187+ # Use the Docker host IP for external connections
188+ host_ip = self .get_container_host_ip ()
189+ # When using host IP, use the exposed ports
190+ blob_port = (
191+ self .get_exposed_port (self .blob_service_port )
192+ if self .blob_service_port in self .ports
193+ else self .blob_service_port
194+ )
195+ queue_port = (
196+ self .get_exposed_port (self .queue_service_port )
197+ if self .queue_service_port in self .ports
198+ else self .queue_service_port
199+ )
200+ table_port = (
201+ self .get_exposed_port (self .table_service_port )
202+ if self .table_service_port in self .ports
203+ else self .table_service_port
204+ )
205+
206+ connection_string = (
207+ f"DefaultEndpointsProtocol=http;AccountName={ self .account_name } ;AccountKey={ self .account_key } ;"
208+ )
209+
210+ if self .blob_service_port in self .ports :
211+ connection_string += f"BlobEndpoint=http://{ host_ip } :{ blob_port } /{ self .account_name } ;"
212+
213+ if self .queue_service_port in self .ports :
214+ connection_string += f"QueueEndpoint=http://{ host_ip } :{ queue_port } /{ self .account_name } ;"
215+
216+ if self .table_service_port in self .ports :
217+ connection_string += f"TableEndpoint=http://{ host_ip } :{ table_port } /{ self .account_name } ;"
218+
219+ return connection_string
220+
99221 def start (self ) -> "AzuriteContainer" :
100222 super ().start ()
101223 self ._connect ()
0 commit comments