From 747499ef87edf9e6025ebf9e1352a389d48306df Mon Sep 17 00:00:00 2001 From: sepi Date: Wed, 22 Mar 2017 14:14:50 +0200 Subject: [PATCH 01/16] GenericContainer added --- testcontainers/core/generic.py | 5 +++++ tests/test_db_containers.py | 3 ++- tests/test_new_docker_api.py | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 75baac3d8..55a61097e 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -55,3 +55,8 @@ def start(self): def _configure(self): raise NotImplementedError + + +class GenericContainer(DockerContainer): + def __init__(self, image): + super(GenericContainer, self).__init__(image) diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index 3aa3c3348..e85951fb9 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -2,6 +2,7 @@ from pymongo import MongoClient from testcontainers.core.container import DockerContainer +from testcontainers.core.generic import GenericContainer from testcontainers.core.waiting_utils import wait_container_is_ready from testcontainers.mysql import MySqlContainer, MariaDbContainer from testcontainers.postgres import PostgresContainer @@ -35,7 +36,7 @@ def test_docker_run_mariadb(): def test_docker_generic_db(): - mongo_container = DockerContainer("mongo:latest") + mongo_container = GenericContainer("mongo:latest") mongo_container.expose_port(27017, 27017) with mongo_container: diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index ebf4ea3d7..a5b5afc48 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -1,11 +1,11 @@ from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager -from testcontainers.core.container import DockerContainer +from testcontainers.core.generic import GenericContainer def test_docker_custom_image(): - container = DockerContainer("spirogov/video_service:latest").expose_port(8086, 8086) + container = GenericContainer("spirogov/video_service:latest").expose_port(8086, 8086) with container: driver = webdriver.Chrome(ChromeDriverManager().install()) From a9ff782a1e011f5fc5cc77094312f173ee536f2a Mon Sep 17 00:00:00 2001 From: sepi Date: Wed, 22 Mar 2017 15:39:11 +0200 Subject: [PATCH 02/16] DockerContainer additional methods --- testcontainers/core/container.py | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 64cb5d025..4d1f42608 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -1,7 +1,9 @@ import blindspin import crayons +from docker.models.containers import Container from testcontainers.core.docker_client import DockerClient +from testcontainers.core.exceptions import ContainerStartException from testcontainers.core.utils import is_windows @@ -12,6 +14,7 @@ def __init__(self, image): self._docker = DockerClient() self.image = image self._container = None + self._command = None def add_env(self, key, value): self.env[key] = value @@ -29,17 +32,18 @@ def start(self): print("") print("{} {}".format(crayons.yellow("Pulling image"), crayons.red(self.image))) with blindspin.spinner(): - self._container = self._docker.run(self.image, - detach=True, - environment=self.env, - ports=self.ports, - publish_all_ports=True) + self._container = self.get_docker_client().run(self.image, + command=self._command, + detach=True, + environment=self.env, + ports=self.ports, + publish_all_ports=True) print("") - print("Container started: ", crayons.yellow(self._container.id, bold=True)) + print("Container started: ", crayons.yellow(self._container.short_id, bold=True)) return self def stop(self): - self._container.remove(force=True) + self.get_wrapped_contaner().remove(force=True) def __enter__(self): return self.start() @@ -54,4 +58,18 @@ def get_container_host_ip(self) -> str: return "0.0.0.0" def get_exposed_port(self, port) -> str: - return self._docker.port(self._container.id, port) + return self.get_docker_client().port(self._container.id, port) + + def with_command(self, command): + self._command = command + + def get_wrapped_contaner(self) -> Container: + return self._container + + def get_docker_client(self) -> DockerClient: + return self._docker + + def exec(self, command): + if not self._container: + raise ContainerStartException("Container should be started before") + return self.get_wrapped_contaner().exec_run(command) From 7eaf522f18fb2046d76c243e68ccd0697ae1ba24 Mon Sep 17 00:00:00 2001 From: sepi Date: Wed, 22 Mar 2017 15:53:49 +0200 Subject: [PATCH 03/16] mysql environ variables added --- testcontainers/mysql.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index dfeec3d26..7242d4da5 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -10,25 +10,31 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +from os import environ + from testcontainers.core.generic import DbContainer class MySqlContainer(DbContainer): + + MYSQL_ROOT_PASSWORD = environ.get("MYSQL_ROOT_PASSWORD", "test") + MYSQL_DATABASE = environ.get("MYSQL_DATABASE", "test") + MYSQL_USER = environ.get("MYSQL_USER", "test") + MYSQL_PASSWORD = environ.get("MYSQL_PASSWORD", "test") + def __init__(self, image="mysql:latest"): super(MySqlContainer, self).__init__(image, dialect="mysql+pymysql", - username="test", - password="test", + username=self.MYSQL_ROOT_PASSWORD, + password=self.MYSQL_PASSWORD, port=3306, - db_name="test") - self.root_password = "test" - self.host_port = 3306 + db_name=self.MYSQL_DATABASE) def _configure(self): - self.add_env("MYSQL_ROOT_PASSWORD", self.root_password) - self.add_env("MYSQL_DATABASE", self.db_name) - self.add_env("MYSQL_USER", self.username) - self.add_env("MYSQL_PASSWORD", self.password) + self.add_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) + self.add_env("MYSQL_DATABASE", self.MYSQL_DATABASE) + self.add_env("MYSQL_USER", self.MYSQL_USER) + self.add_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) class MariaDbContainer(MySqlContainer): From 3a34969cd7c4bf1ce1023c3420c706720b2ee26d Mon Sep 17 00:00:00 2001 From: sepi Date: Wed, 22 Mar 2017 17:31:03 +0200 Subject: [PATCH 04/16] mysql environ variables added --- testcontainers/core/container.py | 2 +- testcontainers/core/generic.py | 24 +++++++++--------------- testcontainers/mysql.py | 16 +++++++++------- tests/test_db_containers.py | 2 +- tests/test_new_docker_api.py | 15 +++++++++++++-- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 4d1f42608..5c4c0e2d8 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -20,7 +20,7 @@ def add_env(self, key, value): self.env[key] = value return self - def expose_port(self, container, host=None): + def bind_ports(self, container, host=None): self.ports[container] = host return self diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 55a61097e..92d974407 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -17,17 +17,8 @@ class DbContainer(DockerContainer): - def __init__(self, image, dialect, - username, - password, - port, - db_name): + def __init__(self, image): super(DbContainer, self).__init__(image) - self.dialect = dialect - self.username = username - self.password = password - self.port = port - self.db_name = db_name @wait_container_is_ready() def _connect(self): @@ -39,14 +30,17 @@ def _connect(self): engine.connect() def get_connection_url(self): + raise NotImplementedError + + def create_connection_url(self, dialect, username, password, port, db_name): return "{dialect}://{username}" \ ":{password}@{host}:" \ - "{port}/{db}".format(dialect=self.dialect, - username=self.username, - password=self.password, + "{port}/{db}".format(dialect=dialect, + username=username, + password=password, host=self.get_container_host_ip(), - port=self.get_exposed_port(self.port), - db=self.db_name) + port=self.get_exposed_port(port), + db=db_name) def start(self): super().start() diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 7242d4da5..501fb2339 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -16,19 +16,14 @@ class MySqlContainer(DbContainer): - MYSQL_ROOT_PASSWORD = environ.get("MYSQL_ROOT_PASSWORD", "test") MYSQL_DATABASE = environ.get("MYSQL_DATABASE", "test") MYSQL_USER = environ.get("MYSQL_USER", "test") MYSQL_PASSWORD = environ.get("MYSQL_PASSWORD", "test") def __init__(self, image="mysql:latest"): - super(MySqlContainer, self).__init__(image, - dialect="mysql+pymysql", - username=self.MYSQL_ROOT_PASSWORD, - password=self.MYSQL_PASSWORD, - port=3306, - db_name=self.MYSQL_DATABASE) + super(MySqlContainer, self).__init__(image) + self.port_to_expose = 3306 def _configure(self): self.add_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) @@ -36,6 +31,13 @@ def _configure(self): self.add_env("MYSQL_USER", self.MYSQL_USER) self.add_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) + def get_connection_url(self): + return super().create_connection_url(dialect="mysql+pymysql", + username=self.MYSQL_USER, + password=self.MYSQL_PASSWORD, + db_name=self.MYSQL_DATABASE, + port=self.port_to_expose) + class MariaDbContainer(MySqlContainer): def __init__(self, image="mariadb:latest"): diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index e85951fb9..b9fa78ee0 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -37,7 +37,7 @@ def test_docker_run_mariadb(): def test_docker_generic_db(): mongo_container = GenericContainer("mongo:latest") - mongo_container.expose_port(27017, 27017) + mongo_container.bind_ports(27017, 27017) with mongo_container: @wait_container_is_ready() diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index a5b5afc48..98c1634ec 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -2,13 +2,24 @@ from webdriver_manager.chrome import ChromeDriverManager from testcontainers.core.generic import GenericContainer +from testcontainers.mysql import MySqlContainer def test_docker_custom_image(): - container = GenericContainer("spirogov/video_service:latest").expose_port(8086, 8086) + container = GenericContainer("spirogov/video_service:latest").bind_ports(8086, 8086) with container: driver = webdriver.Chrome(ChromeDriverManager().install()) driver.implicitly_wait(10) driver.get("http://localhost:8086") - driver.find_element_by_css_selector("#inputEmail3").send_keys("admin") \ No newline at end of file + driver.find_element_by_css_selector("#inputEmail3").send_keys("admin") + + +def test_docker_env_variables(): + mysql = MySqlContainer() + mysql.MYSQL_DATABASE = "custom_db" + mysql.bind_ports(3306, 32785) + + with mysql: + url = mysql.get_connection_url() + assert url == 'mysql+pymysql://test:test@0.0.0.0:32785/custom_db' From 3d61ad0b269abddf7bbc93a72e6f426cc280983e Mon Sep 17 00:00:00 2001 From: sergey Date: Wed, 22 Mar 2017 20:41:22 +0200 Subject: [PATCH 05/16] test for env variables added --- Pipfile | 1 - tests/test_new_docker_api.py | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Pipfile b/Pipfile index 4c6bf222e..30c81ea15 100644 --- a/Pipfile +++ b/Pipfile @@ -15,5 +15,4 @@ docker = "*" pymongo = "*" selenium = "*" SQLAlchemy = "*" -PyYAML = "*" PyMySQL = "*" diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 98c1634ec..5cfcd47e3 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -1,7 +1,13 @@ +import os + from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from testcontainers.core.generic import GenericContainer + +os.environ["MYSQL_USER"] = "demo" +os.environ["MYSQL_DATABASE"] = "custom_db" + from testcontainers.mysql import MySqlContainer @@ -17,9 +23,8 @@ def test_docker_custom_image(): def test_docker_env_variables(): mysql = MySqlContainer() - mysql.MYSQL_DATABASE = "custom_db" mysql.bind_ports(3306, 32785) - + print(mysql.MYSQL_USER, mysql.MYSQL_DATABASE) with mysql: url = mysql.get_connection_url() - assert url == 'mysql+pymysql://test:test@0.0.0.0:32785/custom_db' + assert url == 'mysql+pymysql://demo:test@0.0.0.0:32785/custom_db' From b40eb9414d5b4208c831a82dcd44e46ee62034fb Mon Sep 17 00:00:00 2001 From: sergey Date: Wed, 22 Mar 2017 21:07:19 +0200 Subject: [PATCH 06/16] test for env variables fixed --- tests/test_new_docker_api.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 5cfcd47e3..d2cbc8bed 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -3,12 +3,15 @@ from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager +from testcontainers import mysql + from testcontainers.core.generic import GenericContainer -os.environ["MYSQL_USER"] = "demo" -os.environ["MYSQL_DATABASE"] = "custom_db" +from importlib import reload -from testcontainers.mysql import MySqlContainer +def setup_module(m): + os.environ["MYSQL_USER"] = "demo" + os.environ["MYSQL_DATABASE"] = "custom_db" def test_docker_custom_image(): @@ -22,9 +25,11 @@ def test_docker_custom_image(): def test_docker_env_variables(): - mysql = MySqlContainer() - mysql.bind_ports(3306, 32785) - print(mysql.MYSQL_USER, mysql.MYSQL_DATABASE) - with mysql: - url = mysql.get_connection_url() + reload(mysql) + + db = mysql.MySqlContainer() + db.bind_ports(3306, 32785) + print(db.MYSQL_USER, db.MYSQL_DATABASE) + with db: + url = db.get_connection_url() assert url == 'mysql+pymysql://demo:test@0.0.0.0:32785/custom_db' From 0c1b2874d383d77d994b53944e826fe83577d7cd Mon Sep 17 00:00:00 2001 From: sergey Date: Wed, 22 Mar 2017 21:22:25 +0200 Subject: [PATCH 07/16] postgres fix --- testcontainers/core/generic.py | 2 +- testcontainers/mysql.py | 10 +++++----- testcontainers/postgres.py | 27 +++++++++++++++++---------- tests/test_new_docker_api.py | 5 +++-- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 92d974407..198f645da 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -32,7 +32,7 @@ def _connect(self): def get_connection_url(self): raise NotImplementedError - def create_connection_url(self, dialect, username, password, port, db_name): + def _create_connection_url(self, dialect, username, password, port, db_name): return "{dialect}://{username}" \ ":{password}@{host}:" \ "{port}/{db}".format(dialect=dialect, diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 501fb2339..3af362659 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -32,11 +32,11 @@ def _configure(self): self.add_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) def get_connection_url(self): - return super().create_connection_url(dialect="mysql+pymysql", - username=self.MYSQL_USER, - password=self.MYSQL_PASSWORD, - db_name=self.MYSQL_DATABASE, - port=self.port_to_expose) + return super()._create_connection_url(dialect="mysql+pymysql", + username=self.MYSQL_USER, + password=self.MYSQL_PASSWORD, + db_name=self.MYSQL_DATABASE, + port=self.port_to_expose) class MariaDbContainer(MySqlContainer): diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 83644f296..0932f42e1 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -10,21 +10,28 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import os from testcontainers.core.generic import DbContainer class PostgresContainer(DbContainer): + POSTGRES_USER = os.environ.get("POSTGRES_USER", "test") + POSTGRES_PASSWORD = os.environ.get("POSTGRES_PASSWORD", "test") + POSTGRES_DB = os.environ.get("POSTGRES_DB", "test") + def __init__(self, image="postgres:latest"): - super(PostgresContainer, self).__init__(image=image, - dialect="postgresql+psycopg2", - username="test", - password="test", - db_name="test", - port=5432) - self.host_port = 5432 + super(PostgresContainer, self).__init__(image=image) + self.port_to_expose = 5432 def _configure(self): - self.add_env("POSTGRES_USER", self.username) - self.add_env("POSTGRES_PASSWORD", self.password) - self.add_env("POSTGRES_DB", self.db_name) + self.add_env("POSTGRES_USER", self.POSTGRES_USER) + self.add_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) + self.add_env("POSTGRES_DB", self.POSTGRES_DB) + + def get_connection_url(self): + super()._create_connection_url(dialect="postgresql+psycopg2", + username=self.POSTGRES_USER, + password=self.POSTGRES_PASSWORD, + db_name=self.POSTGRES_DB, + port=self.port_to_expose) diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index d2cbc8bed..fd0b6dd1c 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -9,13 +9,15 @@ from importlib import reload + def setup_module(m): os.environ["MYSQL_USER"] = "demo" os.environ["MYSQL_DATABASE"] = "custom_db" def test_docker_custom_image(): - container = GenericContainer("spirogov/video_service:latest").bind_ports(8086, 8086) + container = GenericContainer("spirogov/video_service:latest") + container.bind_ports(8086, 8086) with container: driver = webdriver.Chrome(ChromeDriverManager().install()) @@ -29,7 +31,6 @@ def test_docker_env_variables(): db = mysql.MySqlContainer() db.bind_ports(3306, 32785) - print(db.MYSQL_USER, db.MYSQL_DATABASE) with db: url = db.get_connection_url() assert url == 'mysql+pymysql://demo:test@0.0.0.0:32785/custom_db' From 1d31a5288e299f6cd2f4f0ae0a18bd06b9f17ebf Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 13:58:16 +0200 Subject: [PATCH 08/16] postgres get_connection_url fix --- testcontainers/postgres.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 0932f42e1..bf502ac52 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -30,8 +30,8 @@ def _configure(self): self.add_env("POSTGRES_DB", self.POSTGRES_DB) def get_connection_url(self): - super()._create_connection_url(dialect="postgresql+psycopg2", - username=self.POSTGRES_USER, - password=self.POSTGRES_PASSWORD, - db_name=self.POSTGRES_DB, - port=self.port_to_expose) + return super()._create_connection_url(dialect="postgresql+psycopg2", + username=self.POSTGRES_USER, + password=self.POSTGRES_PASSWORD, + db_name=self.POSTGRES_DB, + port=self.port_to_expose) From 4e82ef28a51f136478ac18e4f7accc1f53acb04e Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 14:51:36 +0200 Subject: [PATCH 09/16] release process description changed --- release-process.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/release-process.rst b/release-process.rst index 0b4a9d5ca..24ac23aab 100644 --- a/release-process.rst +++ b/release-process.rst @@ -66,7 +66,6 @@ Submit release to PYPI Steps:: - python setup.py bdist_wheel register - python setup.py bdist_wheel upload + twine dist/* upload From b2cc8175b5432729c8f37ffee1b2c28d4feefc9e Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 16:18:47 +0200 Subject: [PATCH 10/16] with_name added to docker container class --- testcontainers/core/container.py | 9 ++++++++- tests/test_new_docker_api.py | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 5c4c0e2d8..989b9a980 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -15,6 +15,7 @@ def __init__(self, image): self.image = image self._container = None self._command = None + self._name = None def add_env(self, key, value): self.env[key] = value @@ -37,7 +38,8 @@ def start(self): detach=True, environment=self.env, ports=self.ports, - publish_all_ports=True) + publish_all_ports=True, + name=self._name) print("") print("Container started: ", crayons.yellow(self._container.short_id, bold=True)) return self @@ -62,6 +64,11 @@ def get_exposed_port(self, port) -> str: def with_command(self, command): self._command = command + return self + + def with_name(self, name): + self._name = name + return self def get_wrapped_contaner(self) -> Container: return self._container diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index fd0b6dd1c..9ae8d7cf0 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -18,6 +18,7 @@ def setup_module(m): def test_docker_custom_image(): container = GenericContainer("spirogov/video_service:latest") container.bind_ports(8086, 8086) + container.with_name("video_service") with container: driver = webdriver.Chrome(ChromeDriverManager().install()) From c5294e82224f9ddaa1ab9aae8d80ba0acdb14ee1 Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 17:27:06 +0200 Subject: [PATCH 11/16] with_mount_volumes added to docker container --- testcontainers/core/container.py | 13 ++++++++++--- tests/test_new_docker_api.py | 5 +++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 989b9a980..78cf9b3f8 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -11,8 +11,9 @@ class DockerContainer(object): def __init__(self, image): self.env = {} self.ports = {} - self._docker = DockerClient() + self.volumes = {} self.image = image + self._docker = DockerClient() self._container = None self._command = None self._name = None @@ -21,7 +22,7 @@ def add_env(self, key, value): self.env[key] = value return self - def bind_ports(self, container, host=None): + def with_bind_ports(self, container, host=None): self.ports[container] = host return self @@ -39,7 +40,8 @@ def start(self): environment=self.env, ports=self.ports, publish_all_ports=True, - name=self._name) + name=self._name, + volumes=self.volumes) print("") print("Container started: ", crayons.yellow(self._container.short_id, bold=True)) return self @@ -70,6 +72,11 @@ def with_name(self, name): self._name = name return self + def with_volume_mapping(self, host, container, mode='ro'): + # '/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'} + mapping = {'bind': container, 'mode': mode} + self.volumes[host] = mapping + def get_wrapped_contaner(self) -> Container: return self._container diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 9ae8d7cf0..485ac8f39 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -17,8 +17,9 @@ def setup_module(m): def test_docker_custom_image(): container = GenericContainer("spirogov/video_service:latest") - container.bind_ports(8086, 8086) - container.with_name("video_service") + container.with_bind_ports(8086, 8086) + #container.with_name("video_service") + container.with_volume_mapping("/Users/sepi/auto_env", "/root/video", mode='rw') with container: driver = webdriver.Chrome(ChromeDriverManager().install()) From 8344e789b9aeefea09f21ee7df10a6eb7ebf3bfd Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 17:32:19 +0200 Subject: [PATCH 12/16] test fix for port bindings --- tests/test_new_docker_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 485ac8f39..7d0cda897 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -32,7 +32,7 @@ def test_docker_env_variables(): reload(mysql) db = mysql.MySqlContainer() - db.bind_ports(3306, 32785) + db.with_bind_ports(3306, 32785) with db: url = db.get_connection_url() assert url == 'mysql+pymysql://demo:test@0.0.0.0:32785/custom_db' From a87d85d8840f152231908d2f9758006b0882431f Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 17:56:21 +0200 Subject: [PATCH 13/16] with_exposed_ports added --- testcontainers/__init__.py | 6 ++++++ testcontainers/core/container.py | 6 +++++- testcontainers/mysql.py | 1 + testcontainers/postgres.py | 1 + testcontainers/selenium.py | 7 ++++--- tests/test_new_docker_api.py | 5 +++-- 6 files changed, 20 insertions(+), 6 deletions(-) diff --git a/testcontainers/__init__.py b/testcontainers/__init__.py index e69de29bb..bad4cdb9b 100644 --- a/testcontainers/__init__.py +++ b/testcontainers/__init__.py @@ -0,0 +1,6 @@ +from testcontainers.selenium import BrowserWebDriverContainer +from testcontainers.mysql import MySqlContainer +from testcontainers.postgres import PostgresContainer +from testcontainers.oracle import OracleDbContainer +from testcontainers.core.generic import GenericContainer +from testcontainers.core.waiting_utils import wait_container_is_ready diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 78cf9b3f8..0fc4f77cd 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -26,6 +26,11 @@ def with_bind_ports(self, container, host=None): self.ports[container] = host return self + def with_exposed_ports(self, *ports) -> 'DockerContainer': + for port in list(ports): + self.ports[port] = None + return self + def _configure(self): pass @@ -39,7 +44,6 @@ def start(self): detach=True, environment=self.env, ports=self.ports, - publish_all_ports=True, name=self._name, volumes=self.volumes) print("") diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 3af362659..590529f14 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -24,6 +24,7 @@ class MySqlContainer(DbContainer): def __init__(self, image="mysql:latest"): super(MySqlContainer, self).__init__(image) self.port_to_expose = 3306 + self.with_exposed_ports(self.port_to_expose) def _configure(self): self.add_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index bf502ac52..74277ef21 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -23,6 +23,7 @@ class PostgresContainer(DbContainer): def __init__(self, image="postgres:latest"): super(PostgresContainer, self).__init__(image=image) self.port_to_expose = 5432 + self.with_exposed_ports(self.port_to_expose) def _configure(self): self.add_env("POSTGRES_USER", self.POSTGRES_USER) diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index 3c10208a1..093d2d0f4 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -31,9 +31,10 @@ def __init__(self, capabilities, image=None): self.capabilities = capabilities if not image: self.image = get_image_name(capabilities) - self.host_port = 4444 - self.host_vnc_port = 5900 + self.port_to_expose = 4444 + self.vnc_port_to_expose = 5900 super(BrowserWebDriverContainer, self).__init__(image=self.image) + self.with_exposed_ports(self.port_to_expose, self.vnc_port_to_expose) def _configure(self): self.add_env("no_proxy", "localhost") @@ -50,5 +51,5 @@ def get_driver(self) -> WebDriver: def get_connection_url(self) -> str: ip = self.get_container_host_ip() - port = self.get_exposed_port(self.host_port) + port = self.get_exposed_port(self.port_to_expose) return 'http://{}:{}/wd/hub'.format(ip, port) diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 7d0cda897..1213b6815 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -17,8 +17,9 @@ def setup_module(m): def test_docker_custom_image(): container = GenericContainer("spirogov/video_service:latest") - container.with_bind_ports(8086, 8086) - #container.with_name("video_service") + #container.with_bind_ports(8086, 8086) + container.with_exposed_ports(8086, 80) + # container.with_name("video_service") container.with_volume_mapping("/Users/sepi/auto_env", "/root/video", mode='rw') with container: From 147ad5b2eda6624bf71333626a289d847dd64217 Mon Sep 17 00:00:00 2001 From: sepi Date: Thu, 23 Mar 2017 18:10:32 +0200 Subject: [PATCH 14/16] _configure deleted from DockerContainer class --- testcontainers/core/container.py | 15 ++++++--------- testcontainers/core/generic.py | 1 + tests/test_db_containers.py | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 0fc4f77cd..441e973d6 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -18,11 +18,11 @@ def __init__(self, image): self._command = None self._name = None - def add_env(self, key, value): + def add_env(self, key: str, value: str) -> 'DockerContainer': self.env[key] = value return self - def with_bind_ports(self, container, host=None): + def with_bind_ports(self, container: int, host: int = None) -> 'DockerContainer': self.ports[container] = host return self @@ -31,11 +31,7 @@ def with_exposed_ports(self, *ports) -> 'DockerContainer': self.ports[port] = None return self - def _configure(self): - pass - def start(self): - self._configure() print("") print("{} {}".format(crayons.yellow("Pulling image"), crayons.red(self.image))) with blindspin.spinner(): @@ -68,18 +64,19 @@ def get_container_host_ip(self) -> str: def get_exposed_port(self, port) -> str: return self.get_docker_client().port(self._container.id, port) - def with_command(self, command): + def with_command(self, command: str) -> 'DockerContainer': self._command = command return self - def with_name(self, name): + def with_name(self, name: str) -> 'DockerContainer': self._name = name return self - def with_volume_mapping(self, host, container, mode='ro'): + def with_volume_mapping(self, host: str, container: str, mode: str = 'ro') -> 'DockerContainer': # '/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'} mapping = {'bind': container, 'mode': mode} self.volumes[host] = mapping + return self def get_wrapped_contaner(self) -> Container: return self._container diff --git a/testcontainers/core/generic.py b/testcontainers/core/generic.py index 198f645da..70a2eacd3 100644 --- a/testcontainers/core/generic.py +++ b/testcontainers/core/generic.py @@ -43,6 +43,7 @@ def _create_connection_url(self, dialect, username, password, port, db_name): db=db_name) def start(self): + self._configure() super().start() self._connect() return self diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index b9fa78ee0..e152ce27c 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -9,7 +9,7 @@ def test_docker_run_mysql(): - config = MySqlContainer('mysql:5.7.17') + config = MySqlContainer('mysql:5.7.17').with_bind_ports(3306, 3306) with config as mysql: e = sqlalchemy.create_engine(mysql.get_connection_url()) result = e.execute("select version()") From e46f36e3d36144f26c751b0a73dc171a0b2b00ad Mon Sep 17 00:00:00 2001 From: sergey Date: Thu, 23 Mar 2017 21:55:06 +0200 Subject: [PATCH 15/16] tests fix --- testcontainers/core/container.py | 6 +++--- testcontainers/core/docker_client.py | 2 -- testcontainers/mysql.py | 8 ++++---- testcontainers/oracle.py | 2 +- testcontainers/postgres.py | 6 +++--- testcontainers/selenium.py | 4 ++-- tests/examples/__init__.py | 0 tests/examples/mysql_example.py | 9 +++++++++ tests/test_db_containers.py | 4 ++-- tests/test_new_docker_api.py | 5 +---- 10 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 tests/examples/__init__.py create mode 100644 tests/examples/mysql_example.py diff --git a/testcontainers/core/container.py b/testcontainers/core/container.py index 441e973d6..172cdaeba 100644 --- a/testcontainers/core/container.py +++ b/testcontainers/core/container.py @@ -18,7 +18,7 @@ def __init__(self, image): self._command = None self._name = None - def add_env(self, key: str, value: str) -> 'DockerContainer': + def with_env(self, key: str, value: str) -> 'DockerContainer': self.env[key] = value return self @@ -46,8 +46,8 @@ def start(self): print("Container started: ", crayons.yellow(self._container.short_id, bold=True)) return self - def stop(self): - self.get_wrapped_contaner().remove(force=True) + def stop(self, force=True): + self.get_wrapped_contaner().remove(force=force) def __enter__(self): return self.start() diff --git a/testcontainers/core/docker_client.py b/testcontainers/core/docker_client.py index 645d20a90..635ac9e66 100644 --- a/testcontainers/core/docker_client.py +++ b/testcontainers/core/docker_client.py @@ -10,8 +10,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. -import logging - import docker from docker.models.containers import Container diff --git a/testcontainers/mysql.py b/testcontainers/mysql.py index 590529f14..48a8ab5aa 100644 --- a/testcontainers/mysql.py +++ b/testcontainers/mysql.py @@ -27,10 +27,10 @@ def __init__(self, image="mysql:latest"): self.with_exposed_ports(self.port_to_expose) def _configure(self): - self.add_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) - self.add_env("MYSQL_DATABASE", self.MYSQL_DATABASE) - self.add_env("MYSQL_USER", self.MYSQL_USER) - self.add_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) + self.with_env("MYSQL_ROOT_PASSWORD", self.MYSQL_ROOT_PASSWORD) + self.with_env("MYSQL_DATABASE", self.MYSQL_DATABASE) + self.with_env("MYSQL_USER", self.MYSQL_USER) + self.with_env("MYSQL_PASSWORD", self.MYSQL_PASSWORD) def get_connection_url(self): return super()._create_connection_url(dialect="mysql+pymysql", diff --git a/testcontainers/oracle.py b/testcontainers/oracle.py index 3a249b510..de6a3394a 100644 --- a/testcontainers/oracle.py +++ b/testcontainers/oracle.py @@ -16,4 +16,4 @@ def __init__(self, image="wnameless/oracle-xe-11g:latest"): self.dialect = "oracle" def _configure(self): - self.add_env("ORACLE_ALLOW_REMOTE", "true") + self.with_env("ORACLE_ALLOW_REMOTE", "true") diff --git a/testcontainers/postgres.py b/testcontainers/postgres.py index 74277ef21..95eda6635 100644 --- a/testcontainers/postgres.py +++ b/testcontainers/postgres.py @@ -26,9 +26,9 @@ def __init__(self, image="postgres:latest"): self.with_exposed_ports(self.port_to_expose) def _configure(self): - self.add_env("POSTGRES_USER", self.POSTGRES_USER) - self.add_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) - self.add_env("POSTGRES_DB", self.POSTGRES_DB) + self.with_env("POSTGRES_USER", self.POSTGRES_USER) + self.with_env("POSTGRES_PASSWORD", self.POSTGRES_PASSWORD) + self.with_env("POSTGRES_DB", self.POSTGRES_DB) def get_connection_url(self): return super()._create_connection_url(dialect="postgresql+psycopg2", diff --git a/testcontainers/selenium.py b/testcontainers/selenium.py index 093d2d0f4..20ddbbcd4 100644 --- a/testcontainers/selenium.py +++ b/testcontainers/selenium.py @@ -37,8 +37,8 @@ def __init__(self, capabilities, image=None): self.with_exposed_ports(self.port_to_expose, self.vnc_port_to_expose) def _configure(self): - self.add_env("no_proxy", "localhost") - self.add_env("HUB_ENV_no_proxy", "localhost") + self.with_env("no_proxy", "localhost") + self.with_env("HUB_ENV_no_proxy", "localhost") @wait_container_is_ready() def _connect(self): diff --git a/tests/examples/__init__.py b/tests/examples/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/examples/mysql_example.py b/tests/examples/mysql_example.py new file mode 100644 index 000000000..aad15d20c --- /dev/null +++ b/tests/examples/mysql_example.py @@ -0,0 +1,9 @@ +from testcontainers import MySqlContainer + + +def example_with_mysql(): + mysql = MySqlContainer().start() + mysql.stop() + + +example_with_mysql() \ No newline at end of file diff --git a/tests/test_db_containers.py b/tests/test_db_containers.py index e152ce27c..68e291df2 100644 --- a/tests/test_db_containers.py +++ b/tests/test_db_containers.py @@ -9,7 +9,7 @@ def test_docker_run_mysql(): - config = MySqlContainer('mysql:5.7.17').with_bind_ports(3306, 3306) + config = MySqlContainer('mysql:5.7.17') with config as mysql: e = sqlalchemy.create_engine(mysql.get_connection_url()) result = e.execute("select version()") @@ -37,7 +37,7 @@ def test_docker_run_mariadb(): def test_docker_generic_db(): mongo_container = GenericContainer("mongo:latest") - mongo_container.bind_ports(27017, 27017) + mongo_container.with_bind_ports(27017, 27017) with mongo_container: @wait_container_is_ready() diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index 1213b6815..ca7928202 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -17,10 +17,7 @@ def setup_module(m): def test_docker_custom_image(): container = GenericContainer("spirogov/video_service:latest") - #container.with_bind_ports(8086, 8086) - container.with_exposed_ports(8086, 80) - # container.with_name("video_service") - container.with_volume_mapping("/Users/sepi/auto_env", "/root/video", mode='rw') + container.with_exposed_ports(8086) with container: driver = webdriver.Chrome(ChromeDriverManager().install()) From 0352b76ac614040392bae168d230ceaa3c6fd21a Mon Sep 17 00:00:00 2001 From: sepi Date: Fri, 24 Mar 2017 10:51:10 +0200 Subject: [PATCH 16/16] test fix for custom image --- tests/test_new_docker_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_new_docker_api.py b/tests/test_new_docker_api.py index ca7928202..f901e8bf8 100644 --- a/tests/test_new_docker_api.py +++ b/tests/test_new_docker_api.py @@ -22,7 +22,7 @@ def test_docker_custom_image(): with container: driver = webdriver.Chrome(ChromeDriverManager().install()) driver.implicitly_wait(10) - driver.get("http://localhost:8086") + driver.get("http://localhost:{}".format(container.get_exposed_port(8086))) driver.find_element_by_css_selector("#inputEmail3").send_keys("admin")