diff --git a/exasol/slc/api/deploy.py b/exasol/slc/api/deploy.py index c329768..890aa56 100644 --- a/exasol/slc/api/deploy.py +++ b/exasol/slc/api/deploy.py @@ -1,7 +1,6 @@ import getpass -from typing import Optional, Tuple +from typing import Dict, Optional, Tuple -import luigi from exasol_integration_test_docker_environment.lib.api.common import ( cli_function, generate_root_task, @@ -14,7 +13,9 @@ DependencyLoggerBaseTask, ) -from exasol.slc.internal.tasks.upload.upload_containers import UploadContainers +from exasol.slc.internal.tasks.upload.deploy_containers import DeployContainers +from exasol.slc.internal.tasks.upload.deploy_info import toDeployResult +from exasol.slc.models.deploy_result import DeployResult @cli_function @@ -52,13 +53,14 @@ def deploy( task_dependencies_dot_file: Optional[str] = None, log_level: Optional[str] = None, use_job_specific_log_file: bool = True, -) -> luigi.LocalTarget: +) -> Dict[str, Dict[str, DeployResult]]: """ This command uploads the whole script-language-container package of the flavor to the database. - If the stages or the packaged container do not exists locally, the system will build, pull or + If the stages or the packaged container do not exist locally, the system will build, pull or export them before the upload. :raises api_errors.TaskFailureError: if operation is not successful. - :return: Path to resulting report file. + :return: A dictionary with an instance of class DeployResult for each release for each deployed flavor. + For example { "flavors/standard-flavor" : {"release" : DeployResult(...) } } """ import_build_steps(flavor_path) set_build_config( @@ -94,7 +96,7 @@ def deploy( def root_task_generator() -> DependencyLoggerBaseTask: return generate_root_task( - task_class=UploadContainers, + task_class=DeployContainers, flavor_paths=list(flavor_path), release_goals=list(release_goal), database_host=bucketfs_host, @@ -110,10 +112,30 @@ def root_task_generator() -> DependencyLoggerBaseTask: use_ssl_cert_validation=use_ssl_cert_validation, ) - return run_task( + deploy_infos = run_task( root_task_generator, workers=workers, task_dependencies_dot_file=task_dependencies_dot_file, log_level=log_level, use_job_specific_log_file=use_job_specific_log_file, ) + + return { + flavor: { + release: toDeployResult( + deploy_info=deploy_info, + bucketfs_use_https=bucketfs_use_https, + bucketfs_host=bucketfs_host, + bucketfs_port=bucketfs_port, + bucket_name=bucket, + bucketfs_name=bucketfs_name, + bucketfs_username=bucketfs_user, + bucketfs_password=bucketfs_password, + ssl_cert_path=ssl_cert_path, + use_ssl_cert_validation=use_ssl_cert_validation, + path_in_bucket=path_in_bucket, + ) + for release, deploy_info in deploy_info_per_release.items() + } + for flavor, deploy_info_per_release in deploy_infos.items() + } diff --git a/exasol/slc/internal/tasks/upload/deploy_container_base_task.py b/exasol/slc/internal/tasks/upload/deploy_container_base_task.py new file mode 100644 index 0000000..f22d1d7 --- /dev/null +++ b/exasol/slc/internal/tasks/upload/deploy_container_base_task.py @@ -0,0 +1,130 @@ +from pathlib import Path + +import exasol.bucketfs as bfs # type: ignore +import luigi +from exasol_integration_test_docker_environment.abstract_method_exception import ( + AbstractMethodException, +) +from exasol_integration_test_docker_environment.lib.base.flavor_task import ( + FlavorBaseTask, +) + +from exasol.slc.internal.tasks.upload.deploy_info import DeployInfo +from exasol.slc.internal.tasks.upload.language_def_parser import ( + parse_language_definition, +) +from exasol.slc.internal.tasks.upload.language_definition import LanguageDefinition +from exasol.slc.internal.tasks.upload.upload_container_parameter import ( + UploadContainerParameter, +) +from exasol.slc.models.export_info import ExportInfo +from exasol.slc.models.language_definition_components import ( + LanguageDefinitionComponents, +) +from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder + + +class DeployContainerBaseTask(FlavorBaseTask, UploadContainerParameter): + # TODO check if upload was successfull by requesting the file + # TODO add error checks and propose reasons for the error + # TODO extract bucketfs interaction into own module + + release_goal = luigi.Parameter() + + def __init__(self, *args, **kwargs): + self.export_info_future = None + super().__init__(*args, **kwargs) + + def register_required(self): + task = self.get_export_task() + self.export_info_future = self.register_dependency(task) + + def get_export_task(self): + raise AbstractMethodException() + + def run_task(self): + export_info = self.get_values_from_future(self.export_info_future) + path_in_bucket = self._upload_container(export_info) + language_definition = LanguageDefinition( + release_name=self._get_complete_release_name(export_info), + flavor_path=self.flavor_path, + bucketfs_name=self.bucketfs_name, + bucket_name=self.bucket_name, + path_in_bucket=self.path_in_bucket, + ) + language_definitions = language_definition.generate_definition().split(" ") + language_def_components_list = list() + for lang_def in language_definitions: + alias, url = parse_language_definition(lang_def) + language_def_components_list.append( + LanguageDefinitionComponents(alias=alias, url=url) + ) + + lang_def_builder = LanguageDefinitionsBuilder(language_def_components_list) + try: + release_path = Path(export_info.cache_file).relative_to(Path("").absolute()) + except ValueError: + release_path = Path(export_info.cache_file) + + result = DeployInfo( + release_path=str(release_path), + complete_release_name=self._get_complete_release_name(export_info), + human_readable_location=self._complete_url(export_info), + language_definition_builder=lang_def_builder, + ) + self.return_object(result) + + def build_file_path_in_bucket(self, release_info: ExportInfo) -> bfs.path.PathLike: + backend = bfs.path.StorageBackend.onprem + + complete_release_name = self._get_complete_release_name(release_info) + verify = self.ssl_cert_path or self.use_ssl_cert_validation + path_in_bucket_to_upload_path = bfs.path.build_path( + backend=backend, + url=self._url, + bucket_name=self.bucket_name, + service_name=self.bucketfs_name, + username=self.bucketfs_username, + password=self.bucketfs_password, + verify=verify, + path=self.path_in_bucket or "", + ) + return path_in_bucket_to_upload_path / f"{complete_release_name}.tar.gz" + + @property + def _url(self) -> str: + return f"{self._get_url_prefix()}{self.database_host}:{self.bucketfs_port}" + + def _complete_url(self, export_info: ExportInfo): + path_in_bucket = ( + f"{self.path_in_bucket}/" if self.path_in_bucket not in [None, ""] else "" + ) + return f"{self._url}/{self.bucket_name}/{path_in_bucket}{self._get_complete_release_name(export_info)}.tar.gz" + + def _upload_container(self, release_info: ExportInfo) -> bfs.path.PathLike: + bucket_path = self.build_file_path_in_bucket(release_info) + self.logger.info( + f"Upload {release_info.cache_file} to {self._complete_url(release_info)}" + ) + with open(release_info.cache_file, "rb") as file: + bucket_path.write(file) + return bucket_path + + def _get_complete_release_name(self, release_info: ExportInfo): + complete_release_name = f"""{release_info.name}-{release_info.release_goal}-{self._get_release_name( + release_info)}""" + return complete_release_name + + def _get_release_name(self, release_info: ExportInfo): + if self.release_name is None: + release_name = release_info.hash + else: + release_name = self.release_name + return release_name + + def _get_url_prefix(self): + if self.bucketfs_https: + url_prefix = "https://" + else: + url_prefix = "http://" + return url_prefix diff --git a/exasol/slc/internal/tasks/upload/deploy_container_task.py b/exasol/slc/internal/tasks/upload/deploy_container_task.py new file mode 100644 index 0000000..489d568 --- /dev/null +++ b/exasol/slc/internal/tasks/upload/deploy_container_task.py @@ -0,0 +1,38 @@ +import importlib + +import luigi +from exasol_integration_test_docker_environment.lib.base.json_pickle_parameter import ( + JsonPickleParameter, +) +from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import ( + RequiredTaskInfo, +) + +from exasol.slc.internal.tasks.upload.deploy_container_base_task import ( + DeployContainerBaseTask, +) + + +class DeployContainerTask(DeployContainerBaseTask): + # We need to create the ExportContainerTask for UploadContainerTask dynamically, + # because we want to push as soon as possible after an image was build and + # don't want to wait for the push finishing before starting the build of depended images, + # but we also need to create a UploadContainerTask for each ExportContainerTask of a goal + + required_task_info = JsonPickleParameter( + RequiredTaskInfo, + visibility=luigi.parameter.ParameterVisibility.HIDDEN, + significant=True, + ) # type: RequiredTaskInfo + + def get_export_task(self): + module = importlib.import_module( + self.required_task_info.module_name # pylint: disable=no-member + ) + class_ = getattr( + module, self.required_task_info.class_name # pylint: disable=no-member + ) + instance = self.create_child_task( + class_, **self.required_task_info.params # pylint: disable=no-member + ) + return instance diff --git a/exasol/slc/internal/tasks/upload/deploy_container_tasks_creator.py b/exasol/slc/internal/tasks/upload/deploy_container_tasks_creator.py new file mode 100644 index 0000000..c0250cb --- /dev/null +++ b/exasol/slc/internal/tasks/upload/deploy_container_tasks_creator.py @@ -0,0 +1,42 @@ +from typing import Dict + +from exasol_integration_test_docker_environment.lib.docker.images.create.docker_image_create_task import ( + DockerCreateImageTask, +) +from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import ( + RequiredTaskInfo, +) + +from exasol.slc.internal.tasks.upload.deploy_container_task import DeployContainerTask +from exasol.slc.internal.tasks.upload.upload_containers_parameter import ( + UploadContainersParameter, +) + + +class DeployContainerTasksCreator: + + def __init__(self, task: UploadContainersParameter): + self.task = task + + def create_deploy_tasks(self, build_tasks: Dict[str, DockerCreateImageTask]): + return { + release_goal: self._create_deploy_task(release_goal, build_task) + for release_goal, build_task in build_tasks.items() + } + + def _create_deploy_task(self, release_goal: str, build_task: DockerCreateImageTask): + required_task_info = self._create_required_task_info(build_task) + return self.task.create_child_task_with_common_params( # type: ignore + DeployContainerTask, + required_task_info=required_task_info, + release_goal=release_goal, + ) + + @staticmethod + def _create_required_task_info(build_task): + required_task_info = RequiredTaskInfo( + module_name=build_task.__module__, + class_name=build_task.__class__.__name__, + params=build_task.param_kwargs, + ) + return required_task_info diff --git a/exasol/slc/internal/tasks/upload/deploy_containers.py b/exasol/slc/internal/tasks/upload/deploy_containers.py new file mode 100644 index 0000000..5a1ac6f --- /dev/null +++ b/exasol/slc/internal/tasks/upload/deploy_containers.py @@ -0,0 +1,63 @@ +from typing import Dict + +from exasol_integration_test_docker_environment.lib.base.flavor_task import ( + FlavorsBaseTask, +) + +from exasol.slc.internal.tasks.build.docker_flavor_build_base import ( + DockerFlavorBuildBase, +) +from exasol.slc.internal.tasks.export.export_container_tasks_creator import ( + ExportContainerTasksCreator, +) +from exasol.slc.internal.tasks.upload.deploy_container_tasks_creator import ( + DeployContainerTasksCreator, +) +from exasol.slc.internal.tasks.upload.upload_containers_parameter import ( + UploadContainersParameter, +) + + +class DeployContainers(FlavorsBaseTask, UploadContainersParameter): + + def __init__(self, *args, **kwargs): + self.lang_def_builders_futures = None + super().__init__(*args, **kwargs) + + def register_required(self): + tasks = self.create_tasks_for_flavors_with_common_params( # type: ignore + DeployFlavorContainers + ) # type: Dict[str,DeployFlavorContainers] + self.lang_def_builders_futures = self.register_dependencies(tasks) + + def run_task(self): + lang_definitionbuilders = self.get_values_from_futures( + self.lang_def_builders_futures + ) + self.return_object(lang_definitionbuilders) + + +class DeployFlavorContainers(DockerFlavorBuildBase, UploadContainersParameter): + + def get_goals(self): + return set(self.release_goals) + + def run_task(self): + build_tasks = self.create_build_tasks() + + export_tasks = self.create_export_tasks(build_tasks) + deploy_tasks = self.create_deploy_tasks(export_tasks) + + lang_definitions_futures = yield from self.run_dependencies(deploy_tasks) + language_definition = self.get_values_from_futures(lang_definitions_futures) + self.return_object(language_definition) + + def create_deploy_tasks(self, export_tasks): + deploy_tasks_creator = DeployContainerTasksCreator(self) + deploy_tasks = deploy_tasks_creator.create_deploy_tasks(export_tasks) + return deploy_tasks + + def create_export_tasks(self, build_tasks): + export_tasks_creator = ExportContainerTasksCreator(self, export_path=None) + export_tasks = export_tasks_creator.create_export_tasks(build_tasks) + return export_tasks diff --git a/exasol/slc/internal/tasks/upload/deploy_info.py b/exasol/slc/internal/tasks/upload/deploy_info.py new file mode 100644 index 0000000..6cd214e --- /dev/null +++ b/exasol/slc/internal/tasks/upload/deploy_info.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass + +import exasol.bucketfs as bfs # type: ignore + +from exasol.slc.models.deploy_result import DeployResult +from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder + + +@dataclass +class DeployInfo: + release_path: str + complete_release_name: str + human_readable_location: str + language_definition_builder: LanguageDefinitionsBuilder + + +def toDeployResult( + deploy_info: DeployInfo, + bucketfs_use_https: bool, + bucketfs_host: str, + bucketfs_port: int, + bucket_name: str, + bucketfs_name: str, + bucketfs_username: str, + bucketfs_password: str, + ssl_cert_path: str, + use_ssl_cert_validation: bool, + path_in_bucket: str, +) -> DeployResult: + backend = bfs.path.StorageBackend.onprem + verify = ssl_cert_path or use_ssl_cert_validation + + complete_release_name = deploy_info.complete_release_name + + url_prefix = "https://" if bucketfs_use_https else "http://" + url = f"{url_prefix}{bucketfs_host}:{bucketfs_port}" + bucket_path = ( + bfs.path.build_path( + backend=backend, + url=url, + bucket_name=bucket_name, + service_name=bucketfs_name, + username=bucketfs_username, + password=bucketfs_password, + verify=verify, + path=path_in_bucket or "", + ) + / f"{complete_release_name}.tar.gz" + ) + + return DeployResult( + release_path=deploy_info.release_path, + human_readable_upload_location=deploy_info.human_readable_location, + bucket_path=bucket_path, + language_definition_builder=deploy_info.language_definition_builder, + ) diff --git a/exasol/slc/models/deploy_result.py b/exasol/slc/models/deploy_result.py new file mode 100644 index 0000000..8368420 --- /dev/null +++ b/exasol/slc/models/deploy_result.py @@ -0,0 +1,12 @@ +import exasol.bucketfs as bfs # type: ignore +from attr import dataclass + +from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder + + +@dataclass +class DeployResult: + release_path: str + human_readable_upload_location: str + bucket_path: bfs.path.PathLike + language_definition_builder: LanguageDefinitionsBuilder diff --git a/exasol/slc/tool/commands/deploy.py b/exasol/slc/tool/commands/deploy.py index aeafdce..e9bc7e5 100644 --- a/exasol/slc/tool/commands/deploy.py +++ b/exasol/slc/tool/commands/deploy.py @@ -157,5 +157,23 @@ def deploy( ssl_cert_path=ssl_cert_path, use_ssl_cert_validation=use_ssl_cert_validation, ) - with result.open("r") as f: - print(f.read()) + for flavor_name, lang_def_builds_per_release in result.items(): + for release, deploy_result in lang_def_builds_per_release.items(): + print( + f""" + Uploaded release='{release}' located at {deploy_result.release_path} to {deploy_result.human_readable_upload_location} + + In SQL, you can activate the languages supported by the {flavor_name} + flavor by using the following statements: + + + To activate the flavor only for the current session: + + {deploy_result.language_definition_builder.generate_alter_session()} + + + To activate the flavor on the system: + + {deploy_result.language_definition_builder.generate_alter_system()} + """ + ) diff --git a/test/test_docker_api_deploy.py b/test/test_docker_api_deploy.py index ef142ae..b463931 100644 --- a/test/test_docker_api_deploy.py +++ b/test/test_docker_api_deploy.py @@ -1,6 +1,7 @@ import subprocess import unittest +import exasol.bucketfs as bfs # type: ignore import utils as exaslct_utils # type: ignore # pylint: disable=import-error from exasol_integration_test_docker_environment.lib.api.api_errors import ( TaskRuntimeError, @@ -31,8 +32,9 @@ def test_docker_api_deploy(self): release_name = "TEST" bucketfs_name = "bfsdefault" bucket_name = "default" + flavor_path = exaslct_utils.get_test_flavor() result = api.deploy( - flavor_path=(str(exaslct_utils.get_test_flavor()),), + flavor_path=(str(flavor_path),), bucketfs_host=self.docker_environment.database_host, bucketfs_port=self.docker_environment.ports.bucketfs, bucketfs_user=self.docker_environment.bucketfs_username, @@ -43,15 +45,55 @@ def test_docker_api_deploy(self): path_in_bucket=path_in_bucket, release_name=release_name, ) - with result.open("r") as f: - res = f.read() - self.assertIn( + self.assertIn(str(flavor_path), result.keys()) + + self.assertEqual(len(result), 1) + self.assertIn(str(flavor_path), result.keys()) + self.assertEqual(len(result[str(flavor_path)]), 1) + deploy_result = result[str(flavor_path)]["release"] + expected_alter_session_cmd = ( f"ALTER SESSION SET SCRIPT_LANGUAGES='PYTHON3_TEST=localzmq+protobuf:///{bucketfs_name}/" f"{bucket_name}/{path_in_bucket}/test-flavor-release-{release_name}?lang=python#buckets/" f"{bucketfs_name}/{bucket_name}/{path_in_bucket}/test-flavor-release-{release_name}/" - f"exaudf/exaudfclient_py3", - res, + f"exaudf/exaudfclient_py3';" + ) + result_alter_session_cmd = ( + deploy_result.language_definition_builder.generate_alter_session() + ) + + self.assertEqual(result_alter_session_cmd, expected_alter_session_cmd) + + self.assertIn( + f".build_output/cache/exports/test-flavor-release-", + deploy_result.release_path, + ) + + self.assertEqual( + deploy_result.human_readable_upload_location, + f"http://{self.docker_environment.database_host}:{self.docker_environment.ports.bucketfs}/" + f"{bucket_name}/{path_in_bucket}/test-flavor-release-{release_name}.tar.gz", + ) + + expected_path_in_bucket = ( + bfs.path.build_path( + backend=bfs.path.StorageBackend.onprem, + url=f"http://{self.docker_environment.database_host}:{self.docker_environment.ports.bucketfs}", + bucket_name=bucket_name, + service_name=bucketfs_name, + username="w", + password=self.docker_environment.bucketfs_password, + verify=False, + path=path_in_bucket, + ) + / f"test-flavor-release-{release_name}.tar.gz" + ) + + # Compare UDF path of `bucket_path` until bfs.path.PathLike implements comparison + self.assertEqual( + expected_path_in_bucket.as_udf_path(), + deploy_result.bucket_path.as_udf_path(), ) + self.validate_file_on_bucket_fs( bucket_name, f"{path_in_bucket}/test-flavor-release-{release_name}.tar.gz" ) @@ -60,8 +102,9 @@ def test_docker_api_deploy_without_path_in_bucket(self): release_name = "TEST" bucketfs_name = "bfsdefault" bucket_name = "default" + flavor_path = exaslct_utils.get_test_flavor() result = api.deploy( - flavor_path=(str(exaslct_utils.get_test_flavor()),), + flavor_path=(str(flavor_path),), bucketfs_host=self.docker_environment.database_host, bucketfs_port=self.docker_environment.ports.bucketfs, bucketfs_user=self.docker_environment.bucketfs_username, @@ -71,15 +114,52 @@ def test_docker_api_deploy_without_path_in_bucket(self): bucket=bucket_name, release_name=release_name, ) - with result.open("r") as f: - res = f.read() - self.assertIn( + self.assertIn(str(flavor_path), result.keys()) + self.assertEqual(len(result), 1) + self.assertIn(str(flavor_path), result.keys()) + self.assertEqual(len(result[str(flavor_path)]), 1) + deploy_result = result[str(flavor_path)]["release"] + expected_alter_session_cmd = ( f"ALTER SESSION SET SCRIPT_LANGUAGES='PYTHON3_TEST=localzmq+protobuf:///{bucketfs_name}/" f"{bucket_name}/test-flavor-release-{release_name}?lang=python#buckets/" f"{bucketfs_name}/{bucket_name}/test-flavor-release-{release_name}/" - f"exaudf/exaudfclient_py3", - res, + f"exaudf/exaudfclient_py3';" + ) + result_alter_session_cmd = ( + deploy_result.language_definition_builder.generate_alter_session() + ) + self.assertEqual(result_alter_session_cmd, expected_alter_session_cmd) + + self.assertIn( + f".build_output/cache/exports/test-flavor-release-", + deploy_result.release_path, + ) + + self.assertEqual( + deploy_result.human_readable_upload_location, + f"http://{self.docker_environment.database_host}:{self.docker_environment.ports.bucketfs}/" + f"{bucket_name}/test-flavor-release-{release_name}.tar.gz", + ) + + expected_path_in_bucket = ( + bfs.path.build_path( + backend=bfs.path.StorageBackend.onprem, + url=f"http://{self.docker_environment.database_host}:{self.docker_environment.ports.bucketfs}", + bucket_name=bucket_name, + service_name=bucketfs_name, + username="w", + password=self.docker_environment.bucketfs_password, + verify=False, + ) + / f"test-flavor-release-{release_name}.tar.gz" ) + + # Compare UDF path of `bucket_path` until bfs.path.PathLike implements comparison + self.assertEqual( + expected_path_in_bucket.as_udf_path(), + deploy_result.bucket_path.as_udf_path(), + ) + self.validate_file_on_bucket_fs( bucket_name, f"test-flavor-release-{release_name}.tar.gz" ) diff --git a/test/unit/cli/test_deploy.py b/test/unit/cli/test_deploy.py index e2a39fc..3173d38 100644 --- a/test/unit/cli/test_deploy.py +++ b/test/unit/cli/test_deploy.py @@ -1,24 +1,20 @@ import tempfile from test.unit.cli import CliRunner -from unittest.mock import MagicMock, patch +from unittest.mock import patch +import exasol.bucketfs as bfs # type: ignore import pytest +from exasol.slc.models.deploy_result import DeployResult +from exasol.slc.models.language_definition_components import ( + BuiltInLanguageDefinitionURL, + LanguageDefinitionComponents, + SLCLanguage, +) +from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder from exasol.slc.tool.commands.deploy import deploy -class DummyLocalTarget: - def __init__(self): - self.mock = MagicMock() - self.mock.read.return_value = "deploy was successful" - - def __enter__(self): - return self.mock - - def __exit__(self, exc_type, exc_val, exc_tb): - pass - - @pytest.fixture def cli(): return CliRunner(deploy) @@ -35,15 +31,34 @@ def test_no_flavor_path(cli): TEST_BUCKETFS_NAME = "dummy-bucketfs-name" TEST_BUCKET_NAME = "dummy-bucket-name" +TEST_RELEASE_PATH = "/release_target" +TEST_UPLOAD_URL = "https://my_bucket/target" +TEST_LANG_DEF_BUILDER = LanguageDefinitionsBuilder( + [ + LanguageDefinitionComponents( + alias="dummy-alias", url=BuiltInLanguageDefinitionURL(SLCLanguage.Java) + ) + ] +) +TEST_DUMMY_FLAVOR = "dummy-flavor" +TEST_DUMMY_RELEASE = "dummy-release" + def test_deploy_minimum_parameters(cli): - return_mock = MagicMock() - dummy_returned_target = DummyLocalTarget() - return_mock.open.return_value = dummy_returned_target + return_value = { + TEST_DUMMY_FLAVOR: { + TEST_DUMMY_FLAVOR: DeployResult( + release_path=TEST_RELEASE_PATH, + human_readable_upload_location=TEST_UPLOAD_URL, + bucket_path=None, + language_definition_builder=TEST_LANG_DEF_BUILDER, + ) + } + } with patch( "exasol.slc.api.deploy", - return_value=return_mock, + return_value=return_value, ) as mock_foo: with tempfile.TemporaryDirectory() as temp_flavor_path: cli.run( @@ -62,7 +77,11 @@ def test_deploy_minimum_parameters(cli): "--bucket", TEST_BUCKET_NAME, ) - assert cli.succeeded and "deploy was successful" in cli.output + assert ( + cli.succeeded + and "Uploaded release='dummy-flavor' located at /release_target to https://my_bucket/target" + in cli.output + ) mock_foo.assert_called_once_with( flavor_path=(temp_flavor_path,), bucketfs_host=TEST_BUCKETFS_HOST, @@ -98,21 +117,26 @@ def test_deploy_minimum_parameters(cli): ssl_cert_path="", use_ssl_cert_validation=True, ) - return_mock.open.assert_called_once() - dummy_returned_target.mock.read.assert_called_once() def test_deploy_password_in_env(cli): - return_mock = MagicMock() - dummy_returned_target = DummyLocalTarget() - return_mock.open.return_value = dummy_returned_target + return_value = { + TEST_DUMMY_FLAVOR: { + TEST_DUMMY_FLAVOR: DeployResult( + release_path=TEST_RELEASE_PATH, + human_readable_upload_location=TEST_UPLOAD_URL, + bucket_path=None, + language_definition_builder=TEST_LANG_DEF_BUILDER, + ) + } + } TEST_ENV_PASSWORD = "super_secret_bucketfs_password" cli.env = {"BUCKETFS_PASSWORD": TEST_ENV_PASSWORD} with patch( "exasol.slc.api.deploy", - return_value=return_mock, + return_value=return_value, ) as mock_foo: with tempfile.TemporaryDirectory() as temp_flavor_path: cli.run( @@ -129,41 +153,43 @@ def test_deploy_password_in_env(cli): "--bucket", TEST_BUCKET_NAME, ) - assert cli.succeeded and "deploy was successful" in cli.output - mock_foo.assert_called_once_with( - flavor_path=(temp_flavor_path,), - bucketfs_host=TEST_BUCKETFS_HOST, - bucketfs_port=TEST_BUCKETFS_PORT, - bucketfs_user=TEST_BUCKETFS_USER, - bucketfs_name=TEST_BUCKETFS_NAME, - bucket=TEST_BUCKET_NAME, - bucketfs_password=TEST_ENV_PASSWORD, - bucketfs_use_https=False, - path_in_bucket="", - release_goal=("release",), - release_name=None, - force_rebuild=False, - force_rebuild_from=(), - force_pull=False, - output_directory=".build_output", - temporary_base_directory="/tmp", - log_build_context_content=False, - cache_directory=None, - build_name=None, - source_docker_repository_name="exasol/script-language-container", - source_docker_tag_prefix="", - source_docker_username=None, - source_docker_password=None, - target_docker_repository_name="exasol/script-language-container", - target_docker_tag_prefix="", - target_docker_username=None, - target_docker_password=None, - workers=5, - task_dependencies_dot_file=None, - log_level=None, - use_job_specific_log_file=True, - ssl_cert_path="", - use_ssl_cert_validation=True, - ) - return_mock.open.assert_called_once() - dummy_returned_target.mock.read.assert_called_once() + assert ( + cli.succeeded + and "Uploaded release='dummy-flavor' located at /release_target to https://my_bucket/target" + in cli.output + ) + mock_foo.assert_called_once_with( + flavor_path=(temp_flavor_path,), + bucketfs_host=TEST_BUCKETFS_HOST, + bucketfs_port=TEST_BUCKETFS_PORT, + bucketfs_user=TEST_BUCKETFS_USER, + bucketfs_name=TEST_BUCKETFS_NAME, + bucket=TEST_BUCKET_NAME, + bucketfs_password=TEST_ENV_PASSWORD, + bucketfs_use_https=False, + path_in_bucket="", + release_goal=("release",), + release_name=None, + force_rebuild=False, + force_rebuild_from=(), + force_pull=False, + output_directory=".build_output", + temporary_base_directory="/tmp", + log_build_context_content=False, + cache_directory=None, + build_name=None, + source_docker_repository_name="exasol/script-language-container", + source_docker_tag_prefix="", + source_docker_username=None, + source_docker_password=None, + target_docker_repository_name="exasol/script-language-container", + target_docker_tag_prefix="", + target_docker_username=None, + target_docker_password=None, + workers=5, + task_dependencies_dot_file=None, + log_level=None, + use_job_specific_log_file=True, + ssl_cert_path="", + use_ssl_cert_validation=True, + )