Skip to content

Commit b30b1fa

Browse files
authored
#239: Changed return type for deploy API method (#240)
fixes #239
1 parent 2b26cf1 commit b30b1fa

File tree

10 files changed

+571
-84
lines changed

10 files changed

+571
-84
lines changed

exasol/slc/api/deploy.py

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import getpass
2-
from typing import Optional, Tuple
2+
from typing import Dict, Optional, Tuple
33

4-
import luigi
54
from exasol_integration_test_docker_environment.lib.api.common import (
65
cli_function,
76
generate_root_task,
@@ -14,7 +13,9 @@
1413
DependencyLoggerBaseTask,
1514
)
1615

17-
from exasol.slc.internal.tasks.upload.upload_containers import UploadContainers
16+
from exasol.slc.internal.tasks.upload.deploy_containers import DeployContainers
17+
from exasol.slc.internal.tasks.upload.deploy_info import toDeployResult
18+
from exasol.slc.models.deploy_result import DeployResult
1819

1920

2021
@cli_function
@@ -52,13 +53,14 @@ def deploy(
5253
task_dependencies_dot_file: Optional[str] = None,
5354
log_level: Optional[str] = None,
5455
use_job_specific_log_file: bool = True,
55-
) -> luigi.LocalTarget:
56+
) -> Dict[str, Dict[str, DeployResult]]:
5657
"""
5758
This command uploads the whole script-language-container package of the flavor to the database.
58-
If the stages or the packaged container do not exists locally, the system will build, pull or
59+
If the stages or the packaged container do not exist locally, the system will build, pull or
5960
export them before the upload.
6061
:raises api_errors.TaskFailureError: if operation is not successful.
61-
:return: Path to resulting report file.
62+
:return: A dictionary with an instance of class DeployResult for each release for each deployed flavor.
63+
For example { "flavors/standard-flavor" : {"release" : DeployResult(...) } }
6264
"""
6365
import_build_steps(flavor_path)
6466
set_build_config(
@@ -94,7 +96,7 @@ def deploy(
9496

9597
def root_task_generator() -> DependencyLoggerBaseTask:
9698
return generate_root_task(
97-
task_class=UploadContainers,
99+
task_class=DeployContainers,
98100
flavor_paths=list(flavor_path),
99101
release_goals=list(release_goal),
100102
database_host=bucketfs_host,
@@ -110,10 +112,30 @@ def root_task_generator() -> DependencyLoggerBaseTask:
110112
use_ssl_cert_validation=use_ssl_cert_validation,
111113
)
112114

113-
return run_task(
115+
deploy_infos = run_task(
114116
root_task_generator,
115117
workers=workers,
116118
task_dependencies_dot_file=task_dependencies_dot_file,
117119
log_level=log_level,
118120
use_job_specific_log_file=use_job_specific_log_file,
119121
)
122+
123+
return {
124+
flavor: {
125+
release: toDeployResult(
126+
deploy_info=deploy_info,
127+
bucketfs_use_https=bucketfs_use_https,
128+
bucketfs_host=bucketfs_host,
129+
bucketfs_port=bucketfs_port,
130+
bucket_name=bucket,
131+
bucketfs_name=bucketfs_name,
132+
bucketfs_username=bucketfs_user,
133+
bucketfs_password=bucketfs_password,
134+
ssl_cert_path=ssl_cert_path,
135+
use_ssl_cert_validation=use_ssl_cert_validation,
136+
path_in_bucket=path_in_bucket,
137+
)
138+
for release, deploy_info in deploy_info_per_release.items()
139+
}
140+
for flavor, deploy_info_per_release in deploy_infos.items()
141+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
from pathlib import Path
2+
3+
import exasol.bucketfs as bfs # type: ignore
4+
import luigi
5+
from exasol_integration_test_docker_environment.abstract_method_exception import (
6+
AbstractMethodException,
7+
)
8+
from exasol_integration_test_docker_environment.lib.base.flavor_task import (
9+
FlavorBaseTask,
10+
)
11+
12+
from exasol.slc.internal.tasks.upload.deploy_info import DeployInfo
13+
from exasol.slc.internal.tasks.upload.language_def_parser import (
14+
parse_language_definition,
15+
)
16+
from exasol.slc.internal.tasks.upload.language_definition import LanguageDefinition
17+
from exasol.slc.internal.tasks.upload.upload_container_parameter import (
18+
UploadContainerParameter,
19+
)
20+
from exasol.slc.models.export_info import ExportInfo
21+
from exasol.slc.models.language_definition_components import (
22+
LanguageDefinitionComponents,
23+
)
24+
from exasol.slc.models.language_definitions_builder import LanguageDefinitionsBuilder
25+
26+
27+
class DeployContainerBaseTask(FlavorBaseTask, UploadContainerParameter):
28+
# TODO check if upload was successfull by requesting the file
29+
# TODO add error checks and propose reasons for the error
30+
# TODO extract bucketfs interaction into own module
31+
32+
release_goal = luigi.Parameter()
33+
34+
def __init__(self, *args, **kwargs):
35+
self.export_info_future = None
36+
super().__init__(*args, **kwargs)
37+
38+
def register_required(self):
39+
task = self.get_export_task()
40+
self.export_info_future = self.register_dependency(task)
41+
42+
def get_export_task(self):
43+
raise AbstractMethodException()
44+
45+
def run_task(self):
46+
export_info = self.get_values_from_future(self.export_info_future)
47+
path_in_bucket = self._upload_container(export_info)
48+
language_definition = LanguageDefinition(
49+
release_name=self._get_complete_release_name(export_info),
50+
flavor_path=self.flavor_path,
51+
bucketfs_name=self.bucketfs_name,
52+
bucket_name=self.bucket_name,
53+
path_in_bucket=self.path_in_bucket,
54+
)
55+
language_definitions = language_definition.generate_definition().split(" ")
56+
language_def_components_list = list()
57+
for lang_def in language_definitions:
58+
alias, url = parse_language_definition(lang_def)
59+
language_def_components_list.append(
60+
LanguageDefinitionComponents(alias=alias, url=url)
61+
)
62+
63+
lang_def_builder = LanguageDefinitionsBuilder(language_def_components_list)
64+
try:
65+
release_path = Path(export_info.cache_file).relative_to(Path("").absolute())
66+
except ValueError:
67+
release_path = Path(export_info.cache_file)
68+
69+
result = DeployInfo(
70+
release_path=str(release_path),
71+
complete_release_name=self._get_complete_release_name(export_info),
72+
human_readable_location=self._complete_url(export_info),
73+
language_definition_builder=lang_def_builder,
74+
)
75+
self.return_object(result)
76+
77+
def build_file_path_in_bucket(self, release_info: ExportInfo) -> bfs.path.PathLike:
78+
backend = bfs.path.StorageBackend.onprem
79+
80+
complete_release_name = self._get_complete_release_name(release_info)
81+
verify = self.ssl_cert_path or self.use_ssl_cert_validation
82+
path_in_bucket_to_upload_path = bfs.path.build_path(
83+
backend=backend,
84+
url=self._url,
85+
bucket_name=self.bucket_name,
86+
service_name=self.bucketfs_name,
87+
username=self.bucketfs_username,
88+
password=self.bucketfs_password,
89+
verify=verify,
90+
path=self.path_in_bucket or "",
91+
)
92+
return path_in_bucket_to_upload_path / f"{complete_release_name}.tar.gz"
93+
94+
@property
95+
def _url(self) -> str:
96+
return f"{self._get_url_prefix()}{self.database_host}:{self.bucketfs_port}"
97+
98+
def _complete_url(self, export_info: ExportInfo):
99+
path_in_bucket = (
100+
f"{self.path_in_bucket}/" if self.path_in_bucket not in [None, ""] else ""
101+
)
102+
return f"{self._url}/{self.bucket_name}/{path_in_bucket}{self._get_complete_release_name(export_info)}.tar.gz"
103+
104+
def _upload_container(self, release_info: ExportInfo) -> bfs.path.PathLike:
105+
bucket_path = self.build_file_path_in_bucket(release_info)
106+
self.logger.info(
107+
f"Upload {release_info.cache_file} to {self._complete_url(release_info)}"
108+
)
109+
with open(release_info.cache_file, "rb") as file:
110+
bucket_path.write(file)
111+
return bucket_path
112+
113+
def _get_complete_release_name(self, release_info: ExportInfo):
114+
complete_release_name = f"""{release_info.name}-{release_info.release_goal}-{self._get_release_name(
115+
release_info)}"""
116+
return complete_release_name
117+
118+
def _get_release_name(self, release_info: ExportInfo):
119+
if self.release_name is None:
120+
release_name = release_info.hash
121+
else:
122+
release_name = self.release_name
123+
return release_name
124+
125+
def _get_url_prefix(self):
126+
if self.bucketfs_https:
127+
url_prefix = "https://"
128+
else:
129+
url_prefix = "http://"
130+
return url_prefix
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import importlib
2+
3+
import luigi
4+
from exasol_integration_test_docker_environment.lib.base.json_pickle_parameter import (
5+
JsonPickleParameter,
6+
)
7+
from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import (
8+
RequiredTaskInfo,
9+
)
10+
11+
from exasol.slc.internal.tasks.upload.deploy_container_base_task import (
12+
DeployContainerBaseTask,
13+
)
14+
15+
16+
class DeployContainerTask(DeployContainerBaseTask):
17+
# We need to create the ExportContainerTask for UploadContainerTask dynamically,
18+
# because we want to push as soon as possible after an image was build and
19+
# don't want to wait for the push finishing before starting the build of depended images,
20+
# but we also need to create a UploadContainerTask for each ExportContainerTask of a goal
21+
22+
required_task_info = JsonPickleParameter(
23+
RequiredTaskInfo,
24+
visibility=luigi.parameter.ParameterVisibility.HIDDEN,
25+
significant=True,
26+
) # type: RequiredTaskInfo
27+
28+
def get_export_task(self):
29+
module = importlib.import_module(
30+
self.required_task_info.module_name # pylint: disable=no-member
31+
)
32+
class_ = getattr(
33+
module, self.required_task_info.class_name # pylint: disable=no-member
34+
)
35+
instance = self.create_child_task(
36+
class_, **self.required_task_info.params # pylint: disable=no-member
37+
)
38+
return instance
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from typing import Dict
2+
3+
from exasol_integration_test_docker_environment.lib.docker.images.create.docker_image_create_task import (
4+
DockerCreateImageTask,
5+
)
6+
from exasol_integration_test_docker_environment.lib.docker.images.required_task_info import (
7+
RequiredTaskInfo,
8+
)
9+
10+
from exasol.slc.internal.tasks.upload.deploy_container_task import DeployContainerTask
11+
from exasol.slc.internal.tasks.upload.upload_containers_parameter import (
12+
UploadContainersParameter,
13+
)
14+
15+
16+
class DeployContainerTasksCreator:
17+
18+
def __init__(self, task: UploadContainersParameter):
19+
self.task = task
20+
21+
def create_deploy_tasks(self, build_tasks: Dict[str, DockerCreateImageTask]):
22+
return {
23+
release_goal: self._create_deploy_task(release_goal, build_task)
24+
for release_goal, build_task in build_tasks.items()
25+
}
26+
27+
def _create_deploy_task(self, release_goal: str, build_task: DockerCreateImageTask):
28+
required_task_info = self._create_required_task_info(build_task)
29+
return self.task.create_child_task_with_common_params( # type: ignore
30+
DeployContainerTask,
31+
required_task_info=required_task_info,
32+
release_goal=release_goal,
33+
)
34+
35+
@staticmethod
36+
def _create_required_task_info(build_task):
37+
required_task_info = RequiredTaskInfo(
38+
module_name=build_task.__module__,
39+
class_name=build_task.__class__.__name__,
40+
params=build_task.param_kwargs,
41+
)
42+
return required_task_info
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import Dict
2+
3+
from exasol_integration_test_docker_environment.lib.base.flavor_task import (
4+
FlavorsBaseTask,
5+
)
6+
7+
from exasol.slc.internal.tasks.build.docker_flavor_build_base import (
8+
DockerFlavorBuildBase,
9+
)
10+
from exasol.slc.internal.tasks.export.export_container_tasks_creator import (
11+
ExportContainerTasksCreator,
12+
)
13+
from exasol.slc.internal.tasks.upload.deploy_container_tasks_creator import (
14+
DeployContainerTasksCreator,
15+
)
16+
from exasol.slc.internal.tasks.upload.upload_containers_parameter import (
17+
UploadContainersParameter,
18+
)
19+
20+
21+
class DeployContainers(FlavorsBaseTask, UploadContainersParameter):
22+
23+
def __init__(self, *args, **kwargs):
24+
self.lang_def_builders_futures = None
25+
super().__init__(*args, **kwargs)
26+
27+
def register_required(self):
28+
tasks = self.create_tasks_for_flavors_with_common_params( # type: ignore
29+
DeployFlavorContainers
30+
) # type: Dict[str,DeployFlavorContainers]
31+
self.lang_def_builders_futures = self.register_dependencies(tasks)
32+
33+
def run_task(self):
34+
lang_definitionbuilders = self.get_values_from_futures(
35+
self.lang_def_builders_futures
36+
)
37+
self.return_object(lang_definitionbuilders)
38+
39+
40+
class DeployFlavorContainers(DockerFlavorBuildBase, UploadContainersParameter):
41+
42+
def get_goals(self):
43+
return set(self.release_goals)
44+
45+
def run_task(self):
46+
build_tasks = self.create_build_tasks()
47+
48+
export_tasks = self.create_export_tasks(build_tasks)
49+
deploy_tasks = self.create_deploy_tasks(export_tasks)
50+
51+
lang_definitions_futures = yield from self.run_dependencies(deploy_tasks)
52+
language_definition = self.get_values_from_futures(lang_definitions_futures)
53+
self.return_object(language_definition)
54+
55+
def create_deploy_tasks(self, export_tasks):
56+
deploy_tasks_creator = DeployContainerTasksCreator(self)
57+
deploy_tasks = deploy_tasks_creator.create_deploy_tasks(export_tasks)
58+
return deploy_tasks
59+
60+
def create_export_tasks(self, build_tasks):
61+
export_tasks_creator = ExportContainerTasksCreator(self, export_path=None)
62+
export_tasks = export_tasks_creator.create_export_tasks(build_tasks)
63+
return export_tasks

0 commit comments

Comments
 (0)