From 747499ef87edf9e6025ebf9e1352a389d48306df Mon Sep 17 00:00:00 2001 From: sepi Date: Wed, 22 Mar 2017 14:14:50 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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)