forked from opensearch-project/opensearch-build
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bundle.py
143 lines (126 loc) · 6.12 KB
/
bundle.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# Copyright OpenSearch Contributors
# SPDX-License-Identifier: Apache-2.0
#
# The OpenSearch Contributors require contributions made to
# this file be licensed under the Apache-2.0 license or a
# compatible open source license.
import errno
import logging
import os
import shutil
import subprocess
from abc import ABC, abstractmethod
from typing import Any, List
from assemble_workflow.bundle_recorder import BundleRecorder
from assemble_workflow.dist import Dist
from assemble_workflow.dists import Dists
from manifests.build_manifest import BuildComponent, BuildComponents, BuildManifest
from paths.script_finder import ScriptFinder
from system.temporary_directory import TemporaryDirectory
"""
This class is responsible for executing the build of the full bundle and passing results to a bundle recorder.
It requires a min tarball distribution where plugins will be installed and the path to an artifacts directory where
plugins can be found.
"""
class Bundle(ABC):
def __enter__(self) -> 'Bundle':
return self
def __exit__(self, exc_type: Any, exc_value: Any, exc_traceback: Any) -> None:
self.tmp_dir.__exit__(exc_type, exc_value, exc_traceback)
def __init__(self, build_manifest: BuildManifest, artifacts_dir: str, bundle_recorder: BundleRecorder, keep: bool = False) -> None:
"""
Construct a new Bundle instance.
:param build_manifest: A BuildManifest created from the build workflow.
:param artifacts_dir: Dir location where build artifacts can be found locally
:param bundle_recorder: The bundle recorder that will capture and build a BundleManifest
"""
self.build = build_manifest.build
self.components = build_manifest.components
self.artifacts_dir = artifacts_dir
self.bundle_recorder = bundle_recorder
self.tmp_dir = TemporaryDirectory(keep=keep)
self.min_bundle = self.__get_min_bundle(build_manifest.components)
self.min_dist = self.__get_min_dist(build_manifest.components)
self.installed_plugins: List[str] = []
def install_min(self) -> None:
install_script = ScriptFinder.find_install_script(self.min_dist.name)
install_command = " ".join(
filter(
None,
[
"bash",
install_script,
f"-v {self.build.version}",
f"-p {self.build.platform}",
f"-a {self.build.architecture}",
f"-d {self.build.distribution}" if self.build.distribution else None,
f"-f {self.artifacts_dir}",
f"-o {self.min_dist.archive_path}",
]
)
)
self._execute(install_command)
def install_components(self) -> None:
for c in self.components.values():
if self.min_bundle == c:
pass
elif "plugins" in c.artifacts:
logging.info(f"Installing {c.name}")
self.install_plugin(c)
else:
logging.info(f"Recording {c.name}")
self.bundle_recorder.record_component(c)
plugins_path = os.path.join(self.min_dist.archive_path, "plugins")
if os.path.isdir(plugins_path):
self.installed_plugins = os.listdir(plugins_path)
@abstractmethod
def install_plugin(self, plugin: BuildComponent) -> None:
install_script = ScriptFinder.find_install_script(plugin.name)
install_command = " ".join(
[
"bash",
install_script,
f"-v {self.build.version}",
f"-p {self.build.platform}",
f"-a {self.build.architecture}",
f"-f {self.artifacts_dir}",
f"-o {self.min_dist.archive_path}",
]
)
self._execute(install_command)
def package(self, dest: str) -> None:
self.min_dist.build(self.bundle_recorder.package_name, dest)
def _execute(self, command: str) -> None:
logging.info(f'Executing "{command}" in {self.min_dist.archive_path}')
subprocess.check_call(command, cwd=self.min_dist.archive_path, shell=True)
def _copy_component(self, component: BuildComponent, component_type: str) -> str:
rel_path = self.__get_rel_path(component, component_type)
tmp_path = self.__copy_component_files(rel_path, self.tmp_dir.name)
self.bundle_recorder.record_component(component, rel_path)
return tmp_path
def __get_rel_path(self, component: BuildComponent, component_type: str) -> str:
return next(iter(component.artifacts.get(component_type, [])), None)
def __copy_component_files(self, rel_path: str, dest: str) -> str:
local_path = os.path.join(self.artifacts_dir, rel_path)
dest_path = os.path.join(dest, os.path.basename(local_path))
if os.path.isfile(local_path):
# rel path provided, in this case we copy it into dest
shutil.copyfile(local_path, dest_path)
return os.path.join(dest, os.path.basename(local_path))
else:
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), local_path)
def __get_min_bundle(self, build_components: BuildComponents) -> BuildComponent:
min_bundle = next(iter([c for c in build_components.values() if "dist" in c.artifacts]), None)
if min_bundle is None:
raise ValueError('Missing min "dist" in input artifacts.')
return min_bundle
def __get_min_dist(self, build_components: BuildComponents) -> Dist:
min_dist_path = self._copy_component(self.min_bundle, "dist")
logging.info(f"Copied min bundle to {min_dist_path}.")
min_path = f"{self.build.filename}-{self.build.version}".replace("-SNAPSHOT", "")
logging.info(f"Start creating distribution {self.build.distribution} for {self.min_bundle.name}.")
min_dist = Dists.create_dist(self.min_bundle.name, min_dist_path, min_path, self.build)
logging.info(f"Extracting dist into {self.tmp_dir.name}.")
min_dist.extract(self.tmp_dir.name)
logging.info(f"Extracted dist into {self.tmp_dir.name}.")
return min_dist