cloud-submit 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloud_submit/__init__.py +30 -0
- cloud_submit/cli.py +1014 -0
- cloud_submit/config.py +164 -0
- cloud_submit/controller.py +375 -0
- cloud_submit/environment_handler.py +398 -0
- cloud_submit/envs/aws/__init__.py +2 -0
- cloud_submit/envs/aws/local_environment_handler.py +478 -0
- cloud_submit/envs/aws/local_execution_handler.py +5 -0
- cloud_submit/envs/aws/remote_environment_handler.py +401 -0
- cloud_submit/envs/aws/remote_execution_handler.py +87 -0
- cloud_submit/envs/aws/s3_tools.py +46 -0
- cloud_submit/envs/local/environment_handler.py +121 -0
- cloud_submit/envs/local/execution_handler.py +5 -0
- cloud_submit/execution/base_execution_handler.py +49 -0
- cloud_submit/execution/config.py +248 -0
- cloud_submit/execution/execute.py +108 -0
- cloud_submit/execution/utils.py +98 -0
- cloud_submit/images.py +58 -0
- cloud_submit/utils.py +67 -0
- cloud_submit-0.1.dist-info/METADATA +12 -0
- cloud_submit-0.1.dist-info/RECORD +24 -0
- cloud_submit-0.1.dist-info/WHEEL +4 -0
- cloud_submit-0.1.dist-info/entry_points.txt +2 -0
- cloud_submit-0.1.dist-info/licenses/LICENSE +19 -0
cloud_submit/config.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import contextlib
|
|
3
|
+
|
|
4
|
+
from .execution.utils import write_json
|
|
5
|
+
from .execution.config import ConfigError
|
|
6
|
+
from .envs.local.environment_handler import LocalEnv
|
|
7
|
+
from .images import BaseImage, ExecutionImage
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _dictify(items, kind):
|
|
11
|
+
if items is None:
|
|
12
|
+
items = []
|
|
13
|
+
item_dict = {}
|
|
14
|
+
for item in items:
|
|
15
|
+
if item.name in item_dict:
|
|
16
|
+
raise ConfigError(f'Duplicate {kind} name {item.name}.')
|
|
17
|
+
if kind == 'image' and item.parent is not None:
|
|
18
|
+
if item.parent not in item_dict:
|
|
19
|
+
raise ConfigError(
|
|
20
|
+
f'Parent image of {item.name} must be '
|
|
21
|
+
'listed before {item.name}.'
|
|
22
|
+
)
|
|
23
|
+
if isinstance(item_dict[item.parent], ExecutionImage):
|
|
24
|
+
raise ConfigError(
|
|
25
|
+
f'Image {item.parent} cannot be the parent of {item.name} '
|
|
26
|
+
'since it is an execution image.'
|
|
27
|
+
)
|
|
28
|
+
item_dict[item.name] = item
|
|
29
|
+
return item_dict
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Config:
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
project_name,
|
|
36
|
+
user_name,
|
|
37
|
+
project_root=None,
|
|
38
|
+
images=None,
|
|
39
|
+
artifacts=None,
|
|
40
|
+
pipelines=None,
|
|
41
|
+
environments=None,
|
|
42
|
+
build_default=None,
|
|
43
|
+
run_default=None,
|
|
44
|
+
):
|
|
45
|
+
self.project_name = project_name
|
|
46
|
+
self.user_name = user_name
|
|
47
|
+
self.project_root = project_root
|
|
48
|
+
self.environments = _dictify(environments, 'environment')
|
|
49
|
+
self.images = _dictify(images, 'image')
|
|
50
|
+
self.artifacts = _dictify(artifacts, 'artifact')
|
|
51
|
+
self.pipelines = _dictify(pipelines, 'pipeline')
|
|
52
|
+
self.build_default = build_default
|
|
53
|
+
self.run_default = run_default
|
|
54
|
+
|
|
55
|
+
if self.project_root is None:
|
|
56
|
+
self.project_root = os.getcwd()
|
|
57
|
+
self.project_root = os.path.abspath(self.project_root)
|
|
58
|
+
|
|
59
|
+
# Create local environment if no environments are given.
|
|
60
|
+
if not self.environments:
|
|
61
|
+
self.environments['local'] = LocalEnv(
|
|
62
|
+
name='local',
|
|
63
|
+
project=self.project_name,
|
|
64
|
+
user=self.user_name,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Set default environment to first item in the list if not given.
|
|
68
|
+
firstenv = next(iter(self.environments.keys()))
|
|
69
|
+
if self.build_default is None:
|
|
70
|
+
self.build_default = firstenv
|
|
71
|
+
if self.run_default is None:
|
|
72
|
+
self.run_default = firstenv
|
|
73
|
+
|
|
74
|
+
# check if default environments exist
|
|
75
|
+
if self.build_default not in self.environments:
|
|
76
|
+
raise ConfigError(
|
|
77
|
+
f'Default build environment {repr(self.build_default)} '
|
|
78
|
+
'not found in environment list.'
|
|
79
|
+
)
|
|
80
|
+
if self.run_default not in self.environments:
|
|
81
|
+
raise ConfigError(
|
|
82
|
+
f'Default run environment {repr(self.run_default)} '
|
|
83
|
+
'not found in environment list.'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# check pipelines
|
|
87
|
+
for pipeline_name, pipeline in self.pipelines.items():
|
|
88
|
+
for step in pipeline.steps:
|
|
89
|
+
if step.image not in self.images:
|
|
90
|
+
raise ConfigError(
|
|
91
|
+
f'Step {repr(step.name)} in pipeline '
|
|
92
|
+
f'{repr(pipeline_name)} requires unknown image '
|
|
93
|
+
f'{repr(step.image)}.'
|
|
94
|
+
)
|
|
95
|
+
image = self.images[step.image]
|
|
96
|
+
if not isinstance(image, ExecutionImage):
|
|
97
|
+
raise ConfigError(
|
|
98
|
+
f'Step {repr(step.name)} in pipeline '
|
|
99
|
+
f'{repr(pipeline_name)} requires image '
|
|
100
|
+
f'{repr(step.image)}, which is not an execution image.'
|
|
101
|
+
)
|
|
102
|
+
for a in step.inputs.values():
|
|
103
|
+
if a.artifact_name not in self.artifacts:
|
|
104
|
+
raise ConfigError(
|
|
105
|
+
f'Step {repr(step.name)} in pipeline '
|
|
106
|
+
f'{repr(pipeline_name)} uses undeclared '
|
|
107
|
+
f'input artifact {repr(a.artifact_name)} '
|
|
108
|
+
)
|
|
109
|
+
for a in step.outputs.values():
|
|
110
|
+
if a.artifact_name not in self.artifacts:
|
|
111
|
+
raise ConfigError(
|
|
112
|
+
f'Step {repr(step.name)} in pipeline '
|
|
113
|
+
f'{repr(pipeline_name)} uses undeclared '
|
|
114
|
+
f'output artifact {repr(a.artifact_name)} '
|
|
115
|
+
)
|
|
116
|
+
for a in step.temporaries.values():
|
|
117
|
+
if a.artifact_name not in self.artifacts:
|
|
118
|
+
raise ConfigError(
|
|
119
|
+
f'Step {repr(step.name)} in pipeline '
|
|
120
|
+
f'{repr(pipeline_name)} uses undeclared '
|
|
121
|
+
f'temporary artifact {repr(a.artifact_name)} '
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
def export_pipelines(self, path):
|
|
125
|
+
obj = {}
|
|
126
|
+
for name, pipeline in self.pipelines.items():
|
|
127
|
+
obj[name] = [step.to_dict() for step in pipeline.steps]
|
|
128
|
+
write_json(obj, path)
|
|
129
|
+
|
|
130
|
+
def export_artifacts(self, path):
|
|
131
|
+
obj = {'artifacts': [a.to_dict() for a in self.artifacts.values()]}
|
|
132
|
+
write_json(obj, path)
|
|
133
|
+
|
|
134
|
+
def _get_env_handler(self, env, purpose):
|
|
135
|
+
if env is None:
|
|
136
|
+
if purpose == 'build':
|
|
137
|
+
env = self.build_default
|
|
138
|
+
elif purpose == 'run':
|
|
139
|
+
env = self.run_default
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f'Invalid `purpose` argument {repr(purpose)}.')
|
|
142
|
+
try:
|
|
143
|
+
return self.environments[env]
|
|
144
|
+
except KeyError:
|
|
145
|
+
raise ValueError(f'Unknown environment {env}.')
|
|
146
|
+
|
|
147
|
+
def get_build_env(self, env=None):
|
|
148
|
+
return self._get_env_handler(env, 'build')
|
|
149
|
+
|
|
150
|
+
def get_run_env(self, env=None):
|
|
151
|
+
return self._get_env_handler(env, 'run')
|
|
152
|
+
|
|
153
|
+
def get_image_ancestry(self, image_name):
|
|
154
|
+
images = []
|
|
155
|
+
while image_name is not None:
|
|
156
|
+
image = self.images[image_name]
|
|
157
|
+
images.append(image_name)
|
|
158
|
+
image_name = image.parent
|
|
159
|
+
|
|
160
|
+
return [self.images[name] for name in images[::-1]]
|
|
161
|
+
|
|
162
|
+
def in_project_root(self):
|
|
163
|
+
return contextlib.chdir(self.project_root)
|
|
164
|
+
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import datetime as dt
|
|
4
|
+
|
|
5
|
+
from .utils import parse_image_ref
|
|
6
|
+
from .execution.utils import ensure_path, CloudSubmitError
|
|
7
|
+
from .images import ExecutionImage, BaseImage
|
|
8
|
+
from .execution.config import to_utc
|
|
9
|
+
|
|
10
|
+
class Controller:
|
|
11
|
+
def __init__(self, config):
|
|
12
|
+
self._config = config
|
|
13
|
+
|
|
14
|
+
def _install_execution_modules(self, path):
|
|
15
|
+
sourcedir = os.path.dirname(__file__)
|
|
16
|
+
shutil.copyfile(
|
|
17
|
+
os.path.join(sourcedir, 'execution', 'config.py'),
|
|
18
|
+
os.path.join(path, 'config.py'),
|
|
19
|
+
)
|
|
20
|
+
shutil.copyfile(
|
|
21
|
+
os.path.join(sourcedir, 'execution', 'base_execution_handler.py'),
|
|
22
|
+
os.path.join(path, 'base_execution_handler.py'),
|
|
23
|
+
)
|
|
24
|
+
shutil.copyfile(
|
|
25
|
+
os.path.join(sourcedir, 'execution', 'utils.py'),
|
|
26
|
+
os.path.join(path, 'utils.py'),
|
|
27
|
+
)
|
|
28
|
+
shutil.copyfile(
|
|
29
|
+
os.path.join(sourcedir, 'execution', 'execute.py'),
|
|
30
|
+
os.path.join(path, 'execute.py'),
|
|
31
|
+
)
|
|
32
|
+
self._config.export_pipelines(os.path.join(path, 'pipelines.json'))
|
|
33
|
+
self._config.export_artifacts(os.path.join(path, 'artifacts.json'))
|
|
34
|
+
|
|
35
|
+
def _build_image(self, image, build_id, env, rebuild=False):
|
|
36
|
+
if not rebuild:
|
|
37
|
+
ref = env.get_image_ref(image)
|
|
38
|
+
if ref is not None:
|
|
39
|
+
print(f'Using existing build of {image.name}: {ref}')
|
|
40
|
+
return ref
|
|
41
|
+
|
|
42
|
+
is_execution_image = isinstance(image, ExecutionImage)
|
|
43
|
+
path = os.path.join('temp', 'build', image.name)
|
|
44
|
+
if is_execution_image:
|
|
45
|
+
path = os.path.join(path, env.name)
|
|
46
|
+
ensure_path(path, clear=True)
|
|
47
|
+
shutil.copytree('src', os.path.join(path, 'src'))
|
|
48
|
+
csub_path = os.path.join(path, 'src', 'csub')
|
|
49
|
+
ensure_path(csub_path, clear=True)
|
|
50
|
+
self._install_execution_modules(csub_path)
|
|
51
|
+
env.install_execution_handler(csub_path)
|
|
52
|
+
else:
|
|
53
|
+
ensure_path(path, clear=True)
|
|
54
|
+
|
|
55
|
+
parent_ref = None
|
|
56
|
+
if image.parent is not None:
|
|
57
|
+
parent_image = self._config.images.get(image.parent)
|
|
58
|
+
if parent_image is None:
|
|
59
|
+
raise CloudSubmitError(
|
|
60
|
+
f'Could not find declaration for image {image.parent}.')
|
|
61
|
+
parent_ref = env.get_image_ref(parent_image)
|
|
62
|
+
if parent_ref is None:
|
|
63
|
+
raise CloudSubmitError(
|
|
64
|
+
f'Could not find reference for image {image.parent}.')
|
|
65
|
+
env.pull_base_image(parent_ref)
|
|
66
|
+
image.setup_builddir(path, parent_ref)
|
|
67
|
+
|
|
68
|
+
print(f'Building image {image.name}.')
|
|
69
|
+
ref = env.build_image(path, image, build_id)
|
|
70
|
+
if ref is None:
|
|
71
|
+
raise CloudSubmitError(
|
|
72
|
+
'build_image method of environment handler did not '
|
|
73
|
+
'return a valid image reference.'
|
|
74
|
+
)
|
|
75
|
+
env.save_image_ref(image, ref)
|
|
76
|
+
|
|
77
|
+
def build(self, images, build_id=None, env=None):
|
|
78
|
+
now = dt.datetime.now(tz=dt.UTC)
|
|
79
|
+
env_handler = self._config.get_build_env(env)
|
|
80
|
+
build_id = env_handler.generate_build_id(now, build_id)
|
|
81
|
+
|
|
82
|
+
images = set(images)
|
|
83
|
+
for image in images:
|
|
84
|
+
if image not in self._config.images:
|
|
85
|
+
raise CloudSubmitError(
|
|
86
|
+
f'No definition found for image {image}.'
|
|
87
|
+
)
|
|
88
|
+
all_images = set(
|
|
89
|
+
i.name for image in images
|
|
90
|
+
for i in self._config.get_image_ancestry(image)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
with self._config.in_project_root():
|
|
94
|
+
for image_name, image in self._config.images.items():
|
|
95
|
+
if image_name not in all_images:
|
|
96
|
+
continue
|
|
97
|
+
rebuild = image_name in images
|
|
98
|
+
self._build_image(
|
|
99
|
+
image, build_id, env_handler, rebuild=rebuild)
|
|
100
|
+
|
|
101
|
+
def list_image_refs(self, images=None, ids=None, env=None, remote=False):
|
|
102
|
+
env_handler = self._config.get_build_env(env)
|
|
103
|
+
if images is None:
|
|
104
|
+
images = list(self._config.images.keys())
|
|
105
|
+
else:
|
|
106
|
+
images = set(images)
|
|
107
|
+
images = [
|
|
108
|
+
i for i in self._config.images.keys()
|
|
109
|
+
if i in images
|
|
110
|
+
]
|
|
111
|
+
if ids is not None:
|
|
112
|
+
ids = set(ids)
|
|
113
|
+
|
|
114
|
+
results = []
|
|
115
|
+
for image in images:
|
|
116
|
+
image = self._config.images[image]
|
|
117
|
+
repo_name = env_handler.get_image_repo_name(image)
|
|
118
|
+
if remote:
|
|
119
|
+
tags = env_handler.list_remote_image_tags(repo_name)
|
|
120
|
+
else:
|
|
121
|
+
tags = env_handler.list_local_image_tags(repo_name)
|
|
122
|
+
if ids is not None:
|
|
123
|
+
tags = [t for t in tags if t in ids]
|
|
124
|
+
for tag in tags:
|
|
125
|
+
results.append(':'.join([repo_name, tag]))
|
|
126
|
+
return results
|
|
127
|
+
|
|
128
|
+
def list_images(self, images=None, env=None):
|
|
129
|
+
env_handler = self._config.get_build_env(env)
|
|
130
|
+
if images is not None:
|
|
131
|
+
images = set(images)
|
|
132
|
+
result = []
|
|
133
|
+
with self._config.in_project_root():
|
|
134
|
+
for i in self._config.images.values():
|
|
135
|
+
if images is not None and i.name not in images:
|
|
136
|
+
continue
|
|
137
|
+
if isinstance(i, BaseImage):
|
|
138
|
+
tpe = 'base'
|
|
139
|
+
elif isinstance(i, ExecutionImage):
|
|
140
|
+
tpe = 'execution'
|
|
141
|
+
else:
|
|
142
|
+
raise ValueError('Unknown image type.')
|
|
143
|
+
ref = env_handler.get_image_ref(i)
|
|
144
|
+
result.append((i.name, tpe, ref))
|
|
145
|
+
return result
|
|
146
|
+
|
|
147
|
+
def remove_image_refs(self, refs, remote=False, env=None):
|
|
148
|
+
env_handler = self._config.get_build_env(env)
|
|
149
|
+
if remote:
|
|
150
|
+
env_handler.remove_remote_image_refs(refs)
|
|
151
|
+
else:
|
|
152
|
+
env_handler.remove_local_image_refs(refs)
|
|
153
|
+
|
|
154
|
+
def set_image(self, image_name, ref, env=None):
|
|
155
|
+
env_handler = self._config.get_build_env(env)
|
|
156
|
+
image = self._config.images.get(image_name)
|
|
157
|
+
if image is None:
|
|
158
|
+
raise CloudSubmitError(f'Unknown image name {image_name}.')
|
|
159
|
+
with self._config.in_project_root():
|
|
160
|
+
env_handler.save_image_ref(image, ref)
|
|
161
|
+
|
|
162
|
+
def unset_image(self, image_name, env=None):
|
|
163
|
+
env_handler = self._config.get_build_env(env)
|
|
164
|
+
image = self._config.images.get(image_name)
|
|
165
|
+
if image is None:
|
|
166
|
+
raise CloudSubmitError(f'Unknown image name {image_name}.')
|
|
167
|
+
with self._config.in_project_root():
|
|
168
|
+
env_handler.clear_image_ref(image)
|
|
169
|
+
|
|
170
|
+
def list_artifacts(
|
|
171
|
+
self,
|
|
172
|
+
artifact_names=None,
|
|
173
|
+
run_ids=None,
|
|
174
|
+
remote=False,
|
|
175
|
+
env=None,
|
|
176
|
+
):
|
|
177
|
+
if artifact_names is None:
|
|
178
|
+
artifact_names = list(self._config.artifacts.keys())
|
|
179
|
+
artifacts = []
|
|
180
|
+
for name in artifact_names:
|
|
181
|
+
try:
|
|
182
|
+
artifact = self._config.artifacts[name]
|
|
183
|
+
except KeyError:
|
|
184
|
+
raise CloudSubmitError(f'Unknown artifact name: {name}.')
|
|
185
|
+
artifacts.append(artifact.copy())
|
|
186
|
+
|
|
187
|
+
env_handler = self._config.get_run_env(env)
|
|
188
|
+
with self._config.in_project_root():
|
|
189
|
+
if remote:
|
|
190
|
+
runs = env_handler.list_remote_artifacts(
|
|
191
|
+
artifacts, run_ids=run_ids)
|
|
192
|
+
else:
|
|
193
|
+
runs = env_handler.list_local_artifacts(
|
|
194
|
+
artifacts, run_ids=run_ids)
|
|
195
|
+
|
|
196
|
+
return [a.name for a in artifacts], runs
|
|
197
|
+
|
|
198
|
+
def remove_artifacts(
|
|
199
|
+
self,
|
|
200
|
+
artifact_names,
|
|
201
|
+
run_ids,
|
|
202
|
+
remote=False,
|
|
203
|
+
env=None,
|
|
204
|
+
):
|
|
205
|
+
env_handler = self._config.get_run_env(env)
|
|
206
|
+
artifacts = [
|
|
207
|
+
self._config.artifacts[name].copy() for name in artifact_names]
|
|
208
|
+
with self._config.in_project_root():
|
|
209
|
+
if remote:
|
|
210
|
+
env_handler.remove_remote_artifacts(artifacts, run_ids)
|
|
211
|
+
else:
|
|
212
|
+
env_handler.remove_local_artifacts(artifacts, run_ids)
|
|
213
|
+
|
|
214
|
+
def pull_artifacts(
|
|
215
|
+
self,
|
|
216
|
+
artifact_names,
|
|
217
|
+
run_ids,
|
|
218
|
+
env=None,
|
|
219
|
+
):
|
|
220
|
+
env_handler = self._config.get_run_env(env)
|
|
221
|
+
artifacts = [
|
|
222
|
+
self._config.artifacts[name].copy() for name in artifact_names]
|
|
223
|
+
with self._config.in_project_root():
|
|
224
|
+
env_handler.remove_local_artifacts(artifacts, run_ids)
|
|
225
|
+
env_handler.pull_artifacts(artifacts, run_ids)
|
|
226
|
+
|
|
227
|
+
def push_artifacts(
|
|
228
|
+
self,
|
|
229
|
+
artifact_names,
|
|
230
|
+
run_ids,
|
|
231
|
+
env=None,
|
|
232
|
+
):
|
|
233
|
+
env_handler = self._config.get_run_env(env)
|
|
234
|
+
artifacts = [
|
|
235
|
+
self._config.artifacts[name].copy() for name in artifact_names]
|
|
236
|
+
with self._config.in_project_root():
|
|
237
|
+
env_handler.remove_remote_artifacts(artifacts, run_ids)
|
|
238
|
+
env_handler.push_artifacts(artifacts, run_ids)
|
|
239
|
+
|
|
240
|
+
def copy_artifacts(
|
|
241
|
+
self,
|
|
242
|
+
artifact_names,
|
|
243
|
+
from_run_id,
|
|
244
|
+
to_run_id,
|
|
245
|
+
remote=False,
|
|
246
|
+
env=None,
|
|
247
|
+
):
|
|
248
|
+
if from_run_id == to_run_id:
|
|
249
|
+
raise CloudSubmitError(
|
|
250
|
+
f'Source and destination run ID are identical: {from_run_id}')
|
|
251
|
+
env_handler = self._config.get_run_env(env)
|
|
252
|
+
artifacts = [
|
|
253
|
+
self._config.artifacts[name].copy() for name in artifact_names
|
|
254
|
+
if self._config.artifacts[name].scope == 'run'
|
|
255
|
+
]
|
|
256
|
+
with self._config.in_project_root():
|
|
257
|
+
if remote:
|
|
258
|
+
env_handler.remove_remote_artifacts(
|
|
259
|
+
artifacts, [[to_run_id]]*len(artifacts))
|
|
260
|
+
env_handler.copy_remote_artifacts(
|
|
261
|
+
artifacts, from_run_id, to_run_id)
|
|
262
|
+
else:
|
|
263
|
+
env_handler.remove_local_artifacts(
|
|
264
|
+
artifacts, [[to_run_id]]*len(artifacts))
|
|
265
|
+
env_handler.copy_local_artifacts(
|
|
266
|
+
artifacts, from_run_id, to_run_id)
|
|
267
|
+
|
|
268
|
+
def move_artifacts(
|
|
269
|
+
self,
|
|
270
|
+
artifact_names,
|
|
271
|
+
from_run_id,
|
|
272
|
+
to_run_id,
|
|
273
|
+
remote=False,
|
|
274
|
+
env=None,
|
|
275
|
+
):
|
|
276
|
+
if from_run_id == to_run_id:
|
|
277
|
+
raise CloudSubmitError(
|
|
278
|
+
f'Source and destination run ID are identical: {from_run_id}')
|
|
279
|
+
env_handler = self._config.get_run_env(env)
|
|
280
|
+
artifacts = [
|
|
281
|
+
self._config.artifacts[name].copy() for name in artifact_names
|
|
282
|
+
if self._config.artifacts[name].scope == 'run'
|
|
283
|
+
]
|
|
284
|
+
with self._config.in_project_root():
|
|
285
|
+
if remote:
|
|
286
|
+
env_handler.remove_remote_artifacts(
|
|
287
|
+
artifacts, [[to_run_id]]*len(artifacts))
|
|
288
|
+
env_handler.move_remote_artifacts(
|
|
289
|
+
artifacts, from_run_id, to_run_id)
|
|
290
|
+
else:
|
|
291
|
+
env_handler.remove_local_artifacts(
|
|
292
|
+
artifacts, [[to_run_id]]*len(artifacts))
|
|
293
|
+
env_handler.move_local_artifacts(
|
|
294
|
+
artifacts, from_run_id, to_run_id)
|
|
295
|
+
|
|
296
|
+
def run_pipeline(
|
|
297
|
+
self,
|
|
298
|
+
pipeline,
|
|
299
|
+
steps=None,
|
|
300
|
+
run_id=None,
|
|
301
|
+
timestamp=None,
|
|
302
|
+
env=None,
|
|
303
|
+
build_env=None,
|
|
304
|
+
):
|
|
305
|
+
try:
|
|
306
|
+
pipeline = self._config.pipelines[pipeline]
|
|
307
|
+
except KeyError:
|
|
308
|
+
raise CloudSubmitError(f'Pipeline not found: {pipeline}')
|
|
309
|
+
|
|
310
|
+
if build_env is None:
|
|
311
|
+
build_env = env
|
|
312
|
+
|
|
313
|
+
now = dt.datetime.now(tz=dt.UTC)
|
|
314
|
+
if isinstance(timestamp, str) and timestamp == 'now':
|
|
315
|
+
timestamp = now
|
|
316
|
+
if timestamp is None:
|
|
317
|
+
timestamp = pipeline.default_submit_timestamp
|
|
318
|
+
if timestamp is None:
|
|
319
|
+
timestamp = now
|
|
320
|
+
timestamp = to_utc(timestamp)
|
|
321
|
+
env_handler = self._config.get_run_env(env)
|
|
322
|
+
run_id = env_handler.generate_run_id(now, run_id)
|
|
323
|
+
if steps is None:
|
|
324
|
+
steps = [step.name for step in pipeline.steps]
|
|
325
|
+
steps = set(steps)
|
|
326
|
+
steps = [step for step in pipeline.steps if step.name in steps]
|
|
327
|
+
|
|
328
|
+
with self._config.in_project_root():
|
|
329
|
+
images = [step.image for step in steps]
|
|
330
|
+
self.build(images, build_id=run_id, env=build_env)
|
|
331
|
+
refs = {}
|
|
332
|
+
overwrite_artifacts = []
|
|
333
|
+
for step in steps:
|
|
334
|
+
image = self._config.images.get(step.image)
|
|
335
|
+
if image is None:
|
|
336
|
+
raise CloudSubmitError(
|
|
337
|
+
f'Cannot find declaration for image {step.image}.')
|
|
338
|
+
ref = env_handler.get_image_ref(image)
|
|
339
|
+
if ref is None:
|
|
340
|
+
raise CloudSubmitError(
|
|
341
|
+
f'Could not find image ref for image {step.image}')
|
|
342
|
+
refs[step.name] = ref
|
|
343
|
+
|
|
344
|
+
for loc in step.outputs.values():
|
|
345
|
+
overwrite_artifacts.append(
|
|
346
|
+
self._config.artifacts[loc.artifact_name].copy())
|
|
347
|
+
for loc in step.temporaries.values():
|
|
348
|
+
overwrite_artifacts.append(
|
|
349
|
+
self._config.artifacts[loc.artifact_name].copy())
|
|
350
|
+
|
|
351
|
+
temp_path = 'temp/run'
|
|
352
|
+
ensure_path(temp_path, clear=True)
|
|
353
|
+
env_handler.run_pipeline(
|
|
354
|
+
pipeline, refs, overwrite_artifacts,
|
|
355
|
+
timestamp, run_id, temp_path,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
'run_id': run_id,
|
|
360
|
+
'start_timestamp': now,
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
def print_logs(
|
|
364
|
+
self,
|
|
365
|
+
run_id,
|
|
366
|
+
since=dt.timedelta(minutes=1),
|
|
367
|
+
env=None,
|
|
368
|
+
stream=False
|
|
369
|
+
):
|
|
370
|
+
if isinstance(since, dt.timedelta):
|
|
371
|
+
since = dt.datetime.now(tz=dt.UTC) - since
|
|
372
|
+
env_handler = self._config.get_run_env(env)
|
|
373
|
+
env_handler.print_logs(run_id, since, stream=stream)
|
|
374
|
+
|
|
375
|
+
|