artefacts-cli 0.7.3__py3-none-any.whl → 0.9.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.
- artefacts/cli/__init__.py +66 -18
- artefacts/cli/app.py +209 -166
- artefacts/cli/app_containers.py +116 -24
- 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 +30 -0
- artefacts/cli/locales/art.pot +524 -0
- artefacts/cli/locales/base.pot +936 -0
- artefacts/cli/locales/click.pot +438 -0
- artefacts/cli/locales/ja_JP/LC_MESSAGES/artefacts.mo +0 -0
- artefacts/cli/logger.py +21 -5
- artefacts/cli/other.py +4 -0
- artefacts/cli/ros1.py +21 -6
- artefacts/cli/ros2.py +31 -15
- artefacts/cli/utils.py +8 -4
- artefacts/cli/utils_ros.py +8 -4
- artefacts/cli/version.py +2 -2
- artefacts/copava/__init__.py +1 -0
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.9.1.dist-info}/METADATA +10 -3
- artefacts_cli-0.9.1.dist-info/RECORD +34 -0
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.9.1.dist-info}/WHEEL +1 -1
- artefacts/cli/containers/docker.py +0 -119
- artefacts_cli-0.7.3.dist-info/RECORD +0 -24
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.9.1.dist-info}/entry_points.txt +0 -0
- {artefacts_cli-0.7.3.dist-info → artefacts_cli-0.9.1.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,76 +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
|
+
),
|
200
|
+
)
|
201
|
+
@click.option(
|
202
|
+
"--dockerfile",
|
203
|
+
default="Dockerfile",
|
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
|
+
),
|
246
207
|
)
|
247
208
|
@click.option(
|
248
209
|
"--with-image",
|
249
210
|
default=None,
|
250
|
-
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
|
+
),
|
251
214
|
)
|
252
215
|
@click.option(
|
253
216
|
"--no-rebuild",
|
254
217
|
is_flag=True,
|
255
218
|
default=False,
|
256
|
-
help=
|
219
|
+
help=localise(
|
220
|
+
"[Experimental] Override the default behaviour to always rebuild the container image (as we assume incremental testing)."
|
221
|
+
),
|
257
222
|
)
|
258
223
|
@click.option(
|
259
224
|
"--with-gui",
|
260
225
|
is_flag=True,
|
261
226
|
default=False,
|
262
|
-
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
|
+
),
|
263
230
|
)
|
264
|
-
@click.argument("
|
231
|
+
@click.argument("container-engine-args", nargs=-1, type=click.UNPROCESSED)
|
265
232
|
@click.pass_context
|
266
233
|
def run(
|
267
234
|
ctx: click.Context,
|
@@ -274,16 +241,32 @@ def run(
|
|
274
241
|
description="",
|
275
242
|
skip_validation=False,
|
276
243
|
in_container: bool = False,
|
244
|
+
dockerfile: str = "Dockerfile",
|
277
245
|
with_image: str = "artefacts",
|
278
246
|
no_rebuild: bool = False,
|
279
247
|
with_gui: bool = False,
|
248
|
+
container_engine_args: Optional[tuple] = None,
|
280
249
|
):
|
281
250
|
"""
|
282
251
|
Run JOBNAME locally
|
283
252
|
|
284
253
|
* Directly in the shell by default.
|
285
254
|
* Inside a packaged container when using the --in-container option.
|
255
|
+
|
256
|
+
In container mode:
|
257
|
+
* Images are built automatically if missing.
|
258
|
+
* Currently 1 image per job found in artefacts.yaml.
|
259
|
+
* Images are rebuilt at each run (relatively fast when no change).
|
260
|
+
* `dockerfile` allows to specify an alternative Dockerfile.
|
286
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
|
+
|
287
270
|
warpconfig = read_config(config)
|
288
271
|
project_id = warpconfig["project"]
|
289
272
|
|
@@ -291,30 +274,40 @@ def run(
|
|
291
274
|
click.echo("#" * 80)
|
292
275
|
click.echo(f"# Job {jobname}".ljust(79, " ") + "#")
|
293
276
|
click.echo("#" * 80)
|
294
|
-
click.echo(f"[{jobname}] Checking container image")
|
277
|
+
click.echo(f"[{jobname}] " + localise("Checking container image"))
|
295
278
|
if not no_rebuild:
|
296
279
|
ctx.invoke(
|
297
280
|
containers.build,
|
298
281
|
root=".",
|
282
|
+
dockerfile=dockerfile,
|
283
|
+
only=[jobname],
|
299
284
|
)
|
300
|
-
click.echo(f"[{jobname}] Container image ready")
|
301
|
-
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"))
|
302
287
|
return ctx.invoke(
|
303
288
|
containers.run,
|
304
289
|
jobname=jobname,
|
305
290
|
config=config,
|
306
291
|
with_gui=with_gui,
|
292
|
+
engine_args=container_engine_args,
|
307
293
|
)
|
308
294
|
|
309
|
-
api_conf = APIConf(project_id, jobname)
|
310
|
-
click.echo(f"[{jobname}] Starting tests")
|
295
|
+
api_conf = APIConf(project_id, __version__, jobname)
|
296
|
+
click.echo(f"[{jobname}] " + localise("Starting tests"))
|
311
297
|
if jobname not in warpconfig["jobs"]:
|
312
|
-
click.secho(
|
298
|
+
click.secho(
|
299
|
+
f"[{jobname}] " + localise("Error: Job name not defined"),
|
300
|
+
err=True,
|
301
|
+
bold=True,
|
302
|
+
)
|
313
303
|
raise click.Abort()
|
314
304
|
jobconf = warpconfig["jobs"][jobname]
|
315
305
|
job_type = jobconf.get("type", "test")
|
316
306
|
if job_type not in ["test"]:
|
317
|
-
click.echo(
|
307
|
+
click.echo(
|
308
|
+
f"[{jobname}] "
|
309
|
+
+ localise("Job type not supported: {jt}").format(jt=job_type)
|
310
|
+
)
|
318
311
|
return
|
319
312
|
|
320
313
|
framework = jobconf["runtime"].get("framework", None)
|
@@ -323,19 +316,30 @@ def run(
|
|
323
316
|
if framework in DEPRECATED_FRAMEWORKS.keys():
|
324
317
|
migrated_framework = DEPRECATED_FRAMEWORKS[framework]
|
325
318
|
click.echo(
|
326
|
-
f"[{jobname}]
|
319
|
+
f"[{jobname}] "
|
320
|
+
+ localise(
|
321
|
+
"The selected framework '{framework}' is deprecated. Using '{alt}' instead."
|
322
|
+
).format(framework=framework, alt=migrated_framework)
|
327
323
|
)
|
328
324
|
framework = migrated_framework
|
329
325
|
|
330
326
|
if framework not in SUPPORTED_FRAMEWORKS:
|
331
327
|
click.echo(
|
332
|
-
f"[{jobname}]
|
328
|
+
f"[{jobname}] "
|
329
|
+
+ localise(
|
330
|
+
"WARNING: framework: '{framework}' is not officially supported. Attempting run."
|
331
|
+
).format(framework=framework)
|
333
332
|
)
|
334
333
|
|
335
334
|
batch_index = os.environ.get("AWS_BATCH_JOB_ARRAY_INDEX", None)
|
336
335
|
if batch_index is not None:
|
337
336
|
batch_index = int(batch_index)
|
338
|
-
click.echo(
|
337
|
+
click.echo(
|
338
|
+
f"[{jobname}] "
|
339
|
+
+ localise("AWS BATCH ARRAY DETECTED, batch_index={index}").format(
|
340
|
+
index=batch_index
|
341
|
+
)
|
342
|
+
)
|
339
343
|
scenarios, first = generate_scenarios(jobconf, batch_index)
|
340
344
|
context = None
|
341
345
|
execution_context = getpass.getuser() + "@" + platform.node()
|
@@ -359,7 +363,10 @@ def run(
|
|
359
363
|
)
|
360
364
|
except AuthenticationError:
|
361
365
|
click.secho(
|
362
|
-
f"[{jobname}]
|
366
|
+
f"[{jobname}] "
|
367
|
+
+ localise(
|
368
|
+
"Unable to authenticate (Stage: Job initialisation), please check your project name and API key"
|
369
|
+
),
|
363
370
|
err=True,
|
364
371
|
bold=True,
|
365
372
|
)
|
@@ -368,13 +375,19 @@ def run(
|
|
368
375
|
job_success = True
|
369
376
|
for scenario_n, scenario in enumerate(scenarios):
|
370
377
|
click.echo(
|
371
|
-
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
|
+
)
|
372
382
|
)
|
373
383
|
try:
|
374
384
|
run = warpjob.new_run(scenario)
|
375
385
|
except AuthenticationError:
|
376
386
|
click.secho(
|
377
|
-
f"[{jobname}]
|
387
|
+
f"[{jobname}] "
|
388
|
+
+ localise(
|
389
|
+
"Unable to authenticate (Stage: Job run), please check your project name and API key"
|
390
|
+
),
|
378
391
|
err=True,
|
379
392
|
bold=True,
|
380
393
|
)
|
@@ -385,19 +398,22 @@ def run(
|
|
385
398
|
|
386
399
|
if "ros_testfile" not in run.params:
|
387
400
|
click.secho(
|
388
|
-
f"[{jobname}]
|
401
|
+
f"[{jobname}] "
|
402
|
+
+ localise("Test launch file not specified for ros2 project"),
|
389
403
|
err=True,
|
390
404
|
bold=True,
|
391
405
|
)
|
392
406
|
result = get_TestSuite_error_result(
|
393
407
|
scenario["name"],
|
394
|
-
"launch_test file not specified error",
|
395
|
-
|
408
|
+
localise("launch_test file not specified error"),
|
409
|
+
localise(
|
410
|
+
"Please specify a `ros_testfile` in the artefacts.yaml scenario configuration."
|
411
|
+
),
|
396
412
|
)
|
397
413
|
run.log_tests_results([result], False)
|
398
414
|
run.stop()
|
399
415
|
if dryrun:
|
400
|
-
click.echo(f"[{jobname}] Performing dry run")
|
416
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
401
417
|
results, success = {}, True
|
402
418
|
else:
|
403
419
|
try:
|
@@ -407,7 +423,8 @@ def run(
|
|
407
423
|
warpjob.log_tests_result(False)
|
408
424
|
click.secho(e, bold=True, err=True)
|
409
425
|
click.secho(
|
410
|
-
f"[{jobname}]
|
426
|
+
f"[{jobname}] "
|
427
|
+
+ localise("Artefacts failed to execute the tests"),
|
411
428
|
err=True,
|
412
429
|
bold=True,
|
413
430
|
)
|
@@ -417,7 +434,10 @@ def run(
|
|
417
434
|
warpjob.stop()
|
418
435
|
warpjob.log_tests_result(job_success)
|
419
436
|
click.secho(
|
420
|
-
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
|
+
),
|
421
441
|
err=True,
|
422
442
|
bold=True,
|
423
443
|
)
|
@@ -429,13 +449,14 @@ def run(
|
|
429
449
|
|
430
450
|
if "ros_testfile" not in run.params:
|
431
451
|
click.secho(
|
432
|
-
f"[{jobname}]
|
452
|
+
f"[{jobname}] "
|
453
|
+
+ localise("Test launch file not specified for ros1 project"),
|
433
454
|
err=True,
|
434
455
|
bold=True,
|
435
456
|
)
|
436
457
|
raise click.Abort()
|
437
458
|
if dryrun:
|
438
|
-
click.echo(f"[{jobname}] Performing dry run")
|
459
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
439
460
|
results, success = {}, True
|
440
461
|
else:
|
441
462
|
results, success = run_ros1_tests(run)
|
@@ -446,13 +467,14 @@ def run(
|
|
446
467
|
|
447
468
|
if "run" not in run.params:
|
448
469
|
click.secho(
|
449
|
-
f"[{jobname}]
|
470
|
+
f"[{jobname}] "
|
471
|
+
+ localise("run command not specified for scenario"),
|
450
472
|
err=True,
|
451
473
|
bold=True,
|
452
474
|
)
|
453
475
|
raise click.Abort()
|
454
476
|
if dryrun:
|
455
|
-
click.echo(f"[{jobname}] Performing dry run")
|
477
|
+
click.echo(f"[{jobname}] " + localise("Performing dry run"))
|
456
478
|
results, success = {}, True
|
457
479
|
else:
|
458
480
|
results, success = run_other_tests(run)
|
@@ -466,7 +488,7 @@ def run(
|
|
466
488
|
|
467
489
|
run.stop()
|
468
490
|
warpjob.log_tests_result(job_success)
|
469
|
-
click.echo(f"[{jobname}] Done")
|
491
|
+
click.echo(f"[{jobname}] " + localise("Done"))
|
470
492
|
time.sleep(random.random() * 1)
|
471
493
|
|
472
494
|
warpjob.stop()
|
@@ -477,21 +499,23 @@ def run(
|
|
477
499
|
"--config",
|
478
500
|
callback=config_validation,
|
479
501
|
default="artefacts.yaml",
|
480
|
-
help="Artefacts
|
502
|
+
help=localise("Artefacts configuration file."),
|
481
503
|
)
|
482
504
|
@click.option(
|
483
505
|
"--description",
|
484
506
|
default=None,
|
485
|
-
help="Optional description for this run",
|
507
|
+
help=localise("Optional description for this run"),
|
486
508
|
)
|
487
509
|
@click.option(
|
488
510
|
"--skip-validation",
|
489
511
|
is_flag=True,
|
490
512
|
default=False,
|
491
513
|
is_eager=True, # Necessary for callbacks to see it.
|
492
|
-
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
|
+
),
|
493
517
|
)
|
494
|
-
@click.argument("jobname")
|
518
|
+
@click.argument("jobname", metavar=localise("JOBNAME"))
|
495
519
|
def run_remote(config, description, jobname, skip_validation=False):
|
496
520
|
"""
|
497
521
|
Run JOBNAME in the cloud by packaging local sources.
|
@@ -502,9 +526,11 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
502
526
|
try:
|
503
527
|
warpconfig = read_config(config)
|
504
528
|
except FileNotFoundError:
|
505
|
-
raise click.ClickException(
|
529
|
+
raise click.ClickException(
|
530
|
+
localise("Project config file not found: {config}").format(config=config)
|
531
|
+
)
|
506
532
|
project_id = warpconfig["project"]
|
507
|
-
api_conf = APIConf(project_id)
|
533
|
+
api_conf = APIConf(project_id, __version__)
|
508
534
|
project_folder = os.path.dirname(os.path.abspath(config))
|
509
535
|
dashboard_url = urlparse(api_conf.api_url)
|
510
536
|
dashboard_url = f"{dashboard_url.scheme}://{dashboard_url.netloc}/{project_id}"
|
@@ -513,7 +539,9 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
513
539
|
warpconfig["jobs"][jobname]
|
514
540
|
except KeyError:
|
515
541
|
raise click.ClickException(
|
516
|
-
|
542
|
+
localise("Can't find a job named '{jobname}' in config '{config}'").format(
|
543
|
+
jobname=jobname, config=config
|
544
|
+
)
|
517
545
|
)
|
518
546
|
|
519
547
|
# Mutate job and then keep only the selected job in the config
|
@@ -531,7 +559,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
531
559
|
if "on" in run_config:
|
532
560
|
del run_config["on"]
|
533
561
|
|
534
|
-
click.echo("Packaging source...")
|
562
|
+
click.echo(localise("Packaging source..."))
|
535
563
|
|
536
564
|
with tempfile.NamedTemporaryFile(
|
537
565
|
prefix=project_id.split("/")[-1], suffix=".tgz", delete=True
|
@@ -576,7 +604,13 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
576
604
|
result = upload_urls_response.json()
|
577
605
|
except requests.exceptions.JSONDecodeError:
|
578
606
|
raise click.ClickException(
|
579
|
-
|
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
|
+
)
|
580
614
|
)
|
581
615
|
|
582
616
|
if (
|
@@ -584,25 +618,39 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
584
618
|
and result["message"] == "Not allowed"
|
585
619
|
):
|
586
620
|
raise click.ClickException(
|
587
|
-
|
621
|
+
localise(
|
622
|
+
"Missing access! Please make sure your API key is added at {url}/settings"
|
623
|
+
).format(url=dashboard_url)
|
588
624
|
)
|
589
625
|
|
590
626
|
if upload_urls_response.status_code == 402:
|
591
627
|
raise click.ClickException(
|
592
|
-
|
628
|
+
localise(
|
629
|
+
"Billing issue, please go to {url}/settings to correct: {error}"
|
630
|
+
).format(url=dashboard_url, error=result["error"])
|
593
631
|
)
|
594
632
|
|
595
633
|
if "message" in result:
|
596
634
|
raise click.ClickException(
|
597
|
-
|
635
|
+
localise("Error getting project info: {message}").format(
|
636
|
+
message=result["message"]
|
637
|
+
)
|
598
638
|
)
|
599
639
|
elif "error" in result:
|
600
640
|
raise click.ClickException(
|
601
|
-
|
641
|
+
localise("Error getting project info: {message}").format(
|
642
|
+
message=result["error"]
|
643
|
+
)
|
602
644
|
)
|
603
645
|
else:
|
604
646
|
raise click.ClickException(
|
605
|
-
|
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
|
+
)
|
606
654
|
)
|
607
655
|
|
608
656
|
upload_urls = upload_urls_response.json()["upload_urls"]
|
@@ -613,7 +661,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
613
661
|
description = os.environ.get("GITHUB_WORKFLOW")
|
614
662
|
url = f"{os.environ.get('GITHUB_SERVER_URL')}/{os.environ.get('GITHUB_REPOSITORY')}/actions/runs/{os.environ.get('GITHUB_RUN_ID')}"
|
615
663
|
else:
|
616
|
-
description = "Testing local source"
|
664
|
+
description = localise("Testing local source")
|
617
665
|
# Mock the necessary parts of the GitHub event
|
618
666
|
execution_context = getpass.getuser() + "@" + platform.node()
|
619
667
|
integration_payload = {
|
@@ -661,7 +709,9 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
661
709
|
)
|
662
710
|
|
663
711
|
click.echo(
|
664
|
-
|
712
|
+
localise(
|
713
|
+
"Uploading complete! The new job will show up shortly at {url}"
|
714
|
+
).format(url=dashboard_url)
|
665
715
|
)
|
666
716
|
|
667
717
|
|
@@ -669,14 +719,7 @@ def run_remote(config, description, jobname, skip_validation=False):
|
|
669
719
|
@click.version_option(version=__version__)
|
670
720
|
def artefacts():
|
671
721
|
"""A command line tool to interface with ARTEFACTS"""
|
672
|
-
|
673
|
-
if compute_env != "":
|
674
|
-
click.echo(f"running version {__version__}")
|
675
|
-
if (
|
676
|
-
"development" in compute_env
|
677
|
-
and os.getenv("ARTEFACTS_API_URL", None) is None
|
678
|
-
):
|
679
|
-
os.environ["ARTEFACTS_API_URL"] = "https://ui.artefacts.com/api"
|
722
|
+
pass
|
680
723
|
|
681
724
|
|
682
725
|
artefacts.add_command(config)
|