artefacts-cli 0.8.0__py3-none-any.whl → 0.9.2__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.
- artefacts/cli/__init__.py +67 -19
- artefacts/cli/app.py +196 -167
- artefacts/cli/app_containers.py +97 -25
- artefacts/cli/app_containers.pyi +3 -0
- artefacts/cli/bagparser.py +4 -0
- artefacts/cli/config.py +62 -0
- artefacts/cli/constants.py +7 -0
- artefacts/cli/containers/__init__.py +5 -5
- artefacts/cli/containers/docker_cm.py +175 -0
- artefacts/cli/containers/docker_utils.py +98 -0
- artefacts/cli/containers/utils.py +20 -8
- artefacts/cli/helpers.py +55 -0
- artefacts/cli/i18n.py +35 -0
- artefacts/cli/locales/art.pot +524 -0
- artefacts/cli/locales/base.pot +995 -0
- artefacts/cli/locales/click.pot +496 -0
- artefacts/cli/other.py +1 -0
- artefacts/cli/ros1.py +21 -6
- artefacts/cli/ros2.py +10 -3
- artefacts/cli/utils.py +8 -4
- artefacts/cli/utils_ros.py +35 -9
- artefacts/cli/version.py +2 -2
- artefacts/copava/__init__.py +1 -0
- {artefacts_cli-0.8.0.dist-info → artefacts_cli-0.9.2.dist-info}/METADATA +10 -3
- artefacts_cli-0.9.2.dist-info/RECORD +33 -0
- {artefacts_cli-0.8.0.dist-info → artefacts_cli-0.9.2.dist-info}/WHEEL +1 -1
- artefacts/cli/containers/docker.py +0 -119
- artefacts_cli-0.8.0.dist-info/RECORD +0 -24
- {artefacts_cli-0.8.0.dist-info → artefacts_cli-0.9.2.dist-info}/entry_points.txt +0 -0
- {artefacts_cli-0.8.0.dist-info → artefacts_cli-0.9.2.dist-info}/top_level.txt +0 -0
artefacts/cli/app.py
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
import configparser
|
2
1
|
import getpass
|
3
2
|
import json
|
4
3
|
import os
|
5
4
|
import platform
|
6
5
|
import random
|
7
|
-
import subprocess
|
8
6
|
import tarfile
|
9
7
|
import tempfile
|
10
8
|
import time
|
9
|
+
from typing import Optional
|
11
10
|
from urllib.parse import urlparse
|
12
11
|
import webbrowser
|
13
12
|
|
@@ -17,94 +16,29 @@ import requests
|
|
17
16
|
from pathlib import Path
|
18
17
|
from gitignore_parser import parse_gitignore
|
19
18
|
|
20
|
-
from artefacts.cli import
|
19
|
+
from artefacts.cli import (
|
20
|
+
init_job,
|
21
|
+
generate_scenarios,
|
22
|
+
localise,
|
23
|
+
AuthenticationError,
|
24
|
+
__version__,
|
25
|
+
)
|
21
26
|
from artefacts.cli import app_containers as containers
|
22
|
-
from artefacts.cli.
|
27
|
+
from artefacts.cli.config import APIConf
|
28
|
+
from artefacts.cli.constants import (
|
29
|
+
DEPRECATED_FRAMEWORKS,
|
30
|
+
SUPPORTED_FRAMEWORKS,
|
31
|
+
CONFIG_PATH,
|
32
|
+
)
|
33
|
+
from artefacts.cli.helpers import (
|
34
|
+
add_key_to_conf,
|
35
|
+
get_conf_from_file,
|
36
|
+
get_artefacts_api_url,
|
37
|
+
get_git_revision_branch,
|
38
|
+
get_git_revision_hash,
|
39
|
+
)
|
23
40
|
from artefacts.cli.utils import add_output_from_default, config_validation, read_config
|
24
41
|
|
25
|
-
HOME = os.path.expanduser("~")
|
26
|
-
CONFIG_DIR = f"{HOME}/.artefacts"
|
27
|
-
CONFIG_PATH = f"{CONFIG_DIR}/config"
|
28
|
-
|
29
|
-
|
30
|
-
def get_git_revision_hash() -> str:
|
31
|
-
try:
|
32
|
-
return (
|
33
|
-
subprocess.check_output(["git", "rev-parse", "HEAD"])
|
34
|
-
.decode("ascii")
|
35
|
-
.strip()
|
36
|
-
)
|
37
|
-
except subprocess.CalledProcessError:
|
38
|
-
return ""
|
39
|
-
|
40
|
-
|
41
|
-
def get_git_revision_branch() -> str:
|
42
|
-
try:
|
43
|
-
return (
|
44
|
-
subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
45
|
-
.decode("ascii")
|
46
|
-
.strip()
|
47
|
-
)
|
48
|
-
except subprocess.CalledProcessError:
|
49
|
-
return ""
|
50
|
-
|
51
|
-
|
52
|
-
def get_conf_from_file():
|
53
|
-
config = configparser.ConfigParser()
|
54
|
-
if not os.path.isfile(CONFIG_PATH):
|
55
|
-
os.makedirs(CONFIG_DIR, exist_ok=True)
|
56
|
-
config["DEFAULT"] = {}
|
57
|
-
with open(CONFIG_PATH, "w") as f:
|
58
|
-
config.write(f)
|
59
|
-
config.read(CONFIG_PATH)
|
60
|
-
return config
|
61
|
-
|
62
|
-
|
63
|
-
def get_artefacts_api_url(project_profile):
|
64
|
-
return os.environ.get(
|
65
|
-
"ARTEFACTS_API_URL",
|
66
|
-
project_profile.get(
|
67
|
-
"ApiUrl",
|
68
|
-
"https://app.artefacts.com/api",
|
69
|
-
),
|
70
|
-
)
|
71
|
-
|
72
|
-
|
73
|
-
class APIConf:
|
74
|
-
def __init__(self, project_name: str, job_name: str = None) -> None:
|
75
|
-
config = get_conf_from_file()
|
76
|
-
if project_name in config:
|
77
|
-
profile = config[project_name]
|
78
|
-
else:
|
79
|
-
profile = {}
|
80
|
-
self.api_url = get_artefacts_api_url(profile)
|
81
|
-
self.api_key = os.environ.get("ARTEFACTS_KEY", profile.get("ApiKey", None))
|
82
|
-
if self.api_key is None:
|
83
|
-
batch_id = os.environ.get("AWS_BATCH_JOB_ID", None)
|
84
|
-
job_id = os.environ.get("ARTEFACTS_JOB_ID", None)
|
85
|
-
if batch_id is None or job_id is None:
|
86
|
-
raise click.ClickException(
|
87
|
-
f"No API KEY set. Please run 'artefacts config add {project_name}'"
|
88
|
-
)
|
89
|
-
auth_type = "Internal"
|
90
|
-
# Batch id for array jobs contains array index
|
91
|
-
batch_id = batch_id.split(":")[0]
|
92
|
-
self.headers = {"Authorization": f"{auth_type} {job_id}:{batch_id}"}
|
93
|
-
else:
|
94
|
-
auth_type = "ApiKey"
|
95
|
-
self.headers = {"Authorization": f"{auth_type} {self.api_key}"}
|
96
|
-
self.headers["User-Agent"] = (
|
97
|
-
f"ArtefactsClient/{__version__} ({platform.platform()}/{platform.python_version()})"
|
98
|
-
)
|
99
|
-
if job_name:
|
100
|
-
click.echo(f"[{job_name}] Connecting to {self.api_url} using {auth_type}")
|
101
|
-
else:
|
102
|
-
click.echo(f"Connecting to {self.api_url} using {auth_type}")
|
103
|
-
|
104
|
-
|
105
|
-
def validate_artefacts_config(config_file: str) -> dict:
|
106
|
-
pass
|
107
|
-
|
108
42
|
|
109
43
|
@click.group()
|
110
44
|
def config():
|
@@ -119,15 +53,8 @@ def path():
|
|
119
53
|
click.echo(CONFIG_PATH)
|
120
54
|
|
121
55
|
|
122
|
-
def add_key_to_conf(project_name, api_key):
|
123
|
-
config = get_conf_from_file()
|
124
|
-
config[project_name] = {"ApiKey": api_key}
|
125
|
-
with open(CONFIG_PATH, "w") as f:
|
126
|
-
config.write(f)
|
127
|
-
|
128
|
-
|
129
56
|
@config.command()
|
130
|
-
@click.argument("project_name")
|
57
|
+
@click.argument("project_name", metavar=localise("PROJECT_NAME"))
|
131
58
|
def add(project_name):
|
132
59
|
"""
|
133
60
|
Set configuration for PROJECT_NAME
|
@@ -145,16 +72,26 @@ def add(project_name):
|
|
145
72
|
os.system(f'cmd.exe /C start "" {settings_page_url} 2>/dev/null')
|
146
73
|
else:
|
147
74
|
webbrowser.open(settings_page_url)
|
148
|
-
click.echo(
|
75
|
+
click.echo(
|
76
|
+
localise("Opening the project settings page: {url}").format(
|
77
|
+
url=settings_page_url
|
78
|
+
)
|
79
|
+
)
|
149
80
|
api_key = click.prompt(
|
150
|
-
|
81
|
+
localise("Please enter your API KEY for {project}").format(
|
82
|
+
project=project_name
|
83
|
+
),
|
84
|
+
type=str,
|
85
|
+
hide_input=True,
|
151
86
|
)
|
152
87
|
add_key_to_conf(project_name, api_key)
|
153
|
-
click.echo(
|
88
|
+
click.echo(localise("API KEY saved for {project}").format(project=project_name))
|
154
89
|
if click.confirm(
|
155
|
-
|
90
|
+
localise(
|
91
|
+
"Would you like to download the generated artefacts.yaml file? This will overwrite any existing config file in the current directory."
|
92
|
+
)
|
156
93
|
):
|
157
|
-
api_conf = APIConf(project_name)
|
94
|
+
api_conf = APIConf(project_name, __version__)
|
158
95
|
config_file_name = "artefacts.yaml"
|
159
96
|
config_file_url = f"{api_url}/{project_name}/{config_file_name}"
|
160
97
|
r = requests.get(config_file_url, headers=api_conf.headers)
|
@@ -164,7 +101,7 @@ def add(project_name):
|
|
164
101
|
|
165
102
|
|
166
103
|
@config.command()
|
167
|
-
@click.argument("project_name")
|
104
|
+
@click.argument("project_name", metavar=localise("PROJECT_NAME"))
|
168
105
|
def delete(project_name):
|
169
106
|
"""
|
170
107
|
Delete configuration for PROJECT_NAME
|
@@ -173,14 +110,14 @@ def delete(project_name):
|
|
173
110
|
config.remove_section(project_name)
|
174
111
|
with open(CONFIG_PATH, "w") as f:
|
175
112
|
config.write(f)
|
176
|
-
click.echo(
|
113
|
+
click.echo(localise("{project} config removed").format(project=project_name))
|
177
114
|
|
178
115
|
|
179
116
|
@click.command()
|
180
|
-
@click.argument("project_name")
|
117
|
+
@click.argument("project_name", metavar=localise("PROJECT_NAME"))
|
181
118
|
def hello(project_name):
|
182
119
|
"""Show message to confirm credentials allow access to PROJECT_NAME"""
|
183
|
-
api_conf = APIConf(project_name)
|
120
|
+
api_conf = APIConf(project_name, __version__)
|
184
121
|
response = requests.get(
|
185
122
|
f"{api_conf.api_url}/{project_name}/info",
|
186
123
|
headers=api_conf.headers,
|
@@ -192,81 +129,106 @@ def hello(project_name):
|
|
192
129
|
)
|
193
130
|
else:
|
194
131
|
result = response.json()
|
195
|
-
raise click.ClickException(
|
132
|
+
raise click.ClickException(
|
133
|
+
localise("Error getting project info: {message}").format(
|
134
|
+
message=result["message"]
|
135
|
+
)
|
136
|
+
)
|
196
137
|
|
197
138
|
|
198
|
-
@click.command(
|
139
|
+
@click.command(
|
140
|
+
context_settings=dict(
|
141
|
+
ignore_unknown_options=True,
|
142
|
+
)
|
143
|
+
)
|
144
|
+
@click.argument("jobname", metavar=localise("JOBNAME"))
|
199
145
|
@click.option(
|
200
146
|
"--config",
|
201
147
|
callback=config_validation,
|
202
148
|
default="artefacts.yaml",
|
203
|
-
help="Artefacts
|
149
|
+
help=localise("Artefacts configuration file."),
|
204
150
|
)
|
205
151
|
@click.option(
|
206
152
|
"--dryrun",
|
207
153
|
is_flag=True,
|
208
154
|
default=False,
|
209
|
-
help="
|
155
|
+
help=localise("Run with no tracking nor test execution"),
|
210
156
|
)
|
211
157
|
@click.option(
|
212
158
|
"--nosim",
|
213
159
|
is_flag=True,
|
214
160
|
default=False,
|
215
|
-
help="
|
161
|
+
help=localise("Skip configuring a simulator resource provided by Artefacts"),
|
216
162
|
)
|
217
163
|
@click.option(
|
218
164
|
"--noupload",
|
219
165
|
is_flag=True,
|
220
166
|
default=False,
|
221
|
-
help=
|
167
|
+
help=localise(
|
168
|
+
"Do not upload to Artefacts files generated during a run (e.g. rosbags)"
|
169
|
+
),
|
222
170
|
)
|
223
171
|
@click.option(
|
224
172
|
"--noisolation",
|
225
173
|
is_flag=True,
|
226
174
|
default=False,
|
227
|
-
help=
|
175
|
+
help=localise(
|
176
|
+
"Break the 'middleware network' isolation between the test suite and the host (in ROS1: --reuse-master flag; in ROS2: --disable-isolation flag). Primarily for debugging"
|
177
|
+
),
|
228
178
|
)
|
229
179
|
@click.option(
|
230
180
|
"--description",
|
231
181
|
default=None,
|
232
|
-
help="Optional description for this run",
|
182
|
+
help=localise("Optional description for this run"),
|
233
183
|
)
|
234
184
|
@click.option(
|
235
185
|
"--skip-validation",
|
236
186
|
is_flag=True,
|
237
187
|
default=False,
|
238
188
|
is_eager=True, # Necessary for callbacks to see it.
|
239
|
-
help=
|
189
|
+
help=localise(
|
190
|
+
"Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN."
|
191
|
+
),
|
240
192
|
)
|
241
193
|
@click.option(
|
242
194
|
"--in-container",
|
243
195
|
is_flag=True,
|
244
196
|
default=False,
|
245
|
-
help=
|
197
|
+
help=localise(
|
198
|
+
'[Experimental] Run the job inside a package container. The container image is build if it does not exist yet, with default name as "artefacts" (please use --with-image to override the image name). This option overrides (for now) --dryrun, --nosim, --noisolation and --description.'
|
199
|
+
),
|
246
200
|
)
|
247
201
|
@click.option(
|
248
202
|
"--dockerfile",
|
249
203
|
default="Dockerfile",
|
250
|
-
help=
|
204
|
+
help=localise(
|
205
|
+
"[Experimental] Path to a custom Dockerfile. Defaults to Dockerfile in the run directory. This flag is only used together with `--in-container`"
|
206
|
+
),
|
251
207
|
)
|
252
208
|
@click.option(
|
253
209
|
"--with-image",
|
254
210
|
default=None,
|
255
|
-
help=
|
211
|
+
help=localise(
|
212
|
+
"[Deprecated and unused from 0.8.0; Image names are now internally managed] Run the job using the image name passed here. Only used when running with --in-container set."
|
213
|
+
),
|
256
214
|
)
|
257
215
|
@click.option(
|
258
216
|
"--no-rebuild",
|
259
217
|
is_flag=True,
|
260
218
|
default=False,
|
261
|
-
help=
|
219
|
+
help=localise(
|
220
|
+
"[Experimental] Override the default behaviour to always rebuild the container image (as we assume incremental testing)."
|
221
|
+
),
|
262
222
|
)
|
263
223
|
@click.option(
|
264
224
|
"--with-gui",
|
265
225
|
is_flag=True,
|
266
226
|
default=False,
|
267
|
-
help=
|
227
|
+
help=localise(
|
228
|
+
"Show any GUI if any is created by the test runs. By default, UI elements are run but hidden---only test logs are returned. Please note GUI often assume X11 (e.g. ROS), typically with Qt, so this may not work without a appropriate environment."
|
229
|
+
),
|
268
230
|
)
|
269
|
-
@click.argument("
|
231
|
+
@click.argument("container-engine-args", nargs=-1, type=click.UNPROCESSED)
|
270
232
|
@click.pass_context
|
271
233
|
def run(
|
272
234
|
ctx: click.Context,
|
@@ -283,6 +245,7 @@ def run(
|
|
283
245
|
with_image: str = "artefacts",
|
284
246
|
no_rebuild: bool = False,
|
285
247
|
with_gui: bool = False,
|
248
|
+
container_engine_args: Optional[tuple] = None,
|
286
249
|
):
|
287
250
|
"""
|
288
251
|
Run JOBNAME locally
|
@@ -296,6 +259,14 @@ def run(
|
|
296
259
|
* Images are rebuilt at each run (relatively fast when no change).
|
297
260
|
* `dockerfile` allows to specify an alternative Dockerfile.
|
298
261
|
"""
|
262
|
+
# Workaround for job names coming after engine arguments
|
263
|
+
# Idea: Job names do not start with hyphens.
|
264
|
+
if jobname.startswith("-") and container_engine_args is not None:
|
265
|
+
_fix = list(container_engine_args)
|
266
|
+
_fix.insert(0, jobname)
|
267
|
+
jobname = _fix.pop()
|
268
|
+
container_engine_args = tuple(_fix)
|
269
|
+
|
299
270
|
warpconfig = read_config(config)
|
300
271
|
project_id = warpconfig["project"]
|
301
272
|
|
@@ -303,7 +274,7 @@ def run(
|
|
303
274
|
click.echo("#" * 80)
|
304
275
|
click.echo(f"# Job {jobname}".ljust(79, " ") + "#")
|
305
276
|
click.echo("#" * 80)
|
306
|
-
click.echo(f"[{jobname}] Checking container image")
|
277
|
+
click.echo(f"[{jobname}] " + localise("Checking container image"))
|
307
278
|
if not no_rebuild:
|
308
279
|
ctx.invoke(
|
309
280
|
containers.build,
|
@@ -311,24 +282,32 @@ def run(
|
|
311
282
|
dockerfile=dockerfile,
|
312
283
|
only=[jobname],
|
313
284
|
)
|
314
|
-
click.echo(f"[{jobname}] Container image ready")
|
315
|
-
click.echo(f"[{jobname}] Run in container")
|
285
|
+
click.echo(f"[{jobname}] " + localise("Container image ready"))
|
286
|
+
click.echo(f"[{jobname}] " + localise("Run in container"))
|
316
287
|
return ctx.invoke(
|
317
288
|
containers.run,
|
318
289
|
jobname=jobname,
|
319
290
|
config=config,
|
320
291
|
with_gui=with_gui,
|
292
|
+
engine_args=container_engine_args,
|
321
293
|
)
|
322
294
|
|
323
|
-
api_conf = APIConf(project_id, jobname)
|
324
|
-
click.echo(f"[{jobname}] Starting tests")
|
295
|
+
api_conf = APIConf(project_id, __version__, jobname)
|
296
|
+
click.echo(f"[{jobname}] " + localise("Starting tests"))
|
325
297
|
if jobname not in warpconfig["jobs"]:
|
326
|
-
click.secho(
|
298
|
+
click.secho(
|
299
|
+
f"[{jobname}] " + localise("Error: Job name not defined"),
|
300
|
+
err=True,
|
301
|
+
bold=True,
|
302
|
+
)
|
327
303
|
raise click.Abort()
|
328
304
|
jobconf = warpconfig["jobs"][jobname]
|
329
305
|
job_type = jobconf.get("type", "test")
|
330
306
|
if job_type not in ["test"]:
|
331
|
-
click.echo(
|
307
|
+
click.echo(
|
308
|
+
f"[{jobname}] "
|
309
|
+
+ localise("Job type not supported: {jt}").format(jt=job_type)
|
310
|
+
)
|
332
311
|
return
|
333
312
|
|
334
313
|
framework = jobconf["runtime"].get("framework", None)
|
@@ -337,19 +316,30 @@ def run(
|
|
337
316
|
if framework in DEPRECATED_FRAMEWORKS.keys():
|
338
317
|
migrated_framework = DEPRECATED_FRAMEWORKS[framework]
|
339
318
|
click.echo(
|
340
|
-
f"[{jobname}]
|
319
|
+
f"[{jobname}] "
|
320
|
+
+ localise(
|
321
|
+
"The selected framework '{framework}' is deprecated. Using '{alt}' instead."
|
322
|
+
).format(framework=framework, alt=migrated_framework)
|
341
323
|
)
|
342
324
|
framework = migrated_framework
|
343
325
|
|
344
326
|
if framework not in SUPPORTED_FRAMEWORKS:
|
345
327
|
click.echo(
|
346
|
-
f"[{jobname}]
|
328
|
+
f"[{jobname}] "
|
329
|
+
+ localise(
|
330
|
+
"WARNING: framework: '{framework}' is not officially supported. Attempting run."
|
331
|
+
).format(framework=framework)
|
347
332
|
)
|
348
333
|
|
349
334
|
batch_index = os.environ.get("AWS_BATCH_JOB_ARRAY_INDEX", None)
|
350
335
|
if batch_index is not None:
|
351
336
|
batch_index = int(batch_index)
|
352
|
-
click.echo(
|
337
|
+
click.echo(
|
338
|
+
f"[{jobname}] "
|
339
|
+
+ localise("AWS BATCH ARRAY DETECTED, batch_index={index}").format(
|
340
|
+
index=batch_index
|
341
|
+
)
|
342
|
+
)
|
353
343
|
scenarios, first = generate_scenarios(jobconf, batch_index)
|
354
344
|
context = None
|
355
345
|
execution_context = getpass.getuser() + "@" + platform.node()
|
@@ -373,7 +363,10 @@ def run(
|
|
373
363
|
)
|
374
364
|
except AuthenticationError:
|
375
365
|
click.secho(
|
376
|
-
f"[{jobname}]
|
366
|
+
f"[{jobname}] "
|
367
|
+
+ localise(
|
368
|
+
"Unable to authenticate (Stage: Job initialisation), please check your project name and API key"
|
369
|
+
),
|
377
370
|
err=True,
|
378
371
|
bold=True,
|
379
372
|
)
|
@@ -382,13 +375,19 @@ def run(
|
|
382
375
|
job_success = True
|
383
376
|
for scenario_n, scenario in enumerate(scenarios):
|
384
377
|
click.echo(
|
385
|
-
f"[{jobname}]
|
378
|
+
f"[{jobname}] "
|
379
|
+
+ localise("Starting scenario {sid}/{num}: {name}").format(
|
380
|
+
sid=scenario_n + 1, num=len(scenarios), name=scenario["name"]
|
381
|
+
)
|
386
382
|
)
|
387
383
|
try:
|
388
384
|
run = warpjob.new_run(scenario)
|
389
385
|
except AuthenticationError:
|
390
386
|
click.secho(
|
391
|
-
f"[{jobname}]
|
387
|
+
f"[{jobname}] "
|
388
|
+
+ localise(
|
389
|
+
"Unable to authenticate (Stage: Job run), please check your project name and API key"
|
390
|
+
),
|
392
391
|
err=True,
|
393
392
|
bold=True,
|
394
393
|
)
|
@@ -399,19 +398,22 @@ def run(
|
|
399
398
|
|
400
399
|
if "ros_testfile" not in run.params:
|
401
400
|
click.secho(
|
402
|
-
f"[{jobname}]
|
401
|
+
f"[{jobname}] "
|
402
|
+
+ localise("Test launch file not specified for ros2 project"),
|
403
403
|
err=True,
|
404
404
|
bold=True,
|
405
405
|
)
|
406
406
|
result = get_TestSuite_error_result(
|
407
407
|
scenario["name"],
|
408
|
-
"launch_test file not specified error",
|
409
|
-
|
408
|
+
localise("launch_test file not specified error"),
|
409
|
+
localise(
|
410
|
+
"Please specify a `ros_testfile` in the artefacts.yaml scenario configuration."
|
411
|
+
),
|
410
412
|
)
|
411
413
|
run.log_tests_results([result], False)
|
412
414
|
run.stop()
|
413
415
|
if dryrun:
|
414
|
-
click.echo(f"[{jobname}] Performing dry run")
|
416
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
415
417
|
results, success = {}, True
|
416
418
|
else:
|
417
419
|
try:
|
@@ -421,7 +423,8 @@ def run(
|
|
421
423
|
warpjob.log_tests_result(False)
|
422
424
|
click.secho(e, bold=True, err=True)
|
423
425
|
click.secho(
|
424
|
-
f"[{jobname}]
|
426
|
+
f"[{jobname}] "
|
427
|
+
+ localise("Artefacts failed to execute the tests"),
|
425
428
|
err=True,
|
426
429
|
bold=True,
|
427
430
|
)
|
@@ -431,7 +434,10 @@ def run(
|
|
431
434
|
warpjob.stop()
|
432
435
|
warpjob.log_tests_result(job_success)
|
433
436
|
click.secho(
|
434
|
-
f"[{jobname}]
|
437
|
+
f"[{jobname}] "
|
438
|
+
+ localise(
|
439
|
+
"Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct."
|
440
|
+
),
|
435
441
|
err=True,
|
436
442
|
bold=True,
|
437
443
|
)
|
@@ -443,13 +449,14 @@ def run(
|
|
443
449
|
|
444
450
|
if "ros_testfile" not in run.params:
|
445
451
|
click.secho(
|
446
|
-
f"[{jobname}]
|
452
|
+
f"[{jobname}] "
|
453
|
+
+ localise("Test launch file not specified for ros1 project"),
|
447
454
|
err=True,
|
448
455
|
bold=True,
|
449
456
|
)
|
450
457
|
raise click.Abort()
|
451
458
|
if dryrun:
|
452
|
-
click.echo(f"[{jobname}] Performing dry run")
|
459
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
453
460
|
results, success = {}, True
|
454
461
|
else:
|
455
462
|
results, success = run_ros1_tests(run)
|
@@ -460,13 +467,14 @@ def run(
|
|
460
467
|
|
461
468
|
if "run" not in run.params:
|
462
469
|
click.secho(
|
463
|
-
f"[{jobname}]
|
470
|
+
f"[{jobname}] "
|
471
|
+
+ localise("run command not specified for scenario"),
|
464
472
|
err=True,
|
465
473
|
bold=True,
|
466
474
|
)
|
467
475
|
raise click.Abort()
|
468
476
|
if dryrun:
|
469
|
-
click.echo(f"[{jobname}] Performing dry run")
|
477
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
470
478
|
results, success = {}, True
|
471
479
|
else:
|
472
480
|
results, success = run_other_tests(run)
|
@@ -480,7 +488,7 @@ def run(
|
|
480
488
|
|
481
489
|
run.stop()
|
482
490
|
warpjob.log_tests_result(job_success)
|
483
|
-
click.echo(f"[{jobname}] Done")
|
491
|
+
click.echo(f"[{jobname}] " + localise("Done"))
|
484
492
|
time.sleep(random.random() * 1)
|
485
493
|
|
486
494
|
warpjob.stop()
|
@@ -491,21 +499,23 @@ def run(
|
|
491
499
|
"--config",
|
492
500
|
callback=config_validation,
|
493
501
|
default="artefacts.yaml",
|
494
|
-
help="Artefacts
|
502
|
+
help=localise("Artefacts configuration file."),
|
495
503
|
)
|
496
504
|
@click.option(
|
497
505
|
"--description",
|
498
506
|
default=None,
|
499
|
-
help="Optional description for this run",
|
507
|
+
help=localise("Optional description for this run"),
|
500
508
|
)
|
501
509
|
@click.option(
|
502
510
|
"--skip-validation",
|
503
511
|
is_flag=True,
|
504
512
|
default=False,
|
505
513
|
is_eager=True, # Necessary for callbacks to see it.
|
506
|
-
help=
|
514
|
+
help=localise(
|
515
|
+
"Skip configuration validation, so that unsupported settings can be tried out, e.g. non-ROS settings or simulators like SAPIEN."
|
516
|
+
),
|
507
517
|
)
|
508
|
-
@click.argument("jobname")
|
518
|
+
@click.argument("jobname", metavar=localise("JOBNAME"))
|
509
519
|
def run_remote(config, description, jobname, skip_validation=False):
|
510
520
|
"""
|
511
521
|
Run JOBNAME in the cloud by packaging local sources.
|
@@ -516,9 +526,11 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
516
526
|
try:
|
517
527
|
warpconfig = read_config(config)
|
518
528
|
except FileNotFoundError:
|
519
|
-
raise click.ClickException(
|
529
|
+
raise click.ClickException(
|
530
|
+
localise("Project config file not found: {config}").format(config=config)
|
531
|
+
)
|
520
532
|
project_id = warpconfig["project"]
|
521
|
-
api_conf = APIConf(project_id)
|
533
|
+
api_conf = APIConf(project_id, __version__)
|
522
534
|
project_folder = os.path.dirname(os.path.abspath(config))
|
523
535
|
dashboard_url = urlparse(api_conf.api_url)
|
524
536
|
dashboard_url = f"{dashboard_url.scheme}://{dashboard_url.netloc}/{project_id}"
|
@@ -527,7 +539,9 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
527
539
|
warpconfig["jobs"][jobname]
|
528
540
|
except KeyError:
|
529
541
|
raise click.ClickException(
|
530
|
-
|
542
|
+
localise("Can't find a job named '{jobname}' in config '{config}'").format(
|
543
|
+
jobname=jobname, config=config
|
544
|
+
)
|
531
545
|
)
|
532
546
|
|
533
547
|
# Mutate job and then keep only the selected job in the config
|
@@ -545,7 +559,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
545
559
|
if "on" in run_config:
|
546
560
|
del run_config["on"]
|
547
561
|
|
548
|
-
click.echo("Packaging source...")
|
562
|
+
click.echo(localise("Packaging source..."))
|
549
563
|
|
550
564
|
with tempfile.NamedTemporaryFile(
|
551
565
|
prefix=project_id.split("/")[-1], suffix=".tgz", delete=True
|
@@ -590,7 +604,13 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
590
604
|
result = upload_urls_response.json()
|
591
605
|
except requests.exceptions.JSONDecodeError:
|
592
606
|
raise click.ClickException(
|
593
|
-
|
607
|
+
localise(
|
608
|
+
"Apologies, problem in interacting with the Artefacts backend: {status_code} {reason}. Response text: {detail}."
|
609
|
+
).format(
|
610
|
+
status_code=upload_urls_response.status_code,
|
611
|
+
reason=upload_urls_response.reason,
|
612
|
+
detail=upload_urls_response.text,
|
613
|
+
)
|
594
614
|
)
|
595
615
|
|
596
616
|
if (
|
@@ -598,25 +618,39 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
598
618
|
and result["message"] == "Not allowed"
|
599
619
|
):
|
600
620
|
raise click.ClickException(
|
601
|
-
|
621
|
+
localise(
|
622
|
+
"Missing access! Please make sure your API key is added at {url}/settings"
|
623
|
+
).format(url=dashboard_url)
|
602
624
|
)
|
603
625
|
|
604
626
|
if upload_urls_response.status_code == 402:
|
605
627
|
raise click.ClickException(
|
606
|
-
|
628
|
+
localise(
|
629
|
+
"Billing issue, please go to {url}/settings to correct: {error}"
|
630
|
+
).format(url=dashboard_url, error=result["error"])
|
607
631
|
)
|
608
632
|
|
609
633
|
if "message" in result:
|
610
634
|
raise click.ClickException(
|
611
|
-
|
635
|
+
localise("Error getting project info: {message}").format(
|
636
|
+
message=result["message"]
|
637
|
+
)
|
612
638
|
)
|
613
639
|
elif "error" in result:
|
614
640
|
raise click.ClickException(
|
615
|
-
|
641
|
+
localise("Error getting project info: {message}").format(
|
642
|
+
message=result["error"]
|
643
|
+
)
|
616
644
|
)
|
617
645
|
else:
|
618
646
|
raise click.ClickException(
|
619
|
-
|
647
|
+
localise(
|
648
|
+
"Error getting project info: {status_code} {reason}. Response text: {detail}."
|
649
|
+
).format(
|
650
|
+
status_code=upload_urls_response.status_code,
|
651
|
+
reason=upload_urls_response.reason,
|
652
|
+
detail=upload_urls_response.text,
|
653
|
+
)
|
620
654
|
)
|
621
655
|
|
622
656
|
upload_urls = upload_urls_response.json()["upload_urls"]
|
@@ -627,7 +661,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
627
661
|
description = os.environ.get("GITHUB_WORKFLOW")
|
628
662
|
url = f"{os.environ.get('GITHUB_SERVER_URL')}/{os.environ.get('GITHUB_REPOSITORY')}/actions/runs/{os.environ.get('GITHUB_RUN_ID')}"
|
629
663
|
else:
|
630
|
-
description = "Testing local source"
|
664
|
+
description = localise("Testing local source")
|
631
665
|
# Mock the necessary parts of the GitHub event
|
632
666
|
execution_context = getpass.getuser() + "@" + platform.node()
|
633
667
|
integration_payload = {
|
@@ -675,7 +709,9 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
675
709
|
)
|
676
710
|
|
677
711
|
click.echo(
|
678
|
-
|
712
|
+
localise(
|
713
|
+
"Uploading complete! The new job will show up shortly at {url}"
|
714
|
+
).format(url=dashboard_url)
|
679
715
|
)
|
680
716
|
|
681
717
|
|
@@ -683,14 +719,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
683
719
|
@click.version_option(version=__version__)
|
684
720
|
def artefacts():
|
685
721
|
"""A command line tool to interface with ARTEFACTS"""
|
686
|
-
|
687
|
-
if compute_env != "":
|
688
|
-
click.echo(f"running version {__version__}")
|
689
|
-
if (
|
690
|
-
"development" in compute_env
|
691
|
-
and os.getenv("ARTEFACTS_API_URL", None) is None
|
692
|
-
):
|
693
|
-
os.environ["ARTEFACTS_API_URL"] = "https://ui.artefacts.com/api"
|
722
|
+
pass
|
694
723
|
|
695
724
|
|
696
725
|
artefacts.add_command(config)
|