yellowdog-python-examples 7.18.4__tar.gz → 7.18.6__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 (79) hide show
  1. {yellowdog_python_examples-7.18.4/yellowdog_python_examples.egg-info → yellowdog_python_examples-7.18.6}/PKG-INFO +2 -2
  2. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/README.md +54 -15
  3. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/requirements.txt +1 -1
  4. yellowdog_python_examples-7.18.6/yellowdog_cli/__init__.py +1 -0
  5. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/create.py +52 -6
  6. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/list.py +66 -30
  7. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/remove.py +10 -1
  8. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/show.py +21 -4
  9. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/submit.py +0 -3
  10. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/args.py +21 -11
  11. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/csv_data.py +5 -4
  12. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/load_config.py +8 -32
  13. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/misc_utils.py +32 -0
  14. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/printing.py +24 -13
  15. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/settings.py +3 -0
  16. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6/yellowdog_python_examples.egg-info}/PKG-INFO +2 -2
  17. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/requires.txt +1 -1
  18. yellowdog_python_examples-7.18.4/yellowdog_cli/__init__.py +0 -1
  19. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/LICENSE +0 -0
  20. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/PYPI_README.md +0 -0
  21. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/pyproject.toml +0 -0
  22. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/setup.cfg +0 -0
  23. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_create_remove.py +0 -0
  24. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_demos.py +0 -0
  25. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_dryruns.py +0 -0
  26. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_entrypoints.py +0 -0
  27. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_gui.py +0 -0
  28. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_list.py +0 -0
  29. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_objects.py +0 -0
  30. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_variable_processing.py +0 -0
  31. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/abort.py +0 -0
  32. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/admin.py +0 -0
  33. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/boost.py +0 -0
  34. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cancel.py +0 -0
  35. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cloudwizard.py +0 -0
  36. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/compare.py +0 -0
  37. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/delete.py +0 -0
  38. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/download.py +0 -0
  39. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/follow.py +0 -0
  40. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/format_json.py +0 -0
  41. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/hold.py +0 -0
  42. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/instantiate.py +0 -0
  43. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/jsonnet2json.py +0 -0
  44. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/provision.py +0 -0
  45. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/resize.py +0 -0
  46. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/shutdown.py +0 -0
  47. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/start.py +0 -0
  48. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/terminate.py +0 -0
  49. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/upload.py +0 -0
  50. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/__init__.py +0 -0
  51. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/check_imports.py +0 -0
  52. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
  53. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
  54. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
  55. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
  56. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
  57. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/compact_json.py +0 -0
  58. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/config_types.py +0 -0
  59. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/entity_utils.py +0 -0
  60. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/follow_utils.py +0 -0
  61. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/interactive.py +0 -0
  62. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/items.py +0 -0
  63. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/load_resources.py +0 -0
  64. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/property_names.py +0 -0
  65. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/provision_utils.py +0 -0
  66. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
  67. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/start_hold_common.py +0 -0
  68. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/submit_utils.py +0 -0
  69. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/type_check.py +0 -0
  70. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/upload_utils.py +0 -0
  71. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/validate_properties.py +0 -0
  72. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/variables.py +0 -0
  73. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/wrapper.py +0 -0
  74. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/ydid_utils.py +0 -0
  75. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/version.py +0 -0
  76. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
  77. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
  78. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
  79. {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/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: 7.18.4
3
+ Version: 7.18.6
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  Project-URL: Homepage, https://github.com/yellowdog/python-examples
@@ -20,7 +20,7 @@ Requires-Dist: requests
20
20
  Requires-Dist: rich==13.9.4
21
21
  Requires-Dist: tabulate>=0.9.0
22
22
  Requires-Dist: toml
23
- Requires-Dist: yellowdog-sdk>=9.3.3
23
+ Requires-Dist: yellowdog-sdk>=9.4.0
24
24
  Provides-Extra: jsonnet
25
25
  Requires-Dist: jsonnet; extra == "jsonnet"
26
26
  Provides-Extra: cloudwizard
@@ -84,7 +84,8 @@
84
84
  * [Resource Removal](#resource-removal)
85
85
  * [Resource Matching](#resource-matching)
86
86
  * [Resource Specification Definitions](#resource-specification-definitions)
87
- * [Generating Resource Specifications](#generating-resource-specifications)
87
+ * [Generating Resource Specifications using yd-list](#generating-resource-specifications-using-yd-list)
88
+ * [Usage Scenario: Moving or Copying Resources to a New Namespace](#usage-scenario-moving-or-copying-resources-to-a-new-namespace)
88
89
  * [Preprocessing Resource Specifications](#preprocessing-resource-specifications)
89
90
  * [Keyrings](#keyrings)
90
91
  * [Credentials](#credentials)
@@ -131,7 +132,7 @@
131
132
  * [yd-compare](#yd-compare)
132
133
 
133
134
  <!-- Created by https://github.com/ekalinin/github-markdown-toc -->
134
- <!-- Added by: pwt, at: Mon Jul 28 21:41:59 BST 2025 -->
135
+ <!-- Added by: pwt, at: Wed Aug 6 08:55:44 BST 2025 -->
135
136
 
136
137
  <!--te-->
137
138
 
@@ -386,9 +387,11 @@ If all the required common properties are set using the command line or environm
386
387
 
387
388
  ## Support for `.env` Files
388
389
 
389
- Environment variables can also be set in a `.env` file, typically in the user's home directory. Entries in the `.env` file will not overwrite existing environment variables -- i.e., environment variables take precedence over entries in the `.env` file.
390
+ Environment variables can also be set in a `.env` file, typically in the user's home directory or the current working directory.
390
391
 
391
- Environment variables sourced from a `.env` file whose names start with `YD` will be reported on the command line. Variables that do not start with `YD` will not be reported, but they will still be applied.
392
+ Entries in the `.env` file will not overwrite existing environment variables -- i.e., environment variables take precedence over entries in the `.env` file. This precedence can be reversed by using the `--env-override` command line option.
393
+
394
+ Environment variables sourced from a `.env` file whose names start with `YD` will be reported on the command line. Variables whose names do not start with `YD` will not be reported, but they will still be applied.
392
395
 
393
396
  ## Variable Substitutions in Common Properties
394
397
 
@@ -1995,18 +1998,19 @@ When using the `yd-create` and `yd-remove` commands, note that an additional pro
1995
1998
  - `"Application"`
1996
1999
  - `"User"`
1997
2000
 
1998
- ## Generating Resource Specifications
2001
+ ## Generating Resource Specifications using `yd-list`
1999
2002
 
2000
- To generate example JSON specifications from resources already included in the platform, the `yd-list` command can be used with the `--details` option, and select the resources for which details are required. E.g.:
2003
+ To generate example JSON specifications from resources already included in the platform, the `yd-list` command can be used with the `--details`, `--substitute-ids`/`-U`, and `--strip-ids` options, and select the resources for which details are required. E.g.:
2001
2004
 
2002
2005
  ```shell
2003
- yd-list --keyrings --details
2004
- yd-list --source-templates --details
2005
- yd-list --compute-templates --details
2006
- yd-list --image-families --details
2006
+ yd-list --source-templates --details --substitute-ids --strip-ids
2007
+ yd-list --compute-templates --details --substitute-ids --strip-ids
2008
+ yd-list --image-families --details --substitute-ids --strip-ids
2007
2009
  ```
2008
2010
 
2009
- This will produce a list of resource specifications that can be copied and used directly with `yd-create` and `yd-remove`. Certain fields, such as the ID, will be ignored, with warnings. The detailed resource list can also be copied directly to an output file in addition to being displayed on the console:
2011
+ This will produce a list of resource specifications that can be copied and used directly with `yd-create` and `yd-remove`.
2012
+
2013
+ The detailed resource list can also be copied directly to an output file in addition to being displayed on the console using the `--output-file` option:
2010
2014
 
2011
2015
  ```shell
2012
2016
  yd-list yd-list --source-templates --details --output-file my-resources.json
@@ -2015,12 +2019,47 @@ yd-list yd-list --source-templates --details --output-file my-resources.json
2015
2019
  Alternatively, the `yd-show` command can be used with one or more `ydid` arguments to generate the details of each identified resource. E.g.,
2016
2020
 
2017
2021
  ```shell
2018
- yd-show -q ydid:cst:000000:cde265f8-0b17-4e0e-be1c-505174a620e4 > my-compute-source-template.json
2022
+ yd-show -q ydid:cst:000000:cde265f8-0b17-4e0e-be1c-505174a620e4 --substitute-ids --strip-ids --output-file my-compute-source-template.json
2019
2023
  ```
2020
2024
 
2021
2025
  would generate a JSON file that can be used with `yd-create` without alteration, or which could be edited.
2022
2026
 
2023
- Both `yd-list` and `yd-show` support the `--substitute-ids`/`-U` option. For Compute Requirement Template detailed output, this will substitute Compute Source Template IDs and Image Family IDs with their names, to make it easier to use the output acrosss YellowDog accounts.
2027
+ As illustrated above, both `yd-list` and `yd-show` support the `--substitute-ids`/`-U` option. For Compute Requirement Template detailed output, this will substitute Compute Source Template IDs and Image Family IDs with their names, to make it easier to reuse the outputs. For Compute Source Templates, Image Family IDs will be substituted. (Only Image Family IDs are substituted, not Image Group or Image IDs; if these are used in CRTs/CSTs, the JSON resource output may not be reusable.)
2028
+
2029
+ The `--strip-ids` option will remove any YellowDog IDs ('ydids') from the JSON output, as well as any other properties that are not required in order to use the output with `yd-create`.
2030
+
2031
+ ### Usage Scenario: Moving or Copying Resources to a New Namespace
2032
+
2033
+ In the following usage scenario, we want to move a set of resources from one namespace `ns-1`, to another `ns-2`. We'll move all compute source templates, compute requirement templates, and image families.
2034
+
2035
+ **Step 1: Capture the target resources in JSON files**
2036
+
2037
+ ```shell
2038
+ yd-list -q --compute-source-templates --namespace ns-1 --substitute-ids --strip-ids --auto-select-all --output-file csts.json
2039
+ yd-list -q --compute-requirement-templates --namespace ns-1 --substitute-ids --strip-ids --auto-select-all --output-file crts.json
2040
+ yd-list -q --image-families --namespace ns-1 --substitute-ids --strip-ids --auto-select-all --output-file ifs.json
2041
+ ```
2042
+
2043
+ **Step 2: Remove all target resources** if moving resources
2044
+
2045
+ The following will remove all target resources included in the JSON resource files **without user confirmation**. If one instead wants to **copy** the resources to the new namespace rather than move them, omit this step.
2046
+
2047
+ ```shell
2048
+ yd-remove -y csts.json crts.json ifs.json
2049
+ ```
2050
+
2051
+ **Step 3: Change the namespace in all the resources**
2052
+
2053
+ Use an editor's search and replace function, or a command line tool such as `sed` to replace all occurences of `"ns-1"` with `"ns-2`", for every `namespace` property, in each of the JSON files.
2054
+
2055
+ **Step 4: Recreate all resources in the new namespace**
2056
+
2057
+ ```shell
2058
+ yd-create -y csts.json crts.json ifs.json
2059
+ ```
2060
+
2061
+ Once the resources have been created successfully, the JSON files can be deleted (or retained for your records).
2062
+
2024
2063
 
2025
2064
  ## Preprocessing Resource Specifications
2026
2065
 
@@ -2372,7 +2411,7 @@ Namespace Policies are matched by their `namespace` property when using `yd-crea
2372
2411
 
2373
2412
  ## Groups
2374
2413
 
2375
- When creating and removing groups, a list of roles can can be supplied and the group will be created or updated with the roles specified. Roles can be identified by their names or YellowDog IDs.
2414
+ When creating and updating groups, a list of roles can can be supplied and the group will be created or updated with the roles specified. Roles can be identified by their names or YellowDog IDs.
2376
2415
 
2377
2416
  Example:
2378
2417
 
@@ -2402,7 +2441,7 @@ Example:
2402
2441
 
2403
2442
  ### Creating and Regenerating Application Keys
2404
2443
 
2405
- When Application is created its Application Key ID and Secret will be displayed (even if the `--quiet` option is used).
2444
+ When an Application is created its Application Key ID and Secret will be displayed (even if the `--quiet` option is used).
2406
2445
 
2407
2446
  When an Application is updated, the `--regenerate-app-keys` option can be used. This will invalidate the current Application key and secret, revoke any Keyring access, and generate a new key and secret which will be displayed.
2408
2447
 
@@ -5,4 +5,4 @@ requests
5
5
  rich == 13.9.4
6
6
  tabulate >= 0.9.0
7
7
  toml
8
- yellowdog-sdk >= 9.3.3
8
+ yellowdog-sdk >= 9.4.0
@@ -0,0 +1 @@
1
+ __version__ = "7.18.6"
@@ -532,7 +532,7 @@ def create_image_family(resource):
532
532
  if e.response.status_code == 404:
533
533
  # This will create the Image Family and all of its constituent
534
534
  # Image Group/Image resources
535
- image_family = CLIENT.images_client.add_image_family(image_family)
535
+ image_family = _create_image_family(image_family, fq_name)
536
536
  print_log(f"Created Machine Image Family '{fq_name}' ('{image_family.id}')")
537
537
  if ARGS_PARSER.quiet:
538
538
  print(image_family.id)
@@ -984,7 +984,7 @@ def create_namespace_policy(resource: Dict):
984
984
  return
985
985
 
986
986
  print_log(
987
- f"Created or updated Namespace Policy '{namespace_policy.namespace}' with "
987
+ f"Created or updated Namespace Policy '{namespace_policy.namespace}' with "
988
988
  f"'autoscalingMaxNodes={namespace_policy.autoscalingMaxNodes}'"
989
989
  )
990
990
 
@@ -1007,7 +1007,7 @@ def create_group(resource: Dict):
1007
1007
  for role_name in roles:
1008
1008
  role_id = get_role_id_by_name(CLIENT, role_name)
1009
1009
  if role_id is None:
1010
- print_warning(f"Role name '{role_name}' not found ... ignoring")
1010
+ print_warning(f"Role '{role_name}' not found ... ignoring")
1011
1011
  else:
1012
1012
  new_role_ids.add(role_id)
1013
1013
 
@@ -1056,6 +1056,9 @@ def create_group(resource: Dict):
1056
1056
  Helper function to update an existing group, including updating
1057
1057
  its roles.
1058
1058
  """
1059
+ if not confirmed(f"Update Group '{name}' ({group_id})?"):
1060
+ return
1061
+
1059
1062
  group: Group = CLIENT.account_client.update_group(
1060
1063
  group_id, _get_model_object(RN_UPDATE_GROUP_REQUEST, resource)
1061
1064
  )
@@ -1088,7 +1091,7 @@ def create_application(resource: Dict):
1088
1091
  for group_name in groups:
1089
1092
  app_id = get_group_id_by_name(CLIENT, group_name)
1090
1093
  if app_id is None:
1091
- print_warning(f"Group name '{group_name}' not found ... ignoring")
1094
+ print_warning(f"Group '{group_name}' not found ... ignoring")
1092
1095
  else:
1093
1096
  new_group_ids.add(app_id)
1094
1097
 
@@ -1148,6 +1151,9 @@ def create_application(resource: Dict):
1148
1151
  Helper function to update an existing application, including updating
1149
1152
  its groups.
1150
1153
  """
1154
+ if not confirmed(f"Update Application '{name}' ({app_id})?"):
1155
+ return
1156
+
1151
1157
  app: Application = CLIENT.account_client.update_application(
1152
1158
  app_id, _get_model_object(RN_UPDATE_APPLICATION_REQUEST, resource)
1153
1159
  )
@@ -1192,7 +1198,7 @@ def update_user(resource: Dict):
1192
1198
  for group_name in groups:
1193
1199
  group_id = get_group_id_by_name(CLIENT, group_name)
1194
1200
  if group_id is None:
1195
- print_warning(f"Group name '{group_name}' not found ... ignoring")
1201
+ print_warning(f"Group '{group_name}' not found ... ignoring")
1196
1202
  else:
1197
1203
  new_group_ids.add(group_id)
1198
1204
 
@@ -1203,6 +1209,9 @@ def update_user(resource: Dict):
1203
1209
  if groups is None:
1204
1210
  return
1205
1211
 
1212
+ if not confirmed(f"Update Groups for User '{username}' ({user.id})?"):
1213
+ return
1214
+
1206
1215
  current_group_ids = {group.id for group in get_user_groups(CLIENT, user.id)}
1207
1216
 
1208
1217
  if current_group_ids == new_group_ids:
@@ -1245,12 +1254,49 @@ def update_user(resource: Dict):
1245
1254
  username = user.username if isinstance(user, InternalUser) else user.name
1246
1255
 
1247
1256
  if groups is not None:
1248
- print_log(f"Updating Groups for User '{username}' ({user.id})")
1249
1257
  update_groups(user)
1250
1258
  else:
1251
1259
  print_log(f"Nothing to do for User '{username}' ({user.id})")
1252
1260
 
1253
1261
 
1262
+ def _create_image_family(
1263
+ image_family: MachineImageFamily, fq_name: str
1264
+ ) -> MachineImageFamily:
1265
+ """
1266
+ Creates a new image family. Only one image group can be added at the time of
1267
+ image family creation, so any additional image groups must be added separately.
1268
+ """
1269
+
1270
+ # Remove all except the first image group; keep the rest as a separate list
1271
+ image_groups = image_family.imageGroups
1272
+ if image_groups is not None:
1273
+ image_family.imageGroups = image_groups[:1]
1274
+ image_groups = image_groups[1:] # Remaining image groups
1275
+
1276
+ # Create the image family
1277
+ try:
1278
+ image_family = CLIENT.images_client.add_image_family(image_family)
1279
+ except Exception as e:
1280
+ raise Exception(f"Failed to create Machine Image Family '{fq_name}': {e}")
1281
+
1282
+ if image_groups is None or len(image_groups) == 0:
1283
+ return image_family
1284
+
1285
+ # Create any additional image groups
1286
+ for image_group in image_groups:
1287
+ try:
1288
+ image_group = CLIENT.images_client.add_image_group(
1289
+ image_family, image_group
1290
+ )
1291
+ except Exception as e:
1292
+ raise Exception(
1293
+ f"Failed to add Machine Image Group '{image_group.name}' to "
1294
+ f"Image Family '{fq_name}': {e}"
1295
+ )
1296
+
1297
+ return image_family
1298
+
1299
+
1254
1300
  # Entry point
1255
1301
  if __name__ == "__main__":
1256
1302
  main()
@@ -75,11 +75,16 @@ from yellowdog_cli.utils.printing import (
75
75
  sorted_objects,
76
76
  )
77
77
  from yellowdog_cli.utils.settings import (
78
+ PROP_GROUPS,
79
+ PROP_RESOURCE,
78
80
  RN_ALLOWANCE,
81
+ RN_APPLICATION,
82
+ RN_GROUP,
79
83
  RN_IMAGE_FAMILY,
80
84
  RN_KEYRING,
81
85
  RN_NUMERIC_ATTRIBUTE_DEFINITION,
82
86
  RN_REQUIREMENT_TEMPLATE,
87
+ RN_ROLE,
83
88
  RN_SOURCE_TEMPLATE,
84
89
  RN_STRING_ATTRIBUTE_DEFINITION,
85
90
  )
@@ -104,7 +109,7 @@ def main():
104
109
  ARGS_PARSER.details = True
105
110
 
106
111
  if ARGS_PARSER.details and ARGS_PARSER.strip_ids:
107
- print_log("Omitting YellowDog IDs from detailed JSON objects")
112
+ print_log("Omitting YellowDog IDs (etc.) from detailed JSON objects")
108
113
 
109
114
  if ARGS_PARSER.output_file and ARGS_PARSER.details:
110
115
  if exists(ARGS_PARSER.output_file):
@@ -584,7 +589,7 @@ def list_compute_requirement_templates():
584
589
  CLIENT,
585
590
  CLIENT.compute_client.get_compute_requirement_template(cr_template.id),
586
591
  ),
587
- RN_REQUIREMENT_TEMPLATE,
592
+ {PROP_RESOURCE: RN_REQUIREMENT_TEMPLATE},
588
593
  )
589
594
  for cr_template in cr_templates
590
595
  ]
@@ -633,7 +638,7 @@ def list_compute_source_templates():
633
638
  CLIENT,
634
639
  CLIENT.compute_client.get_compute_source_template(cs_template.id),
635
640
  ),
636
- RN_SOURCE_TEMPLATE,
641
+ {PROP_RESOURCE: RN_SOURCE_TEMPLATE},
637
642
  )
638
643
  for cs_template in cs_templates
639
644
  ]
@@ -662,7 +667,7 @@ def list_keyrings():
662
667
 
663
668
  # Show details
664
669
  print_yd_object_list(
665
- [(keyring, RN_KEYRING) for keyring in select(CLIENT, keyrings)]
670
+ [(keyring, {PROP_RESOURCE: RN_KEYRING}) for keyring in select(CLIENT, keyrings)]
666
671
  )
667
672
 
668
673
 
@@ -712,7 +717,7 @@ def list_image_families():
712
717
  image_families = [
713
718
  (
714
719
  CLIENT.images_client.get_image_family_by_id(image_family_summary.id),
715
- RN_IMAGE_FAMILY,
720
+ {PROP_RESOURCE: RN_IMAGE_FAMILY},
716
721
  )
717
722
  for image_family_summary in image_family_summaries
718
723
  ]
@@ -809,7 +814,10 @@ def list_allowances():
809
814
 
810
815
  # Show details
811
816
  print_yd_object_list(
812
- [(allowance, RN_ALLOWANCE) for allowance in select(CLIENT, allowances)]
817
+ [
818
+ (allowance, {PROP_RESOURCE: RN_ALLOWANCE})
819
+ for allowance in select(CLIENT, allowances)
820
+ ]
813
821
  )
814
822
 
815
823
 
@@ -841,11 +849,13 @@ def list_attribute_definitions():
841
849
  attribute_definition_list = [
842
850
  (
843
851
  attribute,
844
- (
845
- RN_NUMERIC_ATTRIBUTE_DEFINITION
846
- if "Numeric" in attribute["type"]
847
- else RN_STRING_ATTRIBUTE_DEFINITION
848
- ),
852
+ {
853
+ PROP_RESOURCE: (
854
+ RN_NUMERIC_ATTRIBUTE_DEFINITION
855
+ if "Numeric" in attribute["type"]
856
+ else RN_STRING_ATTRIBUTE_DEFINITION
857
+ )
858
+ },
849
859
  )
850
860
  for attribute in select(
851
861
  CLIENT,
@@ -904,13 +914,20 @@ def list_users():
904
914
  print_numbered_object_list(CLIENT, users, object_type_name="User")
905
915
  return
906
916
 
907
- for selected_user in select(CLIENT, users, object_type_name="User"):
908
- add_groups_field = {
909
- "groups": [
910
- group.name for group in get_user_groups(CLIENT, selected_user.id)
911
- ]
912
- }
913
- print_yd_object(selected_user, add_fields=add_groups_field)
917
+ # Add the list of groups to the user details
918
+ print_yd_object_list(
919
+ [
920
+ (
921
+ user,
922
+ {
923
+ PROP_GROUPS: [
924
+ group.name for group in get_user_groups(CLIENT, user.id)
925
+ ]
926
+ },
927
+ )
928
+ for user in select(CLIENT, users)
929
+ ]
930
+ )
914
931
 
915
932
 
916
933
  def list_applications():
@@ -929,13 +946,22 @@ def list_applications():
929
946
  print_numbered_object_list(CLIENT, applications, object_type_name="Application")
930
947
  return
931
948
 
932
- for selected_app in select(CLIENT, applications, object_type_name="Application"):
933
- add_groups_field = {
934
- "groups": [
935
- group.name for group in get_application_groups(CLIENT, selected_app.id)
936
- ]
937
- }
938
- print_yd_object(selected_app, add_fields=add_groups_field)
949
+ # Add the list of group names for each application
950
+ print_yd_object_list(
951
+ [
952
+ (
953
+ application,
954
+ {
955
+ PROP_GROUPS: [
956
+ group.name
957
+ for group in get_application_groups(CLIENT, application.id)
958
+ ],
959
+ PROP_RESOURCE: RN_APPLICATION,
960
+ },
961
+ )
962
+ for application in select(CLIENT, applications)
963
+ ]
964
+ )
939
965
 
940
966
 
941
967
  def list_groups():
@@ -951,15 +977,24 @@ def list_groups():
951
977
  group_summaries.sort(key=lambda group: group.name if group.name is not None else "")
952
978
 
953
979
  groups: List[Group] = [
954
- CLIENT.account_client.get_group(x.id) for x in group_summaries
980
+ CLIENT.account_client.get_group(group.id) for group in group_summaries
955
981
  ]
956
982
 
957
983
  if not ARGS_PARSER.details:
958
984
  print_numbered_object_list(CLIENT, groups, object_type_name="Group")
959
985
  return
960
986
 
961
- for selected_users in select(CLIENT, groups, object_type_name="Group"):
962
- print_yd_object(selected_users)
987
+ selected_groups = select(CLIENT, groups)
988
+
989
+ # If stripping IDs, just supply the list of role names;
990
+ # subverts the type
991
+ if ARGS_PARSER.strip_ids:
992
+ for group in selected_groups:
993
+ group.roles = [group_role.role.name for group_role in group.roles]
994
+
995
+ print_yd_object_list(
996
+ [(group, {PROP_RESOURCE: RN_GROUP}) for group in selected_groups]
997
+ )
963
998
 
964
999
 
965
1000
  def list_roles():
@@ -986,8 +1021,9 @@ def list_roles():
986
1021
  print_numbered_object_list(CLIENT, roles, object_type_name="Role")
987
1022
  return
988
1023
 
989
- for selected_users in select(CLIENT, roles, object_type_name="Role"):
990
- print_yd_object(selected_users)
1024
+ print_yd_object_list(
1025
+ [(role, {PROP_RESOURCE: RN_ROLE}) for role in select(CLIENT, roles)]
1026
+ )
991
1027
 
992
1028
 
993
1029
  def list_permissions():
@@ -498,6 +498,9 @@ def remove_namespace_policy(resource: Dict):
498
498
  print_error(f"Namespace Policy '{namespace}' not found")
499
499
  return
500
500
 
501
+ if not confirmed(f"Remove Namespace Policy '{namespace}'?"):
502
+ return
503
+
501
504
  try:
502
505
  CLIENT.namespaces_client.delete_namespace_policy(namespace)
503
506
  print_log(f"Removed Namespace Policy '{namespace}'")
@@ -520,6 +523,9 @@ def remove_group(resource: Dict):
520
523
  print_warning(f"Group '{group_name}' not found")
521
524
  return
522
525
 
526
+ if not confirmed(f"Remove Group '{group_name}' ({group_id})?"):
527
+ return
528
+
523
529
  try:
524
530
  CLIENT.account_client.delete_group(group_id)
525
531
  print_log(f"Removed Group '{group_name}' ({group_id})")
@@ -543,12 +549,15 @@ def remove_application(resource: Dict):
543
549
  print_warning(f"Application '{app_name}' not found")
544
550
  return
545
551
 
552
+ if not confirmed(f"Remove Application '{app_name}' ({app_id})?"):
553
+ return
554
+
546
555
  try:
547
556
  CLIENT.account_client.delete_application(app_id)
548
557
  print_log(f"Removed Application '{app_name}' ({app_id})")
549
558
  clear_application_caches()
550
559
  except Exception as e:
551
- print_error(f"Unable to delete Application '{app_name}' ({app_id}): {e}")
560
+ print_error(f"Unable to remove Application '{app_name}' ({app_id}): {e}")
552
561
 
553
562
 
554
563
  # Entry point
@@ -8,11 +8,18 @@ from yellowdog_client.model import ConfiguredWorkerPool
8
8
 
9
9
  from yellowdog_cli.list import get_keyring
10
10
  from yellowdog_cli.utils.entity_utils import (
11
+ get_application_groups,
11
12
  substitute_ids_for_names_in_crt,
12
13
  substitute_image_family_id_for_name_in_cst,
13
14
  )
14
- from yellowdog_cli.utils.printing import print_error, print_log, print_yd_object
15
+ from yellowdog_cli.utils.printing import (
16
+ print_error,
17
+ print_log,
18
+ print_to_file,
19
+ print_yd_object,
20
+ )
15
21
  from yellowdog_cli.utils.settings import (
22
+ PROP_GROUPS,
16
23
  RESOURCE_PROPERTY_NAME,
17
24
  RN_ALLOWANCE,
18
25
  RN_APPLICATION,
@@ -36,11 +43,13 @@ def main():
36
43
  # and the 'quiet' option is enabled
37
44
  generate_json_list = len(ARGS_PARSER.yellowdog_ids) > 1 and ARGS_PARSER.quiet
38
45
 
39
- if ARGS_PARSER.details and ARGS_PARSER.strip_ids:
40
- print_log("Omitting YellowDog IDs from detailed JSON objects")
46
+ if ARGS_PARSER.strip_ids:
47
+ print_log("Omitting YellowDog IDs (etc.) from detailed JSON objects")
41
48
 
42
49
  if generate_json_list:
43
50
  print("[")
51
+ if ARGS_PARSER.output_file is not None:
52
+ print_to_file("[", ARGS_PARSER.output_file)
44
53
 
45
54
  for index, ydid in enumerate(ARGS_PARSER.yellowdog_ids):
46
55
  if generate_json_list:
@@ -53,6 +62,8 @@ def main():
53
62
 
54
63
  if generate_json_list:
55
64
  print("]")
65
+ if ARGS_PARSER.output_file is not None:
66
+ print_to_file("]", ARGS_PARSER.output_file)
56
67
 
57
68
 
58
69
  def show_details(ydid: str, initial_indent: int = 0, with_final_comma: bool = False):
@@ -210,11 +221,17 @@ def show_details(ydid: str, initial_indent: int = 0, with_final_comma: bool = Fa
210
221
 
211
222
  elif ydid_type == YDIDType.APPLICATION:
212
223
  print_log(f"Showing details of Application ID '{ydid}'")
224
+ group_names = [group.name for group in get_application_groups(CLIENT, ydid)]
213
225
  print_yd_object(
214
226
  CLIENT.account_client.get_application(ydid),
215
227
  initial_indent=initial_indent,
216
228
  with_final_comma=with_final_comma,
217
- add_fields=({RESOURCE_PROPERTY_NAME: RN_APPLICATION}),
229
+ add_fields=(
230
+ {
231
+ PROP_GROUPS: group_names,
232
+ RESOURCE_PROPERTY_NAME: RN_APPLICATION,
233
+ }
234
+ ),
218
235
  )
219
236
 
220
237
  elif ydid_type == YDIDType.USER:
@@ -23,8 +23,6 @@ from yellowdog_client.model import (
23
23
  RunSpecification,
24
24
  Task,
25
25
  TaskData,
26
- TaskErrorMatcher,
27
- TaskErrorType,
28
26
  TaskGroup,
29
27
  TaskInput,
30
28
  TaskInputSource,
@@ -56,7 +54,6 @@ from yellowdog_cli.utils.printing import (
56
54
  print_log,
57
55
  print_numbered_strings,
58
56
  print_warning,
59
- print_yd_object,
60
57
  )
61
58
  from yellowdog_cli.utils.property_names import *
62
59
  from yellowdog_cli.utils.settings import (
@@ -107,7 +107,6 @@ class CLIParser:
107
107
  required=False,
108
108
  help="disable colouring and text wrapping in command output",
109
109
  )
110
-
111
110
  parser.add_argument(
112
111
  "--quiet",
113
112
  "-q",
@@ -115,6 +114,12 @@ class CLIParser:
115
114
  required=False,
116
115
  help="suppress (non-error, non-interactive) status and progress messages",
117
116
  )
117
+ parser.add_argument(
118
+ "--env-override",
119
+ action="store_true",
120
+ required=False,
121
+ help="values in '.env' file override values in the environment",
122
+ )
118
123
 
119
124
  # Module-specific argument sets
120
125
  if not any(module in sys.argv[0] for module in ["compare"]):
@@ -663,16 +668,6 @@ class CLIParser:
663
668
  required=False,
664
669
  help="show the full JSON representation of objects",
665
670
  )
666
- parser.add_argument(
667
- "--output-file",
668
- type=str,
669
- required=False,
670
- help=(
671
- "if specified, the 'details' JSON resource listing will also be written "
672
- "to the nominated output file"
673
- ),
674
- metavar="<output-file>",
675
- )
676
671
  parser.add_argument(
677
672
  "--auto-select-all",
678
673
  action="store_true",
@@ -1194,6 +1189,16 @@ class CLIParser:
1194
1189
  "(implies '--details')"
1195
1190
  ),
1196
1191
  )
1192
+ parser.add_argument(
1193
+ "--output-file",
1194
+ type=str,
1195
+ required=False,
1196
+ help=(
1197
+ "if specified, the detailed JSON resource listing will also be written "
1198
+ "to the nominated output file"
1199
+ ),
1200
+ metavar="<output-file>",
1201
+ )
1197
1202
 
1198
1203
  if "compare" in sys.argv[0]:
1199
1204
  parser.add_argument(
@@ -1265,6 +1270,11 @@ class CLIParser:
1265
1270
  def quiet(self) -> Optional[bool]:
1266
1271
  return self.args.quiet
1267
1272
 
1273
+ @property
1274
+ @allow_missing_attribute
1275
+ def env_override(self) -> Optional[bool]:
1276
+ return self.args.env_override
1277
+
1268
1278
  @property
1269
1279
  @allow_missing_attribute
1270
1280
  def work_req_file(self) -> Optional[str]:
@@ -8,7 +8,7 @@ from ast import literal_eval
8
8
  from collections import OrderedDict
9
9
  from json import load as json_load
10
10
  from os.path import relpath
11
- from typing import Dict, List, Optional
11
+ from typing import Dict, List, Optional, Tuple
12
12
 
13
13
  from toml import load as toml_load
14
14
 
@@ -290,7 +290,7 @@ USED_FILE_INDEXES = []
290
290
 
291
291
  def get_csv_file_index(
292
292
  csv_filename: str, task_groups: List[Dict]
293
- ) -> [str, Optional[int]]:
293
+ ) -> Tuple[str, Optional[int]]:
294
294
  """
295
295
  Check if the CSV filename ends in an integer index (':<integer>'),
296
296
  or in a Task Group name (':<task_group_name>').
@@ -378,8 +378,6 @@ def csv_expand_toml_tasks(
378
378
  (config_wr.add_yd_env_vars, ADD_YD_ENV_VARS),
379
379
  (config_wr.always_upload, ALWAYS_UPLOAD),
380
380
  (config_wr.args, ARGS),
381
- (config_wr.task_data_inputs, TASK_DATA_INPUTS),
382
- (config_wr.task_data_outputs, TASK_DATA_OUTPUTS),
383
381
  (config_wr.docker_env, DOCKER_ENV),
384
382
  (config_wr.docker_options, DOCKER_OPTIONS),
385
383
  (config_wr.docker_password, DOCKER_PASSWORD),
@@ -393,8 +391,11 @@ def csv_expand_toml_tasks(
393
391
  (config_wr.outputs_optional, OUTPUTS_OPTIONAL),
394
392
  (config_wr.outputs_other, OUTPUTS_OTHER),
395
393
  (config_wr.outputs_required, OUTPUTS_REQUIRED),
394
+ (config_wr.set_task_names, SET_TASK_NAMES),
396
395
  (config_wr.task_data, TASK_DATA),
397
396
  (config_wr.task_data_file, TASK_DATA_FILE),
397
+ (config_wr.task_data_inputs, TASK_DATA_INPUTS),
398
+ (config_wr.task_data_outputs, TASK_DATA_OUTPUTS),
398
399
  (config_wr.task_group_name, TASK_GROUP_NAME), # Note: oddity
399
400
  (config_wr.task_level_timeout, TASK_LEVEL_TIMEOUT),
400
401
  (config_wr.task_name, TASK_NAME),
@@ -9,7 +9,6 @@ from pathlib import Path
9
9
  from sys import exit
10
10
  from typing import Dict
11
11
 
12
- from dotenv import dotenv_values, find_dotenv, load_dotenv
13
12
  from toml import TomlDecodeError
14
13
 
15
14
  from yellowdog_cli.utils.args import ARGS_PARSER
@@ -18,7 +17,14 @@ from yellowdog_cli.utils.config_types import (
18
17
  ConfigWorkerPool,
19
18
  ConfigWorkRequirement,
20
19
  )
21
- from yellowdog_cli.utils.misc_utils import pathname_relative_to_config_file
20
+ from yellowdog_cli.utils.misc_utils import (
21
+ load_dotenv_file,
22
+ pathname_relative_to_config_file,
23
+ )
24
+
25
+ # Load additional environment variables as early as possible
26
+ load_dotenv_file()
27
+
22
28
  from yellowdog_cli.utils.printing import print_error, print_log
23
29
  from yellowdog_cli.utils.property_names import *
24
30
  from yellowdog_cli.utils.settings import (
@@ -102,10 +108,6 @@ def load_config_common() -> ConfigCommon:
102
108
  common_section_imported.update(common_section)
103
109
  common_section = common_section_imported
104
110
 
105
- # Load extra environment variables from a .env file if it exists;
106
- # do not override existing variables (environment takes precedence)
107
- _load_dotenv()
108
-
109
111
  # Replace common section properties with command line or
110
112
  # environment variable overrides. Precedence is:
111
113
  # command line > environment variable > config file
@@ -453,29 +455,3 @@ def load_config_worker_pool() -> ConfigWorkerPool:
453
455
  except ValueError as e:
454
456
  print_error(f"Invalid type for configuration: {e}")
455
457
  exit(1)
456
-
457
-
458
- def _load_dotenv():
459
- """
460
- Load extra environment variables from a .env file if it exists.
461
- Do not override existing variables (environment takes precedence).
462
- Report on YD vars that are taken from .env.
463
- """
464
- dotenv_file = find_dotenv()
465
- if dotenv_file == "":
466
- return
467
-
468
- dotenv_yd_substitutions = [ # Find 'YD' variables
469
- f"'{key}'"
470
- for key in dotenv_values(dotenv_file).keys()
471
- if key.startswith("YD") and os.environ.get(key) is None
472
- ]
473
-
474
- if len(dotenv_yd_substitutions) > 0:
475
- print_log(
476
- f"Adding 'YD' environment variables from '.env' file '{dotenv_file}': "
477
- f"{', '.join(dotenv_yd_substitutions)}"
478
- )
479
-
480
- # Actually load the variables (including non-'YD' variables)
481
- load_dotenv(dotenv_file, override=False)
@@ -2,6 +2,7 @@
2
2
  General utility functions.
3
3
  """
4
4
 
5
+ import os
5
6
  import re
6
7
  from dataclasses import dataclass
7
8
  from datetime import datetime, timezone
@@ -9,6 +10,7 @@ from os.path import join, normpath, relpath
9
10
  from typing import List, Optional
10
11
  from urllib.parse import urlparse
11
12
 
13
+ from dotenv import dotenv_values, find_dotenv, load_dotenv
12
14
  from yellowdog_client.model import (
13
15
  ComputeRequirement,
14
16
  ConfiguredWorkerPool,
@@ -16,6 +18,8 @@ from yellowdog_client.model import (
16
18
  WorkRequirement,
17
19
  )
18
20
 
21
+ from yellowdog_cli.utils.args import ARGS_PARSER
22
+ from yellowdog_cli.utils.printing import print_log
19
23
  from yellowdog_cli.utils.settings import NAMESPACE_OBJECT_STORE_PREFIX_SEPARATOR
20
24
 
21
25
  UTCNOW = datetime.now(timezone.utc)
@@ -236,3 +240,31 @@ def format_yd_name(yd_name: str, add_prefix: bool = True) -> str:
236
240
 
237
241
  # Mustn't exceed 60 chars
238
242
  return new_yd_name[:60]
243
+
244
+
245
+ def load_dotenv_file():
246
+ """
247
+ Load extra environment variables from a .env file if it exists.
248
+ Do not override existing variables (environment takes precedence)
249
+ unless --env-override option is set.
250
+ Report on YD vars that are taken from .env.
251
+ """
252
+ dotenv_file = find_dotenv()
253
+ if dotenv_file == "":
254
+ return
255
+
256
+ dotenv_yd_substitutions = [ # Find 'YD' variables
257
+ f"'{key}'"
258
+ for key in dotenv_values(dotenv_file).keys()
259
+ if key.startswith("YD")
260
+ and (os.environ.get(key) is None or ARGS_PARSER.env_override)
261
+ ]
262
+
263
+ if len(dotenv_yd_substitutions) > 0:
264
+ print_log(
265
+ f"Adding 'YD' environment variables from '.env' file '{dotenv_file}': "
266
+ f"{', '.join(dotenv_yd_substitutions)}"
267
+ )
268
+
269
+ # Actually load the variables (including non-'YD' variables)
270
+ load_dotenv(dotenv_file, override=ARGS_PARSER.env_override)
@@ -71,10 +71,12 @@ from yellowdog_cli.utils.settings import (
71
71
  MAX_LINES_COLOURED_FORMATTING,
72
72
  MAX_TABLE_DESCRIPTION,
73
73
  NAMESPACE_OBJECT_STORE_PREFIX_SEPARATOR,
74
+ PROP_ACCESS_DELEGATES,
75
+ PROP_ADMIN_GROUP,
76
+ PROP_CREATED_BY_USER_ID,
74
77
  PROP_CREATED_TIME,
75
78
  PROP_ID,
76
79
  PROP_INSTANCE_PRICING,
77
- PROP_RESOURCE,
78
80
  PROP_SUPPORTING_RESOURCE_CREATED,
79
81
  PROP_TRAITS,
80
82
  WARNING_STYLE,
@@ -98,10 +100,13 @@ class PrintLogHighlighter(RegexHighlighter):
98
100
  r"(?P<date_time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]"
99
101
  r" [0-9][0-9]:[0-9][0-9]:[0-9][0-9])",
100
102
  r"(?P<quoted>'[a-zA-Z0-9-._=;:\/\\\[\]{}+#@$£%\^&\*\(\)~`<>?]*')",
101
- r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*)",
102
103
  r"(?P<ydid>ydid:[a-z]*:[0-9abcdef-]*)",
104
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*)",
103
105
  r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*)",
104
106
  r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*:[0-9]*)",
107
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*)",
108
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*)",
109
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*:[0-9]*)",
105
110
  r"(?P<url>(https?):((//)|(\\\\))+[\w\d:#@%/;$~_?\+-=\\\.&]*)",
106
111
  ] + HIGHLIGHTED_STATES
107
112
 
@@ -120,6 +125,9 @@ class PrintTableHighlighter(RegexHighlighter):
120
125
  r"(?P<ydid>ydid:[a-z]*:[0-9abcdef-]*)",
121
126
  r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*)",
122
127
  r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*:[0-9]*)",
