artefacts-cli 0.6.19__tar.gz → 0.7.1__tar.gz

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.
Files changed (71) hide show
  1. artefacts_cli-0.7.1/CHANGELOG.md +57 -0
  2. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/PKG-INFO +4 -2
  3. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/README.md +1 -1
  4. artefacts_cli-0.7.1/artefacts/__init__.py +4 -0
  5. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/app.py +59 -39
  6. artefacts_cli-0.7.1/artefacts/cli/app_containers.py +165 -0
  7. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/containers/docker.py +5 -1
  8. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/utils.py +17 -0
  9. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/version.py +9 -4
  10. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/PKG-INFO +4 -2
  11. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/SOURCES.txt +3 -0
  12. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/requires.txt +1 -0
  13. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/pyproject.toml +2 -0
  14. artefacts_cli-0.7.1/tests/cli/test_app_containers.py +36 -0
  15. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_cli.py +29 -25
  16. artefacts_cli-0.7.1/tests/cli/test_utils.py +24 -0
  17. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/conftest.py +16 -0
  18. artefacts_cli-0.6.19/artefacts/cli/app_containers.py +0 -99
  19. artefacts_cli-0.6.19/tests/cli/test_app_containers.py +0 -53
  20. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/README_INTERNAL.md +0 -0
  21. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/__init__.py +0 -0
  22. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/bagparser.py +0 -0
  23. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/constants.py +0 -0
  24. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/containers/__init__.py +0 -0
  25. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/containers/utils.py +0 -0
  26. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/errors.py +0 -0
  27. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/logger.py +0 -0
  28. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/other.py +0 -0
  29. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/parameters.py +0 -0
  30. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/ros1.py +0 -0
  31. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/ros2.py +0 -0
  32. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/cli/utils_ros.py +0 -0
  33. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts/wrappers/artefacts_ros1_meta.launch +0 -0
  34. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts.yaml +0 -0
  35. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/dependency_links.txt +0 -0
  36. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/entry_points.txt +0 -0
  37. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/artefacts_cli.egg-info/top_level.txt +0 -0
  38. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/bin/release +0 -0
  39. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/test_run_remote.yaml +0 -0
  40. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/CMakeLists.txt +0 -0
  41. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_meta.launch +0 -0
  42. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/test_turtle.launch +0 -0
  43. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/launch/turtle_odometry.launch +0 -0
  44. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/package.xml +0 -0
  45. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/setup.py +0 -0
  46. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/TestTurtle.py +0 -0
  47. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/__init__.py +0 -0
  48. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_odom.py +0 -0
  49. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_post_process.py +0 -0
  50. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/src/turtle_trajectory.py +0 -0
  51. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim1/ros_workspace/src/turtle_odometry/test/viz_turtle_odom.xml +0 -0
  52. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim2/launch_turtle.py +0 -0
  53. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/infra-tests/turtlesim2/sample_node.py +0 -0
  54. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/pytest.ini +0 -0
  55. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/setup.cfg +0 -0
  56. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/__init__.py +0 -0
  57. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/__init__.py +0 -0
  58. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/containers/__init__.py +0 -0
  59. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/containers/test_utils.py +0 -0
  60. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_config_validation.py +0 -0
  61. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_other.py +0 -0
  62. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_parameters.py +0 -0
  63. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_ros1.py +0 -0
  64. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_ros2.py +0 -0
  65. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/cli/test_warp.py +0 -0
  66. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/fixtures/artefacts_deprecated.yaml +0 -0
  67. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/fixtures/artefacts_ros1.yaml +0 -0
  68. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/fixtures/warp-env-param.yaml +0 -0
  69. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/fixtures/warp.yaml +0 -0
  70. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/test_config_validation.py +0 -0
  71. {artefacts_cli-0.6.19 → artefacts_cli-0.7.1}/tests/utils/docker_mock.py +0 -0
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - Run in containers with only an artefacts.yaml configuration file. No need to
13
+ write a Dockerfile in many standard situations.
14
+
15
+ ### Changed
16
+
17
+ - New logging messages and format.
18
+
19
+
20
+ ## [0.7.1] - 2025-03-14
21
+
22
+ ### Added
23
+
24
+ - Partial CHANGELOG with information on the day we start SemVer and the current
25
+ 0.7.0. More detail to come inbetween, but we will focus on the future.
26
+
27
+ ### Changed
28
+
29
+ - Replace Ruff shield for the original Black one.
30
+
31
+
32
+ ## [0.7.0] - 2025-02-25
33
+
34
+ ### Added
35
+
36
+ - Default upload directory to automatically include output from the Artefacts
37
+ toolkit.
38
+
39
+ ### Changed
40
+
41
+ - Always rebuild container images before run. These incremental rebuilds avoid
42
+ existing confusion when running an updated code base without rebuilding.
43
+ - Separate CD workflow from PyPi publication testing: For reusability and
44
+ direct invocation.
45
+
46
+
47
+ ## [0.5.8] - 2024-08-19
48
+
49
+ ### Added
50
+
51
+ - Beginning of semantic versioning.
52
+ - Local metrics errors do not block publication of results.
53
+ - Introduction of Black formatting.
54
+
55
+ [unreleased]: https://github.com/art-e-fact/artefacts-client/compare/0.7.0...HEAD
56
+ [0.7.0]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.7.0
57
+ [0.5.8]: https://github.com/art-e-fact/artefacts-client/releases/tag/0.5.8
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: artefacts_cli
3
- Version: 0.6.19
3
+ Version: 0.7.1
4
4
  Author-email: FD <fabian@artefacts.com>, AGC <alejandro@artefacts.com>, TN <tomo@artefacts.com>, EP <eric@artefacts.com>
