yellowdog-python-examples 8.1.3__tar.gz → 8.1.5__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 (80) hide show
  1. {yellowdog_python_examples-8.1.3/yellowdog_python_examples.egg-info → yellowdog_python_examples-8.1.5}/PKG-INFO +2 -2
  2. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/README.md +4 -4
  3. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/requirements.txt +1 -1
  4. yellowdog_python_examples-8.1.5/yellowdog_cli/__init__.py +1 -0
  5. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/create.py +27 -37
  6. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/instantiate.py +5 -5
  7. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/provision.py +4 -4
  8. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/remove.py +15 -15
  9. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/entity_utils.py +206 -125
  10. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/printing.py +9 -7
  11. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/provision_utils.py +8 -29
  12. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5/yellowdog_python_examples.egg-info}/PKG-INFO +2 -2
  13. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_python_examples.egg-info/requires.txt +1 -1
  14. yellowdog_python_examples-8.1.3/yellowdog_cli/__init__.py +0 -1
  15. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/LICENSE +0 -0
  16. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/PYPI_README.md +0 -0
  17. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/pyproject.toml +0 -0
  18. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/setup.cfg +0 -0
  19. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_create_remove.py +0 -0
  20. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_demos.py +0 -0
  21. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_dryruns.py +0 -0
  22. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_entrypoints.py +0 -0
  23. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_gui.py +0 -0
  24. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_list.py +0 -0
  25. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_objects.py +0 -0
  26. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/tests/test_variable_processing.py +0 -0
  27. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/abort.py +0 -0
  28. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/admin.py +0 -0
  29. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/boost.py +0 -0
  30. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/cancel.py +0 -0
  31. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/cloudwizard.py +0 -0
  32. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/compare.py +0 -0
  33. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/delete.py +0 -0
  34. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/download.py +0 -0
  35. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/finish.py +0 -0
  36. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/follow.py +0 -0
  37. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/format_json.py +0 -0
  38. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/hold.py +0 -0
  39. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/jsonnet2json.py +0 -0
  40. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/list.py +0 -0
  41. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/resize.py +0 -0
  42. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/show.py +0 -0
  43. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/shutdown.py +0 -0
  44. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/start.py +0 -0
  45. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/submit.py +0 -0
  46. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/terminate.py +0 -0
  47. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/upload.py +0 -0
  48. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/__init__.py +0 -0
  49. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/args.py +0 -0
  50. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/check_imports.py +0 -0
  51. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
  52. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
  53. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
  54. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
  55. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
  56. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/compact_json.py +0 -0
  57. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/config_types.py +0 -0
  58. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/csv_data.py +0 -0
  59. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/follow_utils.py +0 -0
  60. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/interactive.py +0 -0
  61. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/items.py +0 -0
  62. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/load_config.py +0 -0
  63. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/load_resources.py +0 -0
  64. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/misc_utils.py +0 -0
  65. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/property_names.py +0 -0
  66. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
  67. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/settings.py +0 -0
  68. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/start_hold_common.py +0 -0
  69. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/submit_utils.py +0 -0
  70. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/type_check.py +0 -0
  71. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/upload_utils.py +0 -0
  72. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/validate_properties.py +0 -0
  73. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/variables.py +0 -0
  74. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/wrapper.py +0 -0
  75. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/utils/ydid_utils.py +0 -0
  76. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_cli/version.py +0 -0
  77. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
  78. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
  79. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
  80. {yellowdog_python_examples-8.1.3 → yellowdog_python_examples-8.1.5}/yellowdog_python_examples.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-python-examples
3
- Version: 8.1.3
3
+ Version: 8.1.5
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ Requires-Dist: requests
26
26
  Requires-Dist: rich==13.9.4
27
27
  Requires-Dist: tabulate>=0.9.0
28
28
  Requires-Dist: toml
29
- Requires-Dist: yellowdog-sdk>=11.5.0
29
+ Requires-Dist: yellowdog-sdk>=11.6.0
30
30
  Provides-Extra: jsonnet
31
31
  Requires-Dist: jsonnet; extra == "jsonnet"
32
32
  Provides-Extra: cloudwizard
@@ -1693,7 +1693,7 @@ The following properties are available:
1693
1693
  |:------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|
1694
1694
  | `idleNodeTimeout` | The timeout in minutes after which an idle node will be shut down. Set this to `0` to disable the timeout. | `5.0` |
1695
1695
  | `idlePoolTimeout` | The timeout in minutes after which an idle Worker Pool will be shut down. Set this to `0` to disable the timeout. | `30.0` |
1696
- | `imagesId` | The image ID, Image Family ID, Image Family name, or Image Group name to use when booting instances. | |
1696
+ | `imagesId` | The Image ID, Image Family ID, Image Family name, or Image Group name to use when booting instances. | |
1697
1697
  | `instanceTags` | The dictionary of instance tags to apply to the instances. Tag names must be lower case. | |
1698
1698
  | `maintainInstanceCount` | Only used when instantiating Compute Requirements; attempt to maintain the requested number of instances. | `False` |
1699
1699
  | `maxNodes` | The maximum number of nodes to which the Worker Pool can be scaled up. | `1` |
@@ -1717,7 +1717,7 @@ The following properties are available:
1717
1717
 
1718
1718
  The `templateId` property can be directly populated with the YellowDog ID (YDID), or it can be populated with the textual name of the template, in the form `namespace/template_name`.
1719
1719
 
1720
- Similarly, the `imagesId` property can be populated with the YDID of an Image Family, Image Group, Image, or a string representing the native name of a cloud provider image (e.g., an AWS AMI). It can also be populated with an Image Family name in the form `namespace/image_family_name`, or an Image Group name in the form `namespace/image_family_name/image_group_name`.
1720
+ Similarly, the `imagesId` property can be populated with the YDID of an Image Family, Image Group, Image, or a string representing the native name of a cloud provider image (e.g., an AWS AMI). It can also be populated with an Image Family name in the form `namespace/image_family_name`, or an Image Group name in the form `namespace/image_family_name/image_group_name` or `image_family_name/image_group_name`. Optionally, a `yd/` prefix can be supplied. The CLI will aim to map the provided name into an Image Family or Group YDID.
1721
1721
 