128
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*)",
129
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*)",
130
+ r"(?P<ydid>ydid:[a-z]*:[0-9ABCDEF]*:[0-9ABCDEF]*:[0-9abcdef-]*:[0-9]*:[0-9]*)",
123
131
  ] + HIGHLIGHTED_STATES
124
132
 
125
133
 
@@ -227,10 +235,12 @@ def print_warning(
227
235
  TYPE_MAP = {
228
236
  AWSAvailabilityZone: "AWS Availability Zones",
229
237
  Allowance: "Allowance",
238
+ Application: "Application",
230
239
  ComputeRequirement: "Compute Requirement",
231
240
  ComputeRequirementTemplateSummary: "Compute Requirement Template",
232
241
  ComputeSourceTemplateSummary: "Compute Source Template",
233
242
  ConfiguredWorkerPool: "Configured Worker Pool",
243
+ Group: "Group",
234
244
  KeyringSummary: "Keyring",
235
245
  MachineImageFamilySummary: "Machine Image Family",
236
246
  NamespacePolicy: "Namespace Policy",
@@ -238,8 +248,10 @@ TYPE_MAP = {
238
248
  ObjectPath: "Object Path",
239
249
  PermissionDetail: "Permission",
240
250
  ProvisionedWorkerPool: "Provisioned Worker Pool",
251
+ Role: "Role",
241
252
  Task: "Task",
242
253
  TaskGroup: "Task Group",
254
+ User: "User",
243
255
  WorkRequirementSummary: "Work Requirement",
244
256
  Worker: "Worker",
245
257
  WorkerPoolSummary: "Worker Pool",
@@ -1048,7 +1060,7 @@ def print_json(
1048
1060
  CONSOLE_JSON.print(json_string, soft_wrap=True)
1049
1061
 
1050
1062
  if ARGS_PARSER.output_file is not None: # Also output to a nominated file
1051
- _print_to_file(
1063
+ print_to_file(
1052
1064
  json_string=json_string,
1053
1065
  output_file=ARGS_PARSER.output_file,
1054
1066
  with_final_comma=with_final_comma,
@@ -1081,6 +1093,9 @@ def print_yd_object(
1081
1093
  if k
1082
1094
  not in [
1083
1095
  PROP_ID,
1096
+ PROP_ACCESS_DELEGATES,
1097
+ PROP_ADMIN_GROUP,
1098
+ PROP_CREATED_BY_USER_ID,
1084
1099
  PROP_CREATED_TIME,
1085
1100
  PROP_INSTANCE_PRICING,
1086
1101
  PROP_SUPPORTING_RESOURCE_CREATED,
@@ -1111,7 +1126,7 @@ def print_yd_object(
1111
1126
 
1112
1127
 
1113
1128
  def print_yd_object_list(
1114
- objects: List[Tuple[Any, Optional[str]]], # Tuples are (object, resource_type_name)
1129
+ objects: List[Tuple[Any, Optional[Dict]]],
1115
1130
  ):
1116
1131
  """
1117
1132
  Print a JSON list of objects.
@@ -1123,24 +1138,20 @@ def print_yd_object_list(
1123
1138
  if len(objects) > 1:
1124
1139
  print("[")
1125
1140
  if ARGS_PARSER.output_file is not None:
1126
- _print_to_file("[", ARGS_PARSER.output_file)
1141
+ print_to_file("[", ARGS_PARSER.output_file)
1127
1142
 
1128
- for index, (object_, resource_type_name) in enumerate(objects):
1143
+ for index, (object_, add_fields) in enumerate(objects):
1129
1144
  print_yd_object(
1130
1145
  object_,
1131
1146
  initial_indent=2 if len(objects) > 1 else 0,
1132
1147
  with_final_comma=(True if index < len(objects) - 1 else False),
1133
- add_fields=(
1134
- {}
1135
- if resource_type_name is None
1136
- else {PROP_RESOURCE: resource_type_name}
1137
- ),
1148
+ add_fields=add_fields,
1138
1149
  )
1139
1150
 
1140
1151
  if len(objects) > 1:
1141
1152
  print("]")
1142
1153
  if ARGS_PARSER.output_file is not None:
1143
- _print_to_file("]", ARGS_PARSER.output_file)
1154
+ print_to_file("]", ARGS_PARSER.output_file)
1144
1155
 
1145
1156
 
1146
1157
  def print_worker_pool(
@@ -1501,7 +1512,7 @@ def print_event(event: str, id_type: YDIDType):
1501
1512
  FIRST_OUTPUT_TO_FILE = True # Determine whether to 'write' or 'append'
1502
1513
 
1503
1514
 
1504
- def _print_to_file(json_string: str, output_file: str, with_final_comma: bool = False):
1515
+ def print_to_file(json_string: str, output_file: str, with_final_comma: bool = False):
1505
1516
  """
1506
1517
  Dump details output to a file.
1507
1518
  """
@@ -133,7 +133,10 @@ RN_UPDATE_GROUP_REQUEST = "UpdateGroupRequest"
133
133
  RN_USER = "User"
134
134
 
135
135
  # Property Names
136
+ PROP_ACCESS_DELEGATES = "accessDelegates"
137
+ PROP_ADMIN_GROUP = "adminGroup"
136
138
  PROP_AUTOSCALING_MAX_NODES = "autoscalingMaxNodes"
139
+ PROP_CREATED_BY_USER_ID = "createdByUserId"
137
140
  PROP_CREATED_TIME = "createdTime"
138
141
  PROP_CREDENTIAL = "credential"
139
142
  PROP_CST_ID = "sourceTemplateId"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-python-examples
3
- Version: 7.18.4
3
+ Version: 7.18.6
4
4
  Summary: Python CLI commands using the YellowDog Python SDK
5
5
  Author-email: YellowDog Limited <support@yellowdog.co>
6
6
  Project-URL: Homepage, https://github.com/yellowdog/python-examples
@@ -20,7 +20,7 @@ Requires-Dist: requests
20
20
  Requires-Dist: rich==13.9.4
21
21
  Requires-Dist: tabulate>=0.9.0
22
22
  Requires-Dist: toml
23
- Requires-Dist: yellowdog-sdk>=9.3.3
23
+ Requires-Dist: yellowdog-sdk>=9.4.0
24
24
  Provides-Extra: jsonnet
25
25
  Requires-Dist: jsonnet; extra == "jsonnet"
26
26
  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>=9.3.3
8
+ yellowdog-sdk>=9.4.0
9
9
 
10
10
  [cloudwizard]
11
11
  boto3
@@ -1 +0,0 @@
1
- __version__ = "7.18.4"