5
5
  Project-URL: Homepage, https://github.com/art-e-fact/artefacts-client
6
6
  Project-URL: Bug Tracker, https://github.com/art-e-fact/artefacts-client/issues
7
+ Project-URL: Changelog, https://github.com/art-e-fact/artefacts-client/CHANGELOG.md
7
8
  Classifier: Programming Language :: Python :: 3
8
9
  Classifier: License :: OSI Approved :: Apache Software License
9
10
  Classifier: Operating System :: OS Independent
10
11
  Requires-Python: >=3.8
11
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: artefacts-c2d>=1.7.1
12
14
  Requires-Dist: artefacts-copava>=0.1.11
13
15
  Requires-Dist: click>=8.0.4
14
16
  Requires-Dist: gitignore_parser>=0.1.11
@@ -42,7 +44,7 @@ Requires-Dist: twine; extra == "dev"
42
44
  CLI to the Artefacts platform.
43
45
 
44
46
  [![Documentation](https://img.shields.io/badge/documentation-blue.svg?style=flat-square)](https://docs.artefacts.com/)
45
- [![Code style: Black-compatible with Ruff](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
47
+ [![Ruff Style](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
46
48
 
47
49
  ## Requirements
48
50
 
@@ -3,7 +3,7 @@
3
3
  CLI to the Artefacts platform.
4
4
 
5
5
  [![Documentation](https://img.shields.io/badge/documentation-blue.svg?style=flat-square)](https://docs.artefacts.com/)
6
- [![Code style: Black-compatible with Ruff](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
6
+ [![Ruff Style](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
7
 
8
8
  ## Requirements
9
9
 
@@ -0,0 +1,4 @@
1
+ from pathlib import Path
2
+
3
+
4
+ ARTEFACTS_DEFAULT_OUTPUT_DIR = Path("artefacts")
@@ -20,7 +20,7 @@ from gitignore_parser import parse_gitignore
20
20
  from artefacts.cli import init_job, generate_scenarios, AuthenticationError, __version__
21
21
  from artefacts.cli import app_containers as containers
22
22
  from artefacts.cli.constants import DEPRECATED_FRAMEWORKS, SUPPORTED_FRAMEWORKS
23
- from artefacts.cli.utils import read_config, config_validation
23
+ from artefacts.cli.utils import add_output_from_default, config_validation, read_config
24
24
 
25
25
  HOME = os.path.expanduser("~")
26
26
  CONFIG_DIR = f"{HOME}/.artefacts"
@@ -71,7 +71,7 @@ def get_artefacts_api_url(project_profile):
71
71
 
72
72
 
73
73
  class APIConf:
74
- def __init__(self, project_name):
74
+ def __init__(self, project_name: str, job_name: str = None) -> None:
75
75
  config = get_conf_from_file()
76
76
  if project_name in config:
77
77
  profile = config[project_name]
@@ -96,7 +96,10 @@ class APIConf:
96
96
  self.headers["User-Agent"] = (
97
97
  f"ArtefactsClient/{__version__} ({platform.platform()}/{platform.python_version()})"
98
98
  )
99
- click.echo(f"Connecting to {self.api_url} using {auth_type}")
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}")
100
103
 
101
104
 
102
105
  def validate_artefacts_config(config_file: str) -> dict:
@@ -243,14 +246,14 @@ def hello(project_name):
243
246
  )
244
247
  @click.option(
245
248
  "--with-image",
246
- default="artefacts",
247
- help="[Experimental] Run the job using the image name passed here. Only used when running with --in-container set.",
249
+ default=None,
250
+ help="[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.",
248
251
  )
249
252
  @click.option(
250
- "--rebuild-container",
253
+ "--no-rebuild",
251
254
  is_flag=True,
252
255
  default=False,
253
- help="[Experimental] Rebuild the container image before running. This flag guarantees that the run uses the latest code available when building the image, and usually takes more time before the run can start (time to compile and generate the image).",
256
+ help="[Experimental] Override the default behaviour to always rebuild the container image (as we assume incremental testing).",
254
257
  )
255
258
  @click.option(
256
259
  "--with-gui",
@@ -272,7 +275,7 @@ def run(
272
275
  skip_validation=False,
273
276
  in_container: bool = False,
274
277
  with_image: str = "artefacts",
275
- rebuild_container: bool = False,
278
+ no_rebuild: bool = False,
276
279
  with_gui: bool = False,
277
280
  ):
278
281
  """
@@ -281,30 +284,37 @@ def run(
281
284
  * Directly in the shell by default.
282
285
  * Inside a packaged container when using the --in-container option.
283
286
  """
287
+ warpconfig = read_config(config)
288
+ project_id = warpconfig["project"]
289
+
284
290
  if in_container:
285
- if rebuild_container or not ctx.invoke(containers.check, name=with_image):
291
+ click.echo("#" * 80)
292
+ click.echo(f"# Job {jobname}".ljust(79, " ") + "#")
293
+ click.echo("#" * 80)
294
+ click.echo(f"[{jobname}] Checking container image")
295
+ if not no_rebuild:
286
296
  ctx.invoke(
287
- containers.build, path=".", dockerfile="Dockerfile", name=with_image
297
+ containers.build,
298
+ root=".",
288
299
  )
300
+ click.echo(f"[{jobname}] Container image ready")
301
+ click.echo(f"[{jobname}] Run in container")
289
302
  return ctx.invoke(
290
303
  containers.run,
291
- image=with_image,
292
304
  jobname=jobname,
293
305
  config=config,
294
306
  with_gui=with_gui,
295
307
  )
296
308
 
297
- warpconfig = read_config(config)
298
-
299
- project_id = warpconfig["project"]
300
- api_conf = APIConf(project_id)
301
- click.echo(f"Starting tests for {project_id}")
309
+ api_conf = APIConf(project_id, jobname)
310
+ click.echo(f"[{jobname}] Starting tests")
302
311
  if jobname not in warpconfig["jobs"]:
303
- raise click.ClickException(f"Job {jobname} not defined")
312
+ click.echo(f"[{jobname}] Error: Job name not defined")
313
+ raise click.ClickException()
304
314
  jobconf = warpconfig["jobs"][jobname]
305
315
  job_type = jobconf.get("type", "test")
306
316
  if job_type not in ["test"]:
307
- click.echo(f"Job type not supported: f{job_type}")
317
+ click.echo(f"[{jobname}] Job type not supported: {job_type}")
308
318
  return
309
319
 
310
320
  framework = jobconf["runtime"].get("framework", None)
@@ -313,19 +323,19 @@ def run(
313
323
  if framework in DEPRECATED_FRAMEWORKS.keys():
314
324
  migrated_framework = DEPRECATED_FRAMEWORKS[framework]
315
325
  click.echo(
316
- f"The selected framework '{framework}' is deprecated. Using '{migrated_framework}' instead."
326
+ f"[{jobname}] The selected framework '{framework}' is deprecated. Using '{migrated_framework}' instead."
317
327
  )
318
328
  framework = migrated_framework
319
329
 
320
330
  if framework not in SUPPORTED_FRAMEWORKS:
321
331
  click.echo(
322
- f"WARNING: framework: '{framework}' is not officially supported. Attempting run."
332
+ f"[{jobname}] WARNING: framework: '{framework}' is not officially supported. Attempting run."
323
333
  )
324
334
 
325
335
  batch_index = os.environ.get("AWS_BATCH_JOB_ARRAY_INDEX", None)
326
336
  if batch_index is not None:
327
337
  batch_index = int(batch_index)
328
- click.echo(f"AWS BATCH ARRAY DETECTED, batch_index={batch_index}")
338
+ click.echo(f"[{jobname}] AWS BATCH ARRAY DETECTED, batch_index={batch_index}")
329
339
  scenarios, first = generate_scenarios(jobconf, batch_index)
330
340
  context = None
331
341
  execution_context = getpass.getuser() + "@" + platform.node()
@@ -348,30 +358,33 @@ def run(
348
358
  first,
349
359
  )
350
360
  except AuthenticationError:
351
- raise click.ClickException(
352
- "Unable to authenticate (Stage: Job initialisation), please check your project name and API key"
361
+ click.echo(
362
+ f"[{jobname}] Unable to authenticate (Stage: Job initialisation), please check your project name and API key"
353
363
  )
364
+ raise click.ClickException()
354
365
 
355
366
  job_success = True
356
367
  for scenario_n, scenario in enumerate(scenarios):
357
368
  click.echo(
358
- f"Starting scenario {scenario_n + 1}/{len(scenarios)}: {scenario['name']}"
369
+ f"[{jobname}] Starting scenario {scenario_n + 1}/{len(scenarios)}: {scenario['name']}"
359
370
  )
360
371
  try:
361
372
  run = warpjob.new_run(scenario)
362
373
  except AuthenticationError:
363
- raise click.ClickException(
364
- "Unable to authenticate (Stage: Job run), please check your project name and API key"
374
+ click.echo(
375
+ f"[{jobname}] Unable to authenticate (Stage: Job run), please check your project name and API key"
365
376
  )
377
+ raise click.ClickException()
366
378
  if framework is not None and framework.startswith("ros2:"):
367
379
  from artefacts.cli.ros2 import run_ros2_tests
368
380
 
369
381
  if "ros_testfile" not in run.params:
370
- raise click.ClickException(
371
- "Test launch file not specified for ros2 project"
382
+ click.echo(
383
+ f"[{jobname}] Test launch file not specified for ros2 project"
372
384
  )
385
+ raise click.ClickException()
373
386
  if dryrun:
374
- click.echo("performing dry run")
387
+ click.echo(f"[{jobname}] Performing dry run")
375
388
  results, success = {}, True
376
389
  else:
377
390
  try:
@@ -380,25 +393,28 @@ def run(
380
393
  warpjob.stop()
381
394
  warpjob.log_tests_result(False)
382
395
  click.secho(e, bold=True, err=True)
383
- raise click.ClickException("artefacts failed to execute the tests")
396
+ click.echo(f"[{jobname}] artefacts failed to execute the tests")
397
+ raise click.ClickException()
384
398
  if success is None:
385
399
  run.stop()
386
400
  warpjob.stop()
387
401
  warpjob.log_tests_result(job_success)
388
- raise click.ClickException(
389
- "Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct."
402
+ click.echo(
403
+ f"[{jobname}] Not able to execute tests. Make sure that ROS2 is sourced and that your launch file syntax is correct."
390
404
  )
405
+ raise click.ClickException()
391
406
  if not success:
392
407
  job_success = False
393
408
  elif framework is not None and framework.startswith("ros1:"):
394
409
  from artefacts.cli.ros1 import run_ros1_tests
395
410
 
396
411
  if "ros_testfile" not in run.params:
397
- raise click.ClickException(
398
- "Test launch file not specified for ros1 project"
412
+ click.echo(
413
+ f"[{jobname}] Test launch file not specified for ros1 project"
399
414
  )
415
+ raise click.ClickException()
400
416
  if dryrun:
401
- click.echo("performing dry run")
417
+ click.echo(f"[{jobname}] Performing dry run")
402
418
  results, success = {}, True
403
419
  else:
404
420
  results, success = run_ros1_tests(run)
@@ -408,9 +424,10 @@ def run(
408
424
  from artefacts.cli.other import run_other_tests
409
425
 
410
426
  if "run" not in run.params:
411
- raise click.ClickException("run command not specified for scenario")
427
+ click.echo(f"[{jobname}] run command not specified for scenario")
428
+ raise click.ClickException()
412
429
  if dryrun:
413
- click.echo("performing dry run")
430
+ click.echo(f"[{jobname}] Performing dry run")
414
431
  results, success = {}, True
415
432
  else:
416
433
  results, success = run_other_tests(run)
@@ -419,9 +436,12 @@ def run(
419
436
  if type(run.params.get("metrics", [])) is str:
420
437
  run.log_metrics()
421
438
 
439
+ # Add for upload any default output generated by the run
440
+ add_output_from_default(run)
441
+
422
442
  run.stop()
423
443
  warpjob.log_tests_result(job_success)
424
- click.echo("Done")
444
+ click.echo(f"[{jobname}] Done")
425
445
  time.sleep(random.random() * 1)
426
446
 
427
447
  warpjob.stop()
@@ -551,7 +571,7 @@ def run_remote(config, description, jobname, skip_validation=False):
551
571
  )
552
572
 
553
573
  raise click.ClickException(
554
- f"Error getting project info: {result['message']}"
574
+ f"Error getting project info: {upload_urls_response.status_code} {upload_urls_response.reason}. Response text: {upload_urls_response.text}."
555
575
  )
556
576
 
557
577
  upload_urls = upload_urls_response.json()["upload_urls"]
@@ -0,0 +1,165 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ import click
5
+
6
+ from c2d.core import Converter
7
+
8
+ from artefacts.cli.constants import DEFAULT_API_URL
9
+ from artefacts.cli.utils import config_validation, read_config
10
+ from artefacts.cli.containers.utils import ContainerMgr
11
+
12
+
13
+ @click.group()
14
+ @click.option("--debug/--no-debug", default=False)
15
+ @click.pass_context
16
+ def containers(ctx: click.Context, debug: bool):
17
+ ctx.ensure_object(dict)
18
+ ctx.obj["debug"] = debug
19
+
20
+
21
+ @containers.command()
22
+ @click.option(
23
+ "--path",
24
+ default=".",
25
+ help="[Deprecated since 0.8.0; please see --root] Path to the root of the project.",
26
+ )
27
+ @click.option(
28
+ "--root",
29
+ default=".",
30
+ help="Path to the root of the project.",
31
+ )
32
+ @click.option(
33
+ "--dockerfile",
34
+ default="Dockerfile",
35
+ help="Path to a custom Dockerfile. Defaults to Dockerfile under `path` (see option of the same name).",
36
+ )
37
+ @click.option(
38
+ "--name",
39
+ required=False,
40
+ help="[Deprecated since 0.8.0; not used and will disappear after 0.8.0] Name for the generated image",
41
+ )
42
+ @click.option(
43
+ "--config",
44
+ callback=config_validation,
45
+ default="artefacts.yaml",
46
+ help="Path to the Artefacts configuration file. It defaults to `./artefacts.yaml`",
47
+ )
48
+ @click.pass_context
49
+ def build(
50
+ ctx: click.Context,
51
+ path: str,
52
+ root: str,
53
+ dockerfile: str,
54
+ name: str,
55
+ config: str,
56
+ ):
57
+ try:
58
+ artefacts_config = read_config(config)
59
+ except FileNotFoundError:
60
+ raise click.ClickException(
61
+ f"Project config file not found: {config}. Please provide an Artefacts configuration file to proceed (running `artefacts init` allows to generate one)."
62
+ )
63
+ prefix = artefacts_config["project"].strip().lower()
64
+ dockerfiles = []
65
+ if os.path.exists(dockerfile):
66
+ for job_name in artefacts_config["jobs"]:
67
+ dockerfiles.append(
68
+ dict(
69
+ path=root,
70
+ dockerfile=dockerfile,
71
+ name=f"{prefix}/{job_name.strip().lower()}",
72
+ )
73
+ )
74
+ else:
75
+ # The split on `prefix` is to ensure there is no slash (project names are org/project) confusing the path across supported OS.
76
+ dest_root = (
77
+ Path.home()
78
+ / Path(".artefacts")
79
+ / Path("projects")
80
+ / Path(*(prefix.split("/")))
81
+ / Path("containers")
82
+ )
83
+ if not dest_root.exists():
84
+ click.echo(
85
+ f"No {dockerfile} found here. Let's generate one per scenario based on artefacts.yaml. They will be available under the `{dest_root}` folder and used from there."
86
+ )
87
+ # No condition on generating the Dockerfiles as:
88
+ # - Fast
89
+ # - We consider entirely managed, so any manual change should be ignored.
90
+ scenarios = Converter().process(config, as_text=False)
91
+ for idx, df in enumerate(scenarios.values()):
92
+ job_name = df.job_name.strip().lower()
93
+ dest = dest_root / Path(job_name)
94
+ dest.mkdir(parents=True, exist_ok=True)
95
+ _dockerfile = os.path.join(dest, "Dockerfile")
96
+ df.dump(_dockerfile)
97
+ click.echo(f"[{job_name}] Using generated Dockerfile at: {_dockerfile}")
98
+ dockerfiles.append(
99
+ dict(
100
+ path=root,
101
+ dockerfile=_dockerfile,
102
+ name=f"{prefix}/{job_name}",
103
+ )
104
+ )
105
+ handler = ContainerMgr()
106
+ if len(dockerfiles) > 0:
107
+ for specs in dockerfiles:
108
+ # No condition on building the images, as relatively fast when already exists, and straightforward logic.
109
+ image, _ = handler.build(**specs)
110
+ else:
111
+ click.echo("No Dockerfile, nothing to do.")
112
+
113
+
114
+ @containers.command()
115
+ @click.argument("name")
116
+ @click.pass_context
117
+ def check(ctx: click.Context, name: str):
118
+ if name is None:
119
+ name = "artefacts"
120
+ handler = ContainerMgr()
121
+ result = handler.check(name)
122
+ if ctx.parent is None:
123
+ # Print only if the command is called directly.
124
+ print(f"Package {name} exists and ready to use.")
125
+ return result
126
+
127
+
128
+ @containers.command()
129
+ @click.argument("jobname")
130
+ @click.option(
131
+ "--config",
132
+ callback=config_validation,
133
+ default="artefacts.yaml",
134
+ help="Path to the Artefacts configuration file. It defaults to `./artefacts.yaml`",
135
+ )
136
+ @click.option(
137
+ "--with-gui",
138
+ "with_gui",
139
+ default=False,
140
+ help="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 an X11 environment, typically with Qt, so this may not work without a appropriate environment.",
141
+ )
142
+ @click.pass_context
143
+ def run(ctx: click.Context, jobname: str, config: str, with_gui: bool):
144
+ try:
145
+ artefacts_config = read_config(config)
146
+ except FileNotFoundError:
147
+ raise click.ClickException(f"Project config file not found: {config}")
148
+ project = artefacts_config["project"]
149
+ handler = ContainerMgr()
150
+ params = dict(
151
+ image=f"{project.strip().lower()}/{jobname}",
152
+ project=project,
153
+ jobname=jobname,
154
+ with_gui=with_gui,
155
+ # Hidden settings primarily useful to Artefacts developers
156
+ api_url=os.environ.get("ARTEFACTS_API_URL", DEFAULT_API_URL),
157
+ api_key=os.environ.get("ARTEFACTS_KEY", None),
158
+ )
159
+ container, logs = handler.run(**params)
160
+ if container:
161
+ print(f"Package run complete: Container Id for inspection: {container['Id']}")
162
+ else:
163
+ print("Package run failed:")
164
+ for entry in logs:
165
+ print("\t- " + entry)
@@ -22,6 +22,10 @@ class DockerManager(CMgr):
22
22
 
23
23
  def build(self, **kwargs) -> Tuple[str, Generator]:
24
24
  kwargs["tag"] = kwargs.pop("name")
25
+ # Ensure `path` is a string, the Docker package does not support pathlib.
26
+ kwargs["path"] = str(kwargs.pop("path"))
27
+ # Remove intermediate containers
28
+ kwargs["rm"] = True
25
29
  logs = []
26
30
  img_id = None
27
31
  for entry in self.client.build(**kwargs):
@@ -32,7 +36,7 @@ class DockerManager(CMgr):
32
36
  if "stream" in data:
33
37
  line = data["stream"].strip()
34
38
  if not line.startswith("---") and len(line) > 0:
35
- print(line)
39
+ print(f"[{kwargs['tag'].split('/')[-1]}] {line}")
36
40
  logs.append(line)
37
41
  elif "aux" in data and "ID" in data["aux"]:
38
42
  img_id = data["aux"]["ID"]
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from pathlib import Path
2
3
  import subprocess
3
4
  import sys
4
5
  from typing import Union
@@ -6,6 +7,8 @@ from typing import Union
6
7
  import click
7
8
 
8
9
  import artefacts_copava as copava
10
+ from artefacts import ARTEFACTS_DEFAULT_OUTPUT_DIR
11
+ from artefacts.cli import WarpRun
9
12
 
10
13
 
11
14
  def run_and_save_logs(
@@ -97,3 +100,17 @@ def pretty_print_config_error(
97
100
  # Must not happen, so broad definition, but we want to know fast.
98
101
  raise Exception(f"Unacceptable data type for config error formatting: {errors}")
99
102
  return output
103
+
104
+
105
+ def add_output_from_default(run: WarpRun) -> None:
106
+ """
107
+ Add every file found under ARTEFACTS_DEFAULT_OUTPUT_DIR to the set of files
108
+ uploaded to Artefacts for the run argument.
109
+
110
+ The default folder is created either directly, or more generally by Artefacts
111
+ toolkit libraries.
112
+ """
113
+ if ARTEFACTS_DEFAULT_OUTPUT_DIR.exists() and ARTEFACTS_DEFAULT_OUTPUT_DIR.is_dir():
114
+ for root, dirs, files in os.walk(ARTEFACTS_DEFAULT_OUTPUT_DIR):
115
+ for file in files:
116
+ run.log_artifacts(Path(root) / Path(file))
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
1
+ # file generated by setuptools-scm
2
2
  # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
3
6
  TYPE_CHECKING = False
4
7
  if TYPE_CHECKING:
5
- from typing import Tuple, Union
8
+ from typing import Tuple
9
+ from typing import Union
10
+
6
11
  VERSION_TUPLE = Tuple[Union[int, str], ...]
7
12
  else:
8
13
  VERSION_TUPLE = object
@@ -12,5 +17,5 @@ __version__: str
12
17
  __version_tuple__: VERSION_TUPLE
13
18
  version_tuple: VERSION_TUPLE
14
19
 
15
- __version__ = version = '0.6.19'
16
- __version_tuple__ = version_tuple = (0, 6, 19)
20
+ __version__ = version = '0.7.1'
21
+ __version_tuple__ = version_tuple = (0, 7, 1)
@@ -1,14 +1,16 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: artefacts_cli
3
- Version: 0.6.19
3
+ Version: 0.7.1
4
4
  Author-email: FD <fabian@artefacts.com>, AGC <alejandro@artefacts.com>, TN <tomo@artefacts.com>, EP <eric@artefacts.com>
5
5
  Project-URL: Homepage, https://github.com/art-e-fact/artefacts-client
6
6
  Project-URL: Bug Tracker, https://github.com/art-e-fact/artefacts-client/issues
7
+ Project-URL: Changelog, https://github.com/art-e-fact/artefacts-client/CHANGELOG.md
7
8
  Classifier: Programming Language :: Python :: 3
8
9
  Classifier: License :: OSI Approved :: Apache Software License
9
10
  Classifier: Operating System :: OS Independent
10
11
  Requires-Python: >=3.8
11
12
  Description-Content-Type: text/markdown
13
+ Requires-Dist: artefacts-c2d>=1.7.1
12
14
  Requires-Dist: artefacts-copava>=0.1.11
13
15
  Requires-Dist: click>=8.0.4
14
16
  Requires-Dist: gitignore_parser>=0.1.11
@@ -42,7 +44,7 @@ Requires-Dist: twine; extra == "dev"
42
44
  CLI to the Artefacts platform.
43
45
 
44
46
  [![Documentation](https://img.shields.io/badge/documentation-blue.svg?style=flat-square)](https://docs.artefacts.com/)
45
- [![Code style: Black-compatible with Ruff](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
47
+ [![Ruff Style](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
46
48
 
47
49
  ## Requirements
48
50
 
@@ -1,8 +1,10 @@
1
+ CHANGELOG.md
1
2
  README.md
2
3
  README_INTERNAL.md
3
4
  artefacts.yaml
4
5
  pyproject.toml
5
6
  pytest.ini
7
+ artefacts/__init__.py
6
8
  artefacts/cli/__init__.py
7
9
  artefacts/cli/app.py
8
10
  artefacts/cli/app_containers.py
@@ -54,6 +56,7 @@ tests/cli/test_other.py
54
56
  tests/cli/test_parameters.py
55
57
  tests/cli/test_ros1.py
56
58
  tests/cli/test_ros2.py
59
+ tests/cli/test_utils.py
57
60
  tests/cli/test_warp.py
58
61
  tests/cli/containers/__init__.py
59
62
  tests/cli/containers/test_utils.py
@@ -1,3 +1,4 @@
1
+ artefacts-c2d>=1.7.1
1
2
  artefacts-copava>=0.1.11
2
3
  click>=8.0.4
3
4
  gitignore_parser>=0.1.11
@@ -19,6 +19,7 @@ classifiers = [
19
19
  "Operating System :: OS Independent",
20
20
  ]
21
21
  dependencies = [
22
+ "artefacts-c2d>=1.7.1",
22
23
  "artefacts-copava>=0.1.11",
23
24
  "click>=8.0.4",
24
25
  "gitignore_parser>=0.1.11",
@@ -63,6 +64,7 @@ dev = [
63
64
  [project.urls]
64
65
  "Homepage" = "https://github.com/art-e-fact/artefacts-client"
65
66
  "Bug Tracker" = "https://github.com/art-e-fact/artefacts-client/issues"
67
+ "Changelog" = "https://github.com/art-e-fact/artefacts-client/CHANGELOG.md"
66
68
 
67
69
  [project.scripts]
68
70
  artefacts = "artefacts.cli.app:artefacts"
@@ -0,0 +1,36 @@
1
+ import os
2
+ from uuid import uuid4
3
+
4
+
5
+ from artefacts.cli.app_containers import containers
6
+
7
+
8
+ def test_container_package_exists(cli_runner):
9
+ result = cli_runner.invoke(containers, [])
10
+ assert result.exit_code == 0
11
+
12
+
13
+ def test_container_package_build_specific_dockerfile(
14
+ cli_runner, dockerfile_available, docker_mocker
15
+ ):
16
+ dockerfile = "non_standard_dockerfile"
17
+ result = cli_runner.invoke(containers, ["build", "--dockerfile", dockerfile])
18
+ dockerfile_available.assert_any_call(dockerfile)
19
+ assert result.exit_code == 0
20
+
21
+
22
+ def test_container_package_build(
23
+ cli_runner, dockerfile_available, docker_mocker, sample_artefacts_config
24
+ ):
25
+ before = len(docker_mocker.images())
26
+ result = cli_runner.invoke(containers, ["build"])
27
+ assert result.exit_code == 0
28
+ assert len(docker_mocker.images()) == before + len(sample_artefacts_config["jobs"])
29
+ for job_name in sample_artefacts_config["jobs"]:
30
+ # Check the images exist, with name following our naming convention project/job_name
31
+ assert (
32
+ docker_mocker.get_image(
33
+ f"{sample_artefacts_config['project'].lower()}/{job_name}"
34
+ )
35
+ is not None
36
+ )
@@ -13,14 +13,6 @@ from artefacts.cli.app import (
13
13
  )
14
14
 
15
15
 
16
- @pytest.fixture(scope="class")
17
- def project_with_key(cli_runner):
18
- project_name = "_pytest-project_"
19
- add_key_to_conf(project_name, "MYAPIKEY")
20
- yield project_name
21
- cli_runner.invoke(delete, [project_name])
22
-
23
-
24
16
  def test_hello(cli_runner):
25
17
  project_name = "_pytest-project_"
26
18
  result = cli_runner.invoke(hello, [project_name])
@@ -60,9 +52,9 @@ def test_run_with_conf_invalid_jobname(cli_runner, project_with_key):
60
52
  )
61
53
  assert result.exit_code == 1
62
54
  assert result.output == (
63
- "Connecting to https://app.artefacts.com/api using ApiKey\n"
64
- f"Starting tests for {project_with_key}\n"
65
- "Error: Job invalid_job_name not defined\n"
55
+ "[invalid_job_name] Connecting to https://app.artefacts.com/api using ApiKey\n"
56
+ f"[invalid_job_name] Starting tests\n"
57
+ "[invalid_job_name] Error: Job name not defined\n"
66
58
  )
67
59
 
68
60
 
@@ -72,13 +64,13 @@ def test_run_with_conf(cli_runner, project_with_key):
72
64
  )
73
65
  assert result.exit_code == 0
74
66
  assert result.output == (
75
- "Connecting to https://app.artefacts.com/api using ApiKey\n"
76
- f"Starting tests for {project_with_key}\n"
77
- "Starting scenario 1/2: basic-tests\n"
78
- "performing dry run\n"
79
- "Starting scenario 2/2: other-tests\n"
80
- "performing dry run\n"
81
- "Done\n"
67
+ "[simple_job] Connecting to https://app.artefacts.com/api using ApiKey\n"
68
+ f"[simple_job] Starting tests\n"
69
+ "[simple_job] Starting scenario 1/2: basic-tests\n"
70
+ "[simple_job] Performing dry run\n"
71
+ "[simple_job] Starting scenario 2/2: other-tests\n"
72
+ "[simple_job] Performing dry run\n"
73
+ "[simple_job] Done\n"
82
74
  )
83
75
 
84
76
 
@@ -89,13 +81,13 @@ def test_run_with_mode_ros1(cli_runner, project_with_key):
89
81
  )
90
82
  assert result.exit_code == 0
91
83
  assert result.output == (
92
- "Connecting to https://app.artefacts.com/api using ApiKey\n"
93
- "Starting tests for _pytest-project_\n"
94
- "Starting scenario 1/2: basic-tests\n"
95
- "performing dry run\n"
96
- "Starting scenario 2/2: other-tests\n"
97
- "performing dry run\n"
98
- "Done\n"
84
+ "[simple_job] Connecting to https://app.artefacts.com/api using ApiKey\n"
85
+ "[simple_job] Starting tests\n"
86
+ "[simple_job] Starting scenario 1/2: basic-tests\n"
87
+ "[simple_job] Performing dry run\n"
88
+ "[simple_job] Starting scenario 2/2: other-tests\n"
89
+ "[simple_job] Performing dry run\n"
90
+ "[simple_job] Done\n"
99
91
  )
100
92
 
101
93
 
@@ -139,3 +131,15 @@ def test_run_remote_with_conf_invalid_jobname(cli_runner, project_with_key):
139
131
  def test_APIConf(project_with_key):
140
132
  conf = APIConf(project_with_key)
141
133
  assert conf.headers["Authorization"] == "ApiKey MYAPIKEY"
134
+
135
+
136
+ def test_upload_default_dir(cli_runner, project_with_key, mocker):
137
+ # Note the patch applies to the object loaded in app, rather than the original in utils.
138
+ # https://docs.python.org/3/library/unittest.mock.html#where-to-patch
139
+ sut = mocker.patch("artefacts.cli.app.add_output_from_default")
140
+ result = cli_runner.invoke(
141
+ run, ["simple_job", "--config", "tests/fixtures/warp.yaml", "--dryrun"]
142
+ )
143
+ assert result.exit_code == 0
144
+ # Called twice in this config.
145
+ assert sut.call_count == 2
@@ -0,0 +1,24 @@
1
+ import pytest
2
+
3
+ from artefacts.cli import WarpJob, WarpRun
4
+ from artefacts.cli.app import APIConf
5
+ from artefacts.cli.utils import add_output_from_default
6
+
7
+
8
+ @pytest.fixture
9
+ def new_run(project_with_key):
10
+ job = WarpJob(
11
+ project_with_key, APIConf(project_with_key), "jobname", {}, dryrun=True
12
+ )
13
+ return WarpRun(job=job, scenario={}, run_n=0)
14
+
15
+
16
+ def test_adds_nothing_on_missing_default_output(new_run, mocker, project_with_key):
17
+ path = mocker.patch("artefacts.cli.utils.ARTEFACTS_DEFAULT_OUTPUT_DIR")
18
+ mocked = {
19
+ "exists.return_value": False,
20
+ "is_dir.return_value": True,
21
+ }
22
+ path.configure_mock(**mocked)
23
+ add_output_from_default(new_run)
24
+ assert len(new_run.uploads) == 0
@@ -6,6 +6,9 @@ from click.testing import CliRunner
6
6
 
7
7
  from tests.utils import docker_mock
8
8
 
9
+ from artefacts.cli.app import add_key_to_conf, delete
10
+ from artefacts.cli.utils import read_config
11
+
9
12
 
10
13
  def dockerfile_presence(mocker, value: bool):
11
14
  original = os.path.exists
@@ -39,3 +42,16 @@ def docker_mocker(module_mocker):
39
42
  @pytest.fixture(scope="module")
40
43
  def cli_runner():
41
44
  return CliRunner()
45
+
46
+
47
+ @pytest.fixture(scope="class")
48
+ def project_with_key(cli_runner):
49
+ project_name = "_pytest-project_"
50
+ add_key_to_conf(project_name, "MYAPIKEY")
51
+ yield project_name
52
+ cli_runner.invoke(delete, [project_name])
53
+
54
+
55
+ @pytest.fixture(scope="session")
56
+ def sample_artefacts_config():
57
+ return read_config(os.path.join(os.path.dirname(__file__), "..", "artefacts.yaml"))
@@ -1,99 +0,0 @@
1
- import os
2
-
3
- import click
4
-
5
- from artefacts.cli.constants import DEFAULT_API_URL
6
- from artefacts.cli.utils import config_validation, read_config
7
- from artefacts.cli.containers.utils import ContainerMgr
8
-
9
-
10
- @click.group()
11
- @click.option("--debug/--no-debug", default=False)
12
- @click.pass_context
13
- def containers(ctx: click.Context, debug: bool):
14
- ctx.ensure_object(dict)
15
- ctx.obj["debug"] = debug
16
-
17
-
18
- @containers.command()
19
- @click.option(
20
- "--path",
21
- default=".",
22
- help="Path to the root of the project, where a Dockerfile is available.",
23
- )
24
- @click.option(
25
- "--dockerfile",
26
- default="Dockerfile",
27
- help="File name of the container definition file. Defaults to the standard Dockerfile inside the project root (see --path)",
28
- )
29
- @click.option(
30
- "--name",
31
- required=False,
32
- help="Name for the generated container",
33
- )
34
- @click.pass_context
35
- def build(ctx: click.Context, path: str, dockerfile: str, name: str):
36
- if not os.path.exists(os.path.join(path, dockerfile)):
37
- raise click.ClickException(
38
- f"No {dockerfile} found here. I cannot build the container."
39
- )
40
- if name is None:
41
- name = "artefacts"
42
- handler = ContainerMgr()
43
- image, _ = handler.build(path=path, name=name)
44
- print(f"Package complete in image: {image}")
45
-
46
-
47
- @containers.command()
48
- @click.argument("name")
49
- @click.pass_context
50
- def check(ctx: click.Context, name: str):
51
- if name is None:
52
- name = "artefacts"
53
- handler = ContainerMgr()
54
- result = handler.check(name)
55
- if ctx.parent is None:
56
- # Print only if the command is called directly.
57
- print(f"Package {name} exists and ready to use.")
58
- return result
59
-
60
-
61
- @containers.command()
62
- @click.argument("image")
63
- @click.argument("jobname")
64
- @click.option(
65
- "--config",
66
- callback=config_validation,
67
- default="artefacts.yaml",
68
- help="Artefacts config file.",
69
- )
70
- @click.option(
71
- "--with-gui",
72
- "with_gui",
73
- default=False,
74
- help="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 an X11 environment, typically with Qt, so this may not work without a appropriate environment.",
75
- )
76
- @click.pass_context
77
- def run(ctx: click.Context, image: str, jobname: str, config: str, with_gui: bool):
78
- try:
79
- artefacts_config = read_config(config)
80
- except FileNotFoundError:
81
- raise click.ClickException(f"Project config file not found: {config}")
82
- project = artefacts_config["project"]
83
- handler = ContainerMgr()
84
- params = dict(
85
- image=image,
86
- project=project,
87
- jobname=jobname,
88
- with_gui=with_gui,
89
- # Hidden settings primarily useful to Artefacts developers
90
- api_url=os.environ.get("ARTEFACTS_API_URL", DEFAULT_API_URL),
91
- api_key=os.environ.get("ARTEFACTS_KEY", None),
92
- )
93
- container, logs = handler.run(**params)
94
- if container:
95
- print(f"Package run complete: Container Id for inspection: {container['Id']}")
96
- else:
97
- print("Package run failed:")
98
- for entry in logs:
99
- print("\t- " + entry)
@@ -1,53 +0,0 @@
1
- import os
2
- from uuid import uuid4
3
-
4
-
5
- from artefacts.cli.app_containers import containers
6
-
7
-
8
- def test_container_package_exists(cli_runner):
9
- result = cli_runner.invoke(containers, [])
10
- assert result.exit_code == 0
11
-
12
-
13
- def test_container_package_build_specific_dockerfile(
14
- cli_runner, dockerfile_available, docker_mocker
15
- ):
16
- dockerfile = "non_standard_dockerfile"
17
- result = cli_runner.invoke(containers, ["build", "--dockerfile", dockerfile])
18
- dockerfile_available.assert_any_call(os.path.join(".", dockerfile))
19
- assert result.exit_code == 0
20
-
21
-
22
- def test_container_package_build_specific_dockerfile_missing(
23
- cli_runner, dockerfile_not_available
24
- ):
25
- dockerfile = "non_standard_dockerfile"
26
- result = cli_runner.invoke(containers, ["build", "--dockerfile", dockerfile])
27
- dockerfile_not_available.assert_any_call(os.path.join(".", dockerfile))
28
- assert result.exit_code == 1
29
- assert (
30
- result.output.strip()
31
- == f"Error: No {dockerfile} found here. I cannot build the container."
32
- )
33
-
34
-
35
- def test_container_package_build_specific_image_name(
36
- cli_runner, dockerfile_available, docker_mocker
37
- ):
38
- name = str(uuid4())
39
- before = len(docker_mocker.images())
40
- result = cli_runner.invoke(containers, ["build", "--name", name])
41
- assert result.exit_code == 0
42
- assert len(docker_mocker.images()) == before + 1
43
- assert docker_mocker.get_image(name).Repository == name
44
-
45
-
46
- def test_container_package_build_default_image_name(
47
- cli_runner, dockerfile_available, docker_mocker
48
- ):
49
- before = len(docker_mocker.images())
50
- result = cli_runner.invoke(containers, ["build"])
51
- assert result.exit_code == 0
52
- assert len(docker_mocker.images()) == before + 1
53
- assert docker_mocker.get_image("artefacts") is not None
File without changes
File without changes