1722
1722
  ## Automatic Properties
1723
1723
 
@@ -2171,7 +2171,7 @@ An example Compute Source resource specification is found below:
2171
2171
  }
2172
2172
  ```
2173
2173
 
2174
- In the Compute Source Template `imageId` property, an Image Family name **namespace/family-name** or Image Group name **namespace/family-name/group-name** may be used instead of an ID. For example: `"imageId": "yellowdog/yd-agent-docker"`. The `yd-create` command will look up the Image Family name and substitute its ID. A **`yd/`** prefix may also optionally be used.
2174
+ In the Compute Source Template `imageId` property, an Image Family name **namespace/family-name** or Image Group name **namespace/family-name/group-name** may be used instead of an ID. For example: `"imageId": "yellowdog/yd-agent-docker"`. The `yd-create` command will look up the Image Family name and substitute with a well-formed name or ID. A **`yd/`** prefix may also optionally be used.
2175
2175
 
2176
2176
  ## Compute Requirement Templates
2177
2177
 
@@ -2197,7 +2197,7 @@ An example Compute Requirement resource specification is found below, for a **st
2197
2197
 
2198
2198
  Note that Compute Source Template **namespace/names** in the form `namespace/compute_source_template_name` can be used instead of their IDs: the **yd-create** command will look up the IDs and make the substitutions. The Compute Source Templates must already exist.
2199
2199
 
2200
- Also, In the `imagesId` property, an Image Family name **namespace/family-name** or an Image Group name **namespace/family-name/group-name** may be used instead of an ID. For example: `"imagesId": "yellowdog/yd-agent-docker/latest"`. The `yd-create` command will look up the Image Family name and substitute its ID. A **`yd/`** prefix may also optionally be used.
2200
+ Also, In the `imagesId` property, an Image Family name **namespace/family-name** or an Image Group name **namespace/family-name/group-name** may be used instead of an ID. For example: `"imagesId": "yellowdog/yd-agent-docker/latest"`. The `yd-create` command will look up the Image Family name and substitute with a well-formed name or ID. A **`yd/`** prefix may also optionally be used.
2201
2201
 
2202
2202
  A **dynamic** template example is:
2203
2203
 
@@ -5,4 +5,4 @@ requests
5
5
  rich == 13.9.4
6
6
  tabulate >= 0.9.0
7
7
  toml
8
- yellowdog-sdk >= 11.5.0
8
+ yellowdog-sdk >= 11.6.0
@@ -0,0 +1 @@
1
+ __version__ = "8.1.5"
@@ -50,10 +50,10 @@ from yellowdog_cli.utils.entity_utils import (
50
50
  clear_application_caches,
51
51
  clear_compute_source_template_cache,
52
52
  clear_group_caches,
53
- clear_image_family_search_cache,
53
+ clear_image_caches,
54
54
  find_compute_requirement_template_id_by_name,
55
55
  find_compute_source_template_id_by_name,
56
- find_image_family_or_group_id_by_name,
56
+ find_image_name_or_id,
57
57
  get_application_groups,
58
58
  get_application_id_by_name,
59
59
  get_group_id_by_name,
@@ -130,7 +130,9 @@ from yellowdog_cli.utils.wrapper import ARGS_PARSER, CLIENT, CONFIG_COMMON, main
130
130
  from yellowdog_cli.utils.ydid_utils import YDIDType, get_ydid_type
131
131
 
132
132
  CLEAR_CST_CACHE: bool = False # Track whether the CST cache needs to be cleared
133
- CLEAR_IMAGE_FAMILY_CACHE: bool = False # Track whether the IF cache needs to be cleared
133
+ CLEAR_IMAGE_FAMILY_CACHE: bool = (
134
+ False # Track whether the image caches need to be cleared
135
+ )
134
136
 
135
137
 
136
138
  @main_wrapper
@@ -233,7 +235,7 @@ def create_compute_source_template(resource: Dict):
233
235
  # Allow image families (etc.) to be referenced by name rather than ID
234
236
  global CLEAR_IMAGE_FAMILY_CACHE
235
237
  if CLEAR_IMAGE_FAMILY_CACHE: # Update the IF cache if required
236
- clear_image_family_search_cache()
238
+ clear_image_caches()
237
239
  CLEAR_IMAGE_FAMILY_CACHE = False
238
240
 
239
241
  # Google CSTs use property name 'image' instead of 'imageId'
@@ -244,17 +246,14 @@ def create_compute_source_template(resource: Dict):
244
246
  else PROP_IMAGE
245
247
  )
246
248
 
247
- image_id = source.get(image_property_name)
248
- if get_ydid_type(image_id) not in [
249
- YDIDType.IMAGE_FAMILY,
250
- YDIDType.IMAGE_GROUP,
251
- YDIDType.IMAGE,
252
- ]:
253
- image_family_id = find_image_family_or_group_id_by_name(
254
- client=CLIENT, image_family_name=image_id
255
- )
256
- if image_family_id is not None:
257
- source[image_property_name] = image_family_id
249
+ image_id = find_image_name_or_id(
250
+ client=CLIENT,
251
+ image_name_or_id=source.get(image_property_name),
252
+ always_return_id=False,
253
+ report_substitutions=True,
254
+ )
255
+ if image_id is not None:
256
+ source[image_property_name] = image_id
258
257
 
259
258
  if ARGS_PARSER.dry_run:
260
259
  resource[PROP_SOURCE] = source
@@ -322,31 +321,26 @@ def create_compute_requirement_template(resource: Dict):
322
321
  # Allow image families to be referenced by name rather than ID
323
322
  global CLEAR_IMAGE_FAMILY_CACHE
324
323
  if CLEAR_IMAGE_FAMILY_CACHE: # Update the IF cache if required
325
- clear_compute_source_template_cache()
324
+ clear_image_caches()
326
325
  CLEAR_IMAGE_FAMILY_CACHE = False
327
326
 
328
- def _get_images_id(image_str: str, context: Dict, key: str) -> int:
327
+ def _get_images_id(image_str: str, context: Dict, key: str):
329
328
  """
330
- Helper function to match an image family name into an ID.
329
+ Helper function to resolve an image ID.
331
330
  """
332
- if get_ydid_type(image_str) not in [
333
- YDIDType.IMAGE_FAMILY,
334
- YDIDType.IMAGE_GROUP,
335
- YDIDType.IMAGE,
336
- ]:
337
- image_family_id = find_image_family_or_group_id_by_name(
338
- client=CLIENT, image_family_name=image_str
339
- )
340
- if image_family_id is not None:
341
- context[key] = image_family_id
342
- return 1
343
- return 0
331
+ images_id_ = find_image_name_or_id(
332
+ client=CLIENT,
333
+ image_name_or_id=image_str,
334
+ always_return_id=False,
335
+ report_substitutions=True,
336
+ )
337
+ if images_id_ is not None:
338
+ context[key] = images_id_
344
339
 
345
340
  # Prepend the namespace when searching for existing templates
346
341
  name = f"{namespace}{NAMESPACE_PREFIX_SEPARATOR}{name}"
347
342
 
348
343
  source_template_substitutions = 0
349
- source_image_id_substitutions = 0
350
344
 
351
345
  # Dynamic templates don't have 'sources'; return '[]'
352
346
  for source in resource.get(PROP_SOURCES, []):
@@ -365,16 +359,12 @@ def create_compute_requirement_template(resource: Dict):
365
359
 
366
360
  source_image_id = source.get(PROP_IMAGE_ID)
367
361
  if source_image_id is not None:
368
- source_image_id_substitutions += _get_images_id(
369
- source_image_id, source, PROP_IMAGE_ID
370
- )
362
+ _get_images_id(source_image_id, source, PROP_IMAGE_ID)
371
363
 
372
364
  if source_template_substitutions > 0:
373
365
  print_log(
374
366
  f"Replaced {source_template_substitutions} Compute Source Template name(s) with ID(s)"
375
367
  )
376
- if source_image_id_substitutions > 0:
377
- print_log(f"Replaced {source_image_id_substitutions} Image name(s) with ID(s)")
378
368
 
379
369
  images_id = resource.get(PROP_IMAGES_ID)
380
370
  if images_id is not None:
@@ -549,7 +539,7 @@ def create_image_family(resource):
549
539
  # This will create the Image Family and all of its constituent
550
540
  # Image Group/Image resources
551
541
  image_family = _create_image_family(image_family, fq_name)
552
- print_log(f"Created Machine Image Family '{fq_name}' ('{image_family.id}')")
542
+ print_log(f"Created Machine Image Family '{fq_name}' ({image_family.id})")
553
543
  if ARGS_PARSER.quiet:
554
544
  print(image_family.id)
555
545
  else:
@@ -30,7 +30,7 @@ from yellowdog_cli.utils.printing import (
30
30
  print_yd_object,
31
31
  )
32
32
  from yellowdog_cli.utils.provision_utils import (
33
- get_image_family_id,
33
+ get_image_id,
34
34
  get_template_id,
35
35
  get_user_data_property,
36
36
  )
@@ -92,8 +92,8 @@ def main():
92
92
 
93
93
  # Allow use of IF name instead of ID
94
94
  if CONFIG_WP.images_id is not None:
95
- CONFIG_WP.images_id = get_image_family_id(
96
- client=CLIENT, image_family_id_or_name=CONFIG_WP.images_id
95
+ CONFIG_WP.images_id = get_image_id(
96
+ client=CLIENT, image_name_or_id=CONFIG_WP.images_id
97
97
  )
98
98
 
99
99
  if not ARGS_PARSER.report:
@@ -303,8 +303,8 @@ def _create_compute_requirement_from_json(
303
303
 
304
304
  # Allow use of IF name instead of ID
305
305
  if cr_data.get("imagesId") is not None:
306
- cr_data["imagesId"] = get_image_family_id(
307
- client=CLIENT, image_family_id_or_name=cr_data["imagesId"]
306
+ cr_data["imagesId"] = get_image_id(
307
+ client=CLIENT, image_name_or_id=cr_data["imagesId"]
308
308
  )
309
309
 
310
310
  if ARGS_PARSER.dry_run:
@@ -47,7 +47,7 @@ from yellowdog_cli.utils.property_names import (
47
47
  WORKER_TAG,
48
48
  )
49
49
  from yellowdog_cli.utils.provision_utils import (
50
- get_image_family_id,
50
+ get_image_id,
51
51
  get_template_id,
52
52
  get_user_data_property,
53
53
  )
@@ -170,7 +170,7 @@ def create_worker_pool_from_json(wp_json_file: str) -> None:
170
170
 
171
171
  # Allow Image Family name to be used instead of ID
172
172
  if reqt_template_usage.get(IMAGES_ID) is not None:
173
- reqt_template_usage[IMAGES_ID] = get_image_family_id(
173
+ reqt_template_usage[IMAGES_ID] = get_image_id(
174
174
  CLIENT, reqt_template_usage[IMAGES_ID]
175
175
  )
176
176
 
@@ -289,8 +289,8 @@ def create_worker_pool_from_toml():
289
289
 
290
290
  # Allow the Image Family name to be used instead of ID
291
291
  if CONFIG_WP.images_id is not None:
292
- CONFIG_WP.images_id = get_image_family_id(
293
- client=CLIENT, image_family_id_or_name=CONFIG_WP.images_id
292
+ CONFIG_WP.images_id = get_image_id(
293
+ client=CLIENT, image_name_or_id=CONFIG_WP.images_id
294
294
  )
295
295
 
296
296
  node_boot_timeout = (
@@ -207,17 +207,17 @@ def remove_keyring(resource: Dict):
207
207
  print_error(f"Expected property to be defined ({e})")
208
208
  return
209
209
 
210
- if not confirmed(f"Delete Keyring '{name}'?"):
210
+ if not confirmed(f"Remove Keyring '{name}'?"):
211
211
  return
212
212
 
213
213
  try:
214
214
  CLIENT.keyring_client.delete_keyring_by_name(name)
215
- print_log(f"Deleted Keyring '{name}'")
215
+ print_log(f"Removed Keyring '{name}'")
216
216
  except HTTPError as e:
217
217
  if e.response.status_code == 404:
218
- print_error(f"Keyring '{name}' not found")
218
+ print_error(f"Cannot find Keyring '{name}'")
219
219
  else:
220
- print_error(f"Unable to delete Keyring '{name}': {e}")
220
+ print_error(f"Unable to remove Keyring '{name}': {e}")
221
221
 
222
222
 
223
223
  def remove_credential(resource: Dict):
@@ -246,7 +246,7 @@ def remove_credential(resource: Dict):
246
246
  except HTTPError as e:
247
247
  if e.response.status_code == 404:
248
248
  print_error(
249
- f"Keyring '{keyring_name}' not found (possibly already deleted,"
249
+ f"Cannot find Keyring '{keyring_name}'(possibly already deleted,"
250
250
  " including its credentials?)"
251
251
  )
252
252
  else:
@@ -275,7 +275,7 @@ def remove_image_family(resource: Dict):
275
275
  )
276
276
  except HTTPError as e:
277
277
  if e.response.status_code == 404:
278
- print_error(f"Machine Image Family '{fq_name}' not found")
278
+ print_error(f"Cannot find Machine Image Family '{fq_name}'")
279
279
  return
280
280
  else:
281
281
  raise e
@@ -285,9 +285,9 @@ def remove_image_family(resource: Dict):
285
285
 
286
286
  try:
287
287
  CLIENT.images_client.delete_image_family(image_family)
288
- print_log(f"Deleted Image Family '{fq_name}' ('{image_family.id}')")
288
+ print_log(f"Removed Image Family '{fq_name}' ({image_family.id})")
289
289
  except Exception as e:
290
- print_error(f"Unable to delete Image Family '{fq_name}': {e}")
290
+ print_error(f"Unable to remove Image Family '{fq_name}': {e}")
291
291
 
292
292
 
293
293
  def remove_namespace_configuration(resource: Dict):
@@ -304,7 +304,7 @@ def remove_namespace_configuration(resource: Dict):
304
304
  CLIENT.object_store_client.get_namespace_storage_configurations()
305
305
  )
306
306
  if namespace not in [x.namespace for x in namespaces]:
307
- print_error(f"Namespace Storage Configuration '{namespace}' not found")
307
+ print_error(f"Cannot find Namespace Storage Configuration '{namespace}'")
308
308
  return
309
309
 
310
310
  if not confirmed(f"Remove Namespace Storage Configuration '{namespace}'?"):
@@ -428,7 +428,7 @@ def remove_resource_by_id(resource_id: str):
428
428
  CLIENT.keyring_client.delete_keyring_by_name(keyring.name)
429
429
  print_log(f"Removed Keyring {resource_id}")
430
430
  return
431
- raise Exception(f"Keyring {resource_id} not found")
431
+ raise Exception(f"Cannot find Keyring {resource_id}")
432
432
 
433
433
  elif get_ydid_type(resource_id) == YDIDType.WORKER_POOL:
434
434
  if confirmed(f"Shut down Worker Pool {resource_id}?"):
@@ -497,7 +497,7 @@ def remove_namespace_policy(resource: Dict):
497
497
  return
498
498
  except Exception:
499
499
  # Assume it's not found ... 404 from API
500
- print_error(f"Namespace Policy '{namespace}' not found")
500
+ print_error(f"Cannot find Namespace Policy '{namespace}'")
501
501
  return
502
502
 
503
503
  if not confirmed(f"Remove Namespace Policy '{namespace}'?"):
@@ -522,7 +522,7 @@ def remove_group(resource: Dict):
522
522
 
523
523
  group_id = get_group_id_by_name(CLIENT, group_name)
524
524
  if group_id is None:
525
- print_warning(f"Group '{group_name}' not found")
525
+ print_warning(f"Cannot find Group '{group_name}'")
526
526
  return
527
527
 
528
528
  if not confirmed(f"Remove Group '{group_name}' ({group_id})?"):
@@ -533,7 +533,7 @@ def remove_group(resource: Dict):
533
533
  print_log(f"Removed Group '{group_name}' ({group_id})")
534
534
  clear_group_caches()
535
535
  except Exception as e:
536
- print_error(f"Unable to delete Group '{group_name}' ({group_id}): {e}")
536
+ print_error(f"Unable to remove Group '{group_name}' ({group_id}): {e}")
537
537
 
538
538
 
539
539
  def remove_application(resource: Dict):
@@ -548,7 +548,7 @@ def remove_application(resource: Dict):
548
548
 
549
549
  app_id = get_application_id_by_name(CLIENT, app_name)
550
550
  if app_id is None:
551
- print_warning(f"Application '{app_name}' not found")
551
+ print_warning(f"Cannot find Application '{app_name}'")
552
552
  return
553
553
 
554
554
  if not confirmed(f"Remove Application '{app_name}' ({app_id})?"):
@@ -574,7 +574,7 @@ def remove_namespace(resource: Dict):
574
574
 
575
575
  namespace_id = get_namespace_id_by_name(CLIENT, name)
576
576
  if namespace_id is None:
577
- print_warning(f"Namespace '{name}' not found")
577
+ print_warning(f"Cannot find Namespace '{name}'")
578
578
  return
579
579
 
580
580
  if not confirmed(f"Remove Namespace '{name}'?"):
@@ -24,6 +24,7 @@ from yellowdog_client.model import (
24
24
  ExternalUser,
25
25
  GroupSearch,
26
26
  GroupSummary,
27
+ ImageAccess,
27
28
  InternalUser,
28
29
  MachineImageFamily,
29
30
  MachineImageFamilySearch,
@@ -53,7 +54,7 @@ from yellowdog_client.model import (
53
54
 
54
55
  from yellowdog_cli.utils.args import ARGS_PARSER
55
56
  from yellowdog_cli.utils.interactive import confirmed, select
56
- from yellowdog_cli.utils.printing import print_log, print_warning
57
+ from yellowdog_cli.utils.printing import print_log
57
58
  from yellowdog_cli.utils.settings import NAMESPACE_PREFIX_SEPARATOR
58
59
  from yellowdog_cli.utils.ydid_utils import YDIDType, get_ydid_type
59
60
 
@@ -94,7 +95,6 @@ def get_filtered_work_requirements(
94
95
  Get a list of Work Requirements filtered by namespace, tag
95
96
  and status. Supply either include_filter OR exclude_filter.
96
97
  """
97
-
98
98
  if include_filter is None:
99
99
  wr_search = WorkRequirementSearch(namespaces=[namespace], tag=tag)
100
100
  else:
@@ -231,6 +231,7 @@ def _find_id_by_name(
231
231
  )
232
232
 
233
233
 
234
+ @lru_cache
234
235
  def find_compute_source_template_id_by_name(
235
236
  client: PlatformClient, name: str
236
237
  ) -> Optional[str]:
@@ -238,7 +239,12 @@ def find_compute_source_template_id_by_name(
238
239
  Find a Compute Source Template id by name.
239
240
  Compute Source Template names are unique within a namespace.
240
241
  """
241
- return _find_id_by_name(name, client, get_compute_source_templates)
242
+ template_id = _find_id_by_name(name, client, get_compute_source_templates)
243
+ if template_id is not None:
244
+ print_log(
245
+ f"Replaced Compute Source Template name '{name}' with ID {template_id}"
246
+ )
247
+ return template_id
242
248
 
243
249
 
244
250
  @lru_cache
@@ -277,8 +283,10 @@ def get_work_requirement_summaries(
277
283
  def clear_compute_source_template_cache():
278
284
  """
279
285
  Clear the cache of Compute Source Templates.
286
+ Clear name -> CST lookups.
280
287
  """
281
288
  get_compute_source_templates.cache_clear()
289
+ find_compute_source_template_id_by_name.cache_clear()
282
290
 
283
291
 
284
292
  def find_compute_requirement_template_id_by_name(
@@ -349,146 +357,172 @@ def get_worker_pools(
349
357
 
350
358
 
351
359
  @lru_cache
352
- def find_image_family_or_group_id_by_name(
353
- client: PlatformClient, image_family_name
360
+ def find_image_name_or_id(
361
+ client: PlatformClient,
362
+ image_name_or_id: Optional[str],
363
+ always_return_id: bool = True,
364
+ report_substitutions: bool = True,
354
365
  ) -> Optional[str]:
355
366
  """
356
- Resolve image family references. Complicated logic.
357
- Fully qualified name is used for non-ambiguous PRIVATE image families.
358
- Return None if mapping can't be done, to be handled by the caller.
367
+ Attempts to resolve to a well-formed YD image name or ID, if it can.
368
+
369
+ Argument 'image_name_or_id' can take one of the following forms:
370
+ - Any image family or group YDID (returned unchanged)
371
+ - Strings prefixed or not prefixed with 'yd/' (will be added if required)
372
+ - Strings post-fixed or not post-fixed with '/latest' (will be removed)
373
+ - Any standalone image-family-name
374
+ - Any namespace/image-family-name combination
375
+ - Any image-family-name/image-group-name combination
376
+ - Any namespace/image-family-name/image-group-name combination
377
+
378
+ The call will attempt to resolve the image into its fully qualified
379
+ name if 'always_return_id' is false:
380
+ - yd/namespace/image-family-name or
381
+ - yd/namespace/image-family-name/image-group-name
382
+
383
+ If the resolved image is PUBLIC or 'always_return_id' is True,
384
+ the relevant YDID will always be returned; this is enforced for
385
+ PUBLIC images.
386
+
387
+ Finally, if nothing matches, the original ID is returned. This is
388
+ likely to be a provider specific string.
359
389
  """
390
+ if image_name_or_id is None:
391
+ return None
360
392
 
361
- if image_family_name.startswith("ami-") or image_family_name.startswith(
362
- "ocid1.image."
363
- ):
364
- # We can identify AWS AMIs and OCI OCIDs, and return immediately
365
- return image_family_name
393
+ # Already a matching YDID?
394
+ if get_ydid_type(image_name_or_id) in [
395
+ YDIDType.IMAGE_FAMILY,
396
+ YDIDType.IMAGE_GROUP,
397
+ YDIDType.IMAGE,
398
+ ]:
399
+ return image_name_or_id
366
400
 
367
- original_image_family_name = image_family_name
401
+ original_image_name_or_id = image_name_or_id
368
402
 
369
- # Remove leading 'yd/' prefix if necessary
370
- image_family_name = (
371
- image_family_name[3:]
372
- if image_family_name.startswith("yd/")
373
- else image_family_name
403
+ # Remove a leading 'yd/' prefix; will be reinstated later if required
404
+ image_name_or_id = (
405
+ image_name_or_id[3:] if image_name_or_id.startswith("yd/") else image_name_or_id
374
406
  )
375
407
 
376
- # Handle image group names of form namespace/image-family-name/image-group-name
377
- split_name = image_family_name.split("/")
378
- if len(split_name) == 3:
379
- try:
380
- namespace, name = split_namespace_and_name(
381
- f"{split_name[0]}/{split_name[1]}"
382
- )
383
- return client.images_client.get_image_group_by_name(
384
- namespace=namespace, family_name=name, group_name=split_name[2]
385
- ).id
386
- except:
387
- return
388
-
389
- namespace, name = split_namespace_and_name(image_family_name)
390
- if_search = MachineImageFamilySearch(
391
- familyName=name,
392
- namespaces=None if namespace in [None, ""] else [namespace],
393
- includePublic=True,
408
+ # Remove "/latest"; this is redundant/implied for YD image groups
409
+ image_name_or_id = (
410
+ image_name_or_id[:-7]
411
+ if image_name_or_id.endswith("/latest")
412
+ else image_name_or_id
394
413
  )
395
- search_client: SearchClient = client.images_client.get_image_families(if_search)
396
414
 
397
- try:
398
- image_families: List[MachineImageFamilySummary] = search_client.list_all()
399
- except Exception as e:
400
- if "MissingPermissionException" in str(e) and "IMAGE_READ" in str(e):
401
- # IMAGE_READ permission (globally, or for this namespace) is absent;
402
- # abandon lookup and return the original image family name
403
- print_log(
404
- f"Note: failed to resolve image name '{original_image_family_name}' "
405
- "due to lack of 'IMAGE_READ' permission; using original name"
415
+ def _replaced(return_val: str, is_ydid: bool = False):
416
+ """
417
+ Helper function to report the replacement.
418
+ """
419
+ if report_substitutions and return_val != original_image_name_or_id:
420
+ msg = f"{return_val}" if is_ydid else f"'{return_val}'"
421
+ print_log(f"Replaced Images ID '{original_image_name_or_id}' with {msg}")
422
+ return return_val
423
+
424
+ split_name = image_name_or_id.split("/")
425
+ image_family_summaries = get_image_family_summaries(client) # All namespaces
426
+
427
+ # Search for image name (only) matches
428
+ if len(split_name) == 1:
429
+ matching_image_families = [
430
+ ifs for ifs in image_family_summaries if ifs.name == split_name[0]
431
+ ]
432
+ if len(matching_image_families) > 1:
433
+ namespaces = [ifs.namespace for ifs in matching_image_families]
434
+ raise Exception(
435
+ f"Ambiguous Images ID '{original_image_name_or_id}': please "
436
+ f"specify a namespace from: {', '.join(namespaces)}"
406
437
  )
407
- return original_image_family_name
408
- else:
409
- raise
438
+ elif len(matching_image_families) == 1:
439
+ if (
440
+ matching_image_families[0].access == ImageAccess.PUBLIC
441
+ or always_return_id
442
+ ):
443
+ return _replaced(matching_image_families[0].id, True)
444
+ else:
445
+ return _replaced(
446
+ f"yd/{matching_image_families[0].namespace}/"
447
+ f"{matching_image_families[0].name}"
448
+ )
410
449
 
411
- # Partial names will match, so filter for exact matches only
412
- image_families = [
413
- img_family for img_family in image_families if img_family.name == name
414
- ]
450
+ # Search for namespace/family_name matches, *or* family_name/group_name matches
451
+ if len(split_name) == 2:
452
+ # namespace/family-name match
415
453
 
416
- # No matches
417
- if len(image_families) == 0:
418
- return
454
+ # This will be tidied up when the Application can
455
+ # query its properties
456
+ if len(image_family_summaries) == 0: # Global search didn't work
457
+ image_family_summaries = get_image_family_summaries(client, split_name[0])
419
458
 
420
- # It's possible to have both a PRIVATE and a PUBLIC match for the same
421
- # namespace/image_family_name. This is a corner case, but ...
422
- if len(image_families) == 2:
423
- image_families_public = [
424
- img_family
425
- for img_family in image_families
426
- if img_family.access.name == "PUBLIC"
459
+ matching_image_families = [
460
+ ifs
461
+ for ifs in image_family_summaries
462
+ if ifs.namespace == split_name[0] and ifs.name == split_name[1]
427
463
  ]
428
- image_families_private = [
429
- img_family
430
- for img_family in image_families
431
- if img_family.access.name == "PRIVATE"
432
- ]
433
- if len(image_families_public) == 1 and len(image_families_private) == 1:
434
- # Favour the PRIVATE image
435
- print_warning(
436
- f"Image Family '{name}' has both PUBLIC and PRIVATE "
437
- "variants; using the PRIVATE image family: "
438
- f"{image_families_private[0].namespace}/{image_families_private[0].name} "
439
- f"({image_families_private[0].id})"
464
+ if len(matching_image_families) == 1:
465
+ if (
466
+ matching_image_families[0].access == ImageAccess.PUBLIC
467
+ or always_return_id
468
+ ):
469
+ return _replaced(matching_image_families[0].id, True)
470
+ return _replaced(
471
+ f"yd/{matching_image_families[0].namespace}/"
472
+ f"{matching_image_families[0].name}"
440
473
  )
441
- image_families = image_families_private
442
-
443
- # Single match
444
- if len(image_families) == 1:
445
- substituted_image_family_name = (
446
- f"yd/{image_families[0].namespace}/{image_families[0].name}"
447
- )
448
474
 
449
- # If this is a PRIVATE image family, we can retain the fully-qualified
450
- # image family name instead of substituting the ID
451
- if image_families[0].access.name == "PRIVATE":
452
- if original_image_family_name != substituted_image_family_name:
453
- print_log(
454
- f"Substituting Image Family name '{original_image_family_name}' "
455
- f"with fully qualified name '{substituted_image_family_name}' "
456
- f"({image_families[0].id})"
475
+ # family-name/group-name match
476
+ matching_image_families = [
477
+ ifs for ifs in image_family_summaries if ifs.name == split_name[0]
478
+ ]
479
+ if_group_matches: List[Tuple[MachineImageFamilySummary, MachineImageGroup]] = []
480
+ for ifs in matching_image_families:
481
+ for if_group in get_image_family_groups(client, ifs.id):
482
+ if if_group.name == split_name[1]:
483
+ if_group_matches.append((ifs, if_group))
484
+ break
485
+ if len(if_group_matches) == 1:
486
+ if if_group_matches[0][0].access == ImageAccess.PUBLIC or always_return_id:
487
+ return _replaced(if_group_matches[0][1].id, True)
488
+ else:
489
+ return _replaced(
490
+ f"yd/{if_group_matches[0][0].namespace}/"
491
+ f"{if_group_matches[0][0].name}/"
492
+ f"{if_group_matches[0][1].name}"
457
493
  )
458
- return substituted_image_family_name
459
-
460
- # If PUBLIC, we need to replace with the YDID
461
- else:
462
- mid_msg = (
463
- ""
464
- if original_image_family_name == substituted_image_family_name
465
- else f"('{substituted_image_family_name}') "
494
+ if len(if_group_matches) > 1:
495
+ namespaces = [match[0].namespace for match in if_group_matches]
496
+ raise Exception(
497
+ f"Ambiguous image-family/image-group '{original_image_name_or_id}': "
498
+ f"please specify a namespace from: {', '.join(namespaces)}"
466
499
  )
467
- print_log(
468
- f"Substituting Image Family name '{original_image_family_name}' {mid_msg}"
469
- f"with ID {image_families[0].id}"
470
- )
471
- return image_families[0].id
472
-
473
- # Multiple matches
474
- matches = [
475
- f"{img_fam.namespace}/{img_fam.name} [{img_fam.access.name}] ({img_fam.id})"
476
- for img_fam in image_families
477
- ]
478
500
 
479
- raise Exception(
480
- f"Ambiguous Image Family name '{name}': "
481
- f"{matches}. "
482
- "Please specify a namespace. Note: PRIVATE image family is selected "
483
- "over PUBLIC if namespace/name are identical."
484
- )
501
+ # Search for names of form 'namespace/image-family-name/image-group-name'
502
+ # (the platform prevents duplicates)
503
+ if len(split_name) == 3:
485
504
 
486
-
487
- def clear_image_family_search_cache():
488
- """
489
- Clear the cache of Image Family name searches.
490
- """
491
- find_image_family_or_group_id_by_name.cache_clear()
505
+ # This will be tidied up when the Application can
506
+ # query its properties
507
+ if len(image_family_summaries) == 0: # Global search didn't work
508
+ image_family_summaries = get_image_family_summaries(client, split_name[0])
509
+
510
+ for ifs in image_family_summaries:
511
+ if ifs.namespace == split_name[0] and ifs.name == split_name[1]:
512
+ for ig in get_image_family_groups(client, ifs.id):
513
+ if ig.name == split_name[2]:
514
+ if ifs.access == ImageAccess.PUBLIC or always_return_id:
515
+ return _replaced(ig.id, True)
516
+ else:
517
+ return _replaced(f"yd/{image_name_or_id}")
518
+ else:
519
+ raise Exception(
520
+ "Image family found, but no matching image "
521
+ f"group for '{original_image_name_or_id}'"
522
+ )
523
+
524
+ # Finally, fall through and return the unchanged, original ID string
525
+ return original_image_name_or_id
492
526
 
493
527
 
494
528
  def remove_allowances_matching_description(
@@ -631,7 +665,6 @@ def substitute_image_family_id_for_name_in_cst(
631
665
  Substitute Image Family IDs for namespace/name,
632
666
  if option is selected.
633
667
  """
634
-
635
668
  if not ARGS_PARSER.substitute_ids:
636
669
  return cst
637
670
 
@@ -664,7 +697,6 @@ def substitute_id_for_name_in_allowance(
664
697
  """
665
698
  Substitute IDs in Allowance objects.
666
699
  """
667
-
668
700
  if not ARGS_PARSER.substitute_ids:
669
701
  return allowance
670
702
 
@@ -679,7 +711,6 @@ def substitute_id_for_name_in_allowance(
679
711
  )
680
712
 
681
713
  # No processing for other allowance types
682
-
683
714
  return allowance
684
715
 
685
716
 
@@ -963,3 +994,53 @@ def get_compute_requirement_summaries(
963
994
  client.compute_client.get_compute_requirement_summaries(crs_search)
964
995
  )
965
996
  return search_client.list_all()
997
+
998
+
999
+ @lru_cache
1000
+ def get_image_family_summaries(
1001
+ client: PlatformClient,
1002
+ namespace: Optional[str] = None,
1003
+ ) -> List[MachineImageFamilySummary]:
1004
+ """
1005
+ Obtain and cache the list of image families.
1006
+ """
1007
+ # Temporarily suppress most permission errors: will be improved
1008
+ # once the application can be queried for its admissible
1009
+ # IMAGE_READ namespaces
1010
+ try:
1011
+ if_search = MachineImageFamilySearch(
1012
+ familyName=None,
1013
+ namespaces=None if namespace is None else [namespace],
1014
+ includePublic=True,
1015
+ )
1016
+ search_client: SearchClient = client.images_client.get_image_families(if_search)
1017
+ return search_client.list_all()
1018
+ except Exception as e:
1019
+ if namespace is not None and "MissingPermissionException" in str(e):
1020
+ # Caching will prevent this warning appearing multiple times
1021
+ print_log(
1022
+ "Warning: Possible 'IMAGE_READ' permission missing if "
1023
+ f"'{namespace}' is meant as an Image namespace?"
1024
+ )
1025
+ pass
1026
+
1027
+ return []
1028
+
1029
+
1030
+ @lru_cache
1031
+ def get_image_family_groups(
1032
+ client: PlatformClient, image_family_id: str
1033
+ ) -> List[MachineImageGroup]:
1034
+ """
1035
+ Obtain and cache the list of image groups for an image family.
1036
+ """
1037
+ return client.images_client.get_image_family_by_id(image_family_id).imageGroups
1038
+
1039
+
1040
+ def clear_image_caches():
1041
+ """
1042
+ Clear the image caches.
1043
+ """
1044
+ find_image_name_or_id.cache_clear()
1045
+ get_image_family_summaries.cache_clear()
1046
+ get_image_family_groups.cache_clear()
@@ -16,6 +16,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
16
16
 
17
17
  from rich.console import Console, Theme
18
18
  from rich.highlighter import JSONHighlighter, RegexHighlighter
19
+ from rich.markup import escape
19
20
  from tabulate import tabulate
20
21
  from yellowdog_client import PlatformClient
21
22
  from yellowdog_client.common.json import Json
@@ -192,7 +193,7 @@ def print_simple(
192
193
  if ARGS_PARSER.quiet and override_quiet is False:
193
194
  return
194
195
 
195
- CONSOLE.print(log_message)
196
+ CONSOLE.print(escape(log_message))
196
197
 
197
198
 
198
199
  def print_log(
@@ -211,7 +212,7 @@ def print_log(
211
212
  print(print_string(log_message, no_fill=no_fill), flush=True)
212
213
  return
213
214
 
214
- CONSOLE.print(print_string(log_message, no_fill=no_fill))
215
+ CONSOLE.print(escape(print_string(log_message, no_fill=no_fill)))
215
216
 
216
217
 
217
218
  def print_error(error_obj: Union[Exception, str]):
@@ -222,7 +223,7 @@ def print_error(error_obj: Union[Exception, str]):
222
223
  print(print_string(f"Error: {error_obj}"), flush=True)
223
224
  return
224
225
 
225
- CONSOLE_ERR.print(print_string(f"Error: {error_obj}"), style=ERROR_STYLE)
226
+ CONSOLE_ERR.print(escape(print_string(f"Error: {error_obj}")), style=ERROR_STYLE)
226
227
 
227
228
 
228
229
  def print_warning(
@@ -241,7 +242,8 @@ def print_warning(
241
242
  return
242
243
 
243
244
  CONSOLE.print(
244
- print_string(f"Warning: {warning}", no_fill=no_fill), style=WARNING_STYLE
245
+ escape(print_string(f"Warning: {warning}", no_fill=no_fill)),
246
+ style=WARNING_STYLE,
245
247
  )
246
248
 
247
249
 
@@ -280,7 +282,7 @@ def print_table_core(table: str):
280
282
  if ARGS_PARSER.no_format or table.count("\n") > MAX_LINES_COLOURED_FORMATTING:
281
283
  print(table, flush=True)
282
284
  else:
283
- CONSOLE_TABLE.print(table)
285
+ CONSOLE_TABLE.print(escape(table))
284
286
 
285
287
 
286
288
  def get_type_name(obj: Item) -> str:
@@ -1098,9 +1100,9 @@ def print_json(
1098
1100
 
1099
1101
  else:
1100
1102
  if with_final_comma:
1101
- CONSOLE_JSON.print(json_string, end=",\n", soft_wrap=True)
1103
+ CONSOLE_JSON.print(escape(json_string), end=",\n", soft_wrap=True)
1102
1104
  else:
1103
- CONSOLE_JSON.print(json_string, soft_wrap=True)
1105
+ CONSOLE_JSON.print(escape(json_string), soft_wrap=True)
1104
1106
 
1105
1107
  if ARGS_PARSER.output_file is not None: # Also output to a nominated file
1106
1108
  print_to_file(
@@ -10,7 +10,7 @@ from yellowdog_client import PlatformClient
10
10
  from yellowdog_cli.utils.config_types import ConfigWorkerPool
11
11
  from yellowdog_cli.utils.entity_utils import (
12
12
  find_compute_requirement_template_id_by_name,
13
- find_image_family_or_group_id_by_name,
13
+ find_image_name_or_id,
14
14
  split_namespace_and_name,
15
15
  )
16
16
  from yellowdog_cli.utils.load_config import CONFIG_FILE_DIR
@@ -85,16 +85,13 @@ def get_template_id(client: PlatformClient, template_id_or_name: str) -> str:
85
85
  if get_ydid_type(template_id_or_name) == YDIDType.COMPUTE_REQUIREMENT_TEMPLATE:
86
86
  return template_id_or_name
87
87
 
88
- # If this is a fully qualified CRT name, allow server-side lookup
89
- namespace, name = split_namespace_and_name(template_id_or_name)
90
- if namespace is not None:
91
- return template_id_or_name
92
-
93
88
  template_id = find_compute_requirement_template_id_by_name(
94
89
  client=client, name=template_id_or_name
95
90
  )
96
91
  if template_id is None:
97
- return template_id_or_name # Return the original input
92
+ raise Exception(
93
+ f"Compute Requirement Template '{template_id_or_name}' not found"
94
+ )
98
95
 
99
96
  print_log(
100
97
  f"Substituting Compute Requirement Template name '{template_id_or_name}'"
@@ -103,28 +100,10 @@ def get_template_id(client: PlatformClient, template_id_or_name: str) -> str:
103
100
  return template_id
104
101
 
105
102
 
106
- def get_image_family_id(client: PlatformClient, image_family_id_or_name: str) -> str:
103
+ def get_image_id(client: PlatformClient, image_name_or_id: str) -> str:
107
104
  """
108
- Check if 'image_id_or_name' looks like a valid IF ID; if not,
109
- assume it's an IF name and perform a lookup.
105
+ This function was simplified, hence the pass-through call for now.
110
106
  """
111
- if get_ydid_type(image_family_id_or_name) in [
112
- YDIDType.IMAGE_FAMILY,
113
- YDIDType.IMAGE_GROUP,
114
- YDIDType.IMAGE,
115
- ]:
116
- return image_family_id_or_name
117
-
118
- image_family_id = find_image_family_or_group_id_by_name(
119
- client=client, image_family_name=image_family_id_or_name
107
+ return find_image_name_or_id(
108
+ client=client, image_name_or_id=image_name_or_id, always_return_id=True
120
109
  )
121
-
122
- # If a specific image (e.g., an AMI) has been supplied, we'll get here
123
- # and will return the original value. This does incur the cost of
124
- # listing image families, and there's a small chance of a collision
125
- # between a supplied specific image and an Image Family name.
126
-
127
- if image_family_id is None:
128
- return image_family_id_or_name # Return the original input
129
-
130
- return image_family_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-python-examples
3
- Version: 8.1.3
3
+ Version: 8.1.5
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  License-Expression: Apache-2.0
@@ -26,7 +26,7 @@ Requires-Dist: requests
26
26
  Requires-Dist: rich==13.9.4
27
27
  Requires-Dist: tabulate>=0.9.0
28
28
  Requires-Dist: toml
29
- Requires-Dist: yellowdog-sdk>=11.5.0
29
+ Requires-Dist: yellowdog-sdk>=11.6.0
30
30
  Provides-Extra: jsonnet
31
31
  Requires-Dist: jsonnet; extra == "jsonnet"
32
32
  Provides-Extra: cloudwizard
@@ -5,7 +5,7 @@ requests
5
5
  rich==13.9.4
6
6
  tabulate>=0.9.0
7
7
  toml
8
- yellowdog-sdk>=11.5.0
8
+ yellowdog-sdk>=11.6.0
9
9
 
10
10
  [cloudwizard]
11
11
  boto3
@@ -1 +0,0 @@
1
- __version__ = "8.1.3"