yellowdog-python-examples 7.17.0__tar.gz → 7.17.2__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.17.0/yellowdog_python_examples.egg-info → yellowdog_python_examples-7.17.2}/PKG-INFO +1 -1
  2. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/README.md +29 -23
  3. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_create_remove.py +21 -0
  4. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_dryruns.py +0 -13
  5. yellowdog_python_examples-7.17.2/yellowdog_cli/__init__.py +1 -0
  6. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/create.py +35 -13
  7. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/list.py +20 -1
  8. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/provision.py +39 -22
  9. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/remove.py +4 -0
  10. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/args.py +11 -0
  11. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/config_types.py +2 -1
  12. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/entity_utils.py +31 -4
  13. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/items.py +2 -0
  14. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/load_config.py +1 -0
  15. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/load_resources.py +2 -0
  16. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/printing.py +27 -0
  17. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/property_names.py +2 -0
  18. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/settings.py +1 -1
  19. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2/yellowdog_python_examples.egg-info}/PKG-INFO +1 -1
  20. yellowdog_python_examples-7.17.0/yellowdog_cli/__init__.py +0 -1
  21. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/LICENSE +0 -0
  22. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/PYPI_README.md +0 -0
  23. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/pyproject.toml +0 -0
  24. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/requirements.txt +0 -0
  25. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/setup.cfg +0 -0
  26. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_demos.py +0 -0
  27. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_entrypoints.py +0 -0
  28. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_gui.py +0 -0
  29. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_list.py +0 -0
  30. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_objects.py +0 -0
  31. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/tests/test_variable_processing.py +0 -0
  32. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/abort.py +0 -0
  33. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/admin.py +0 -0
  34. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/boost.py +0 -0
  35. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/cancel.py +0 -0
  36. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/cloudwizard.py +0 -0
  37. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/compare.py +0 -0
  38. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/delete.py +0 -0
  39. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/download.py +0 -0
  40. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/follow.py +0 -0
  41. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/format_json.py +0 -0
  42. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/hold.py +0 -0
  43. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/instantiate.py +0 -0
  44. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/jsonnet2json.py +0 -0
  45. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/resize.py +0 -0
  46. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/show.py +0 -0
  47. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/shutdown.py +0 -0
  48. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/start.py +0 -0
  49. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/submit.py +0 -0
  50. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/terminate.py +0 -0
  51. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/upload.py +0 -0
  52. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/__init__.py +0 -0
  53. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/check_imports.py +0 -0
  54. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
  55. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
  56. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
  57. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
  58. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
  59. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/compact_json.py +0 -0
  60. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/csv_data.py +0 -0
  61. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/follow_utils.py +0 -0
  62. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/interactive.py +0 -0
  63. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/misc_utils.py +0 -0
  64. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/provision_utils.py +0 -0
  65. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
  66. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/start_hold_common.py +0 -0
  67. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/submit_utils.py +0 -0
  68. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/type_check.py +0 -0
  69. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/upload_utils.py +0 -0
  70. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/validate_properties.py +0 -0
  71. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/variables.py +0 -0
  72. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/wrapper.py +0 -0
  73. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/utils/ydid_utils.py +0 -0
  74. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_cli/version.py +0 -0
  75. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
  76. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
  77. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
  78. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/yellowdog_python_examples.egg-info/requires.txt +0 -0
  79. {yellowdog_python_examples-7.17.0 → yellowdog_python_examples-7.17.2}/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.17.0
3
+ Version: 7.17.2
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
@@ -277,7 +277,7 @@ The configuration file has three possible sections:
277
277
  2. A `workRequirement` section that defines the properties of Work Requirements to be submitted to the YellowDog platform.
278
278
  3. A `workerPool` section that defines the properties of Provisioned Worker Pools to be created using the YellowDog platform.
279
279
 
280
- There is a documented template TOML file provided in [config.toml.template](config.toml.template), containing the main properties that can be configured.
280
+ There is a documented template TOML file provided in [config-template.toml](config-template.toml), containing the main properties that can be configured.
281
281
 
282
282
  The name of the configuration file can be supplied in three different ways:
283
283
 
@@ -1642,28 +1642,29 @@ The `templateId` property can use either the YellowDog ID ('YDID') for the Compu
1642
1642
 
1643
1643
  The following properties are available:
1644
1644
 
1645
- | Property | Description | Default |
1646
- |:--------------------------|:---------------------------------------------------------------------------------------------------------------------|:------------------------|
1647
- | `idleNodeTimeout` | The timeout in minutes after which an idle node will be shut down. Set this to `0` to disable the timeout. | `5.0` |
1648
- | `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` |
1649
- | `imagesId` | The image ID, Image Family ID, or Image Family name to use when booting instances. | |
1650
- | `instanceTags` | The dictionary of instance tags to apply to the instances. Tag names must be lower case. | |
1651
- | `maintainInstanceCount` | Only used when instantiating Compute Requirements; attempt to maintain the requested number of instances. | `False` |
1652
- | `maxNodes` | The maximum number of nodes to which the Worker Pool can be scaled up. | `1` |
1653
- | `metricsEnabled` | Whether to enable performance metrics for nodes in the Worker Pool | `false` |
1654
- | `minNodes` | The minimum number of nodes to which the Worker Pool can be scaled down. | `0` |
1655
- | `name` | The name of the Worker Pool. | Automatically Generated |
1656
- | `nodeBootTimeout` | The time in minutes allowed for a node to boot and register with the platform, otherwise it will be terminated. | `10.0` |
1657
- | `requirementTag` | The tag to apply to the Compute Requirement. | `tag` set in `common` |
1658
- | `targetInstanceCount` | The initial number of nodes to create in the Worker Pool. | `1` |
1659
- | `templateId` | The YellowDog Compute Requirement Template ID or name to use for provisioning. (**Required**) | No default provided |
1660
- | `userData` | User Data to be supplied to instances on boot. | |
1661
- | `userDataFile` | As above, but read the User Data from the filename supplied in this property. | |
1662
- | `userDataFiles` | As above, but create the User Data by concatenating the contents of the list of filenames supplied in this property. | |
1663
- | `workerPoolData` | The name of a file containing a JSON specification of a Worker Pool. | |
1664
- | `workerTag` | The Worker Tag to publish for the each of the Workers on the node(s). | |
1665
- | `workersPerNode` | The number of Workers to establish on each node in the Worker Pool. | `1` |
1666
- | `workersPerVCPU` | The number of Workers to establish per vCPU on each node in the Worker Pool. (Overrides `workersPerNode`.) | |
1645
+ | Property | Description | Default |
1646
+ |:------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|
1647
+ | `idleNodeTimeout` | The timeout in minutes after which an idle node will be shut down. Set this to `0` to disable the timeout. | `5.0` |
1648
+ | `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` |
1649
+ | `imagesId` | The image ID, Image Family ID, or Image Family name to use when booting instances. | |
1650
+ | `instanceTags` | The dictionary of instance tags to apply to the instances. Tag names must be lower case. | |
1651
+ | `maintainInstanceCount` | Only used when instantiating Compute Requirements; attempt to maintain the requested number of instances. | `False` |
1652
+ | `maxNodes` | The maximum number of nodes to which the Worker Pool can be scaled up. | `1` |
1653
+ | `metricsEnabled` | Whether to enable performance metrics for nodes in the Worker Pool | `false` |
1654
+ | `minNodes` | The minimum number of nodes to which the Worker Pool can be scaled down. | `0` |
1655
+ | `name` | The name of the Worker Pool. | Automatically Generated |
1656
+ | `nodeBootTimeout` | The time in minutes allowed for a node to boot and register with the platform, otherwise it will be terminated. | `10.0` |
1657
+ | `requirementTag` | The tag to apply to the Compute Requirement. | `tag` set in `common` |
1658
+ | `targetInstanceCount` | The initial number of nodes to create in the Worker Pool. | `1` |
1659
+ | `templateId` | The YellowDog Compute Requirement Template ID or name to use for provisioning. (**Required**) | No default provided |
1660
+ | `userData` | User Data to be supplied to instances on boot. | |
1661
+ | `userDataFile` | As above, but read the User Data from the filename supplied in this property. | |
1662
+ | `userDataFiles` | As above, but create the User Data by concatenating the contents of the list of filenames supplied in this property. | |
1663
+ | `workerPoolData` | The name of a file containing a JSON specification of a Worker Pool. | |
1664
+ | `workerTag` | The Worker Tag to publish for the each of the Workers on the node(s). | |
1665
+ | `workersPerNode` | The number of Workers to establish on each node in the Worker Pool. | `1` |
1666
+ | `workersPerVCPU` | The number of Workers to establish per vCPU on each node in the Worker Pool. (Overrides `workersPerNode`.) | |
1667
+ | `workersCustomCommand` | The number of Workers to establish on each node in the Worker Pool, determined by a command run on the node. (Overrides `workersPerNode`.) | |
1667
1668
 
1668
1669
  ## Using Textual Names instead of IDs for Compute Requirement Templates and Image Families
1669
1670
 
@@ -1896,6 +1897,9 @@ The commands **yd-create** and **yd-remove** allow the creation, update and remo
1896
1897
  - String Attribute Definitions
1897
1898
  - Numeric Attribute Definitions
1898
1899
  - Namespace Policies
1900
+ - Groups
1901
+ - Applications
1902
+ - Users (update only)
1899
1903
 
1900
1904
  ## Overview of Operation
1901
1905
 
@@ -1962,6 +1966,8 @@ When using the `yd-create` and `yd-remove` commands, note that an additional pro
1962
1966
  - `"NumericAttributeDefinition"`
1963
1967
  - `"NamespacePolicy"`
1964
1968
  - `"Group"`
1969
+ - `"Application"`
1970
+ - `"User"`
1965
1971
 
1966
1972
  ## Generating Resource Specifications
1967
1973
 
@@ -74,3 +74,24 @@ class TestCreateRemove:
74
74
  assert result.exit_code == 0
75
75
  result = shell(f"yd-remove -y {resources}")
76
76
  assert result.exit_code == 0
77
+
78
+ def test_group(self):
79
+ resources = f"{RESOURCE_DIR}/group.json"
80
+ result = shell(f"yd-create -y {resources}")
81
+ assert result.exit_code == 0
82
+ result = shell(f"yd-remove -y {resources}")
83
+ assert result.exit_code == 0
84
+
85
+ def test_application(self):
86
+ resources = f"{RESOURCE_DIR}/application.json"
87
+ result = shell(f"yd-create -y {resources}")
88
+ assert result.exit_code == 0
89
+ result = shell(f"yd-remove -y {resources}")
90
+ assert result.exit_code == 0
91
+
92
+ def test_user(self):
93
+ resources = f"{RESOURCE_DIR}/user.json"
94
+ result = shell(f"yd-create -y {resources}")
95
+ assert result.exit_code == 0
96
+ result = shell(f"yd-remove -y {resources}")
97
+ assert result.exit_code == 0
@@ -64,10 +64,6 @@ class TestDemoDryRuns:
64
64
  result = shell(f"cd {DEMO_DIR}/montecarlo && {CMD_SEQ}")
65
65
  assert result.exit_code == 0
66
66
 
67
- def test_ansys(self):
68
- result = shell(f"cd {DEMO_DIR}/ansys && {CMD_SEQ}")
69
- assert result.exit_code == 0
70
-
71
67
  # Tests run from outside the demo directories
72
68
  def test_bash_out(self):
73
69
  demo_name = "bash"
@@ -186,12 +182,3 @@ class TestDemoDryRuns:
186
182
  f" {demo_name}/config.toml"
187
183
  )
188
184
  assert result.exit_code == 0
189
-
190
- def test_ansys_out(self):
191
- demo_name = "ansys"
192
- result = shell(
193
- f"cd {DEMO_DIR} && yd-provision -D -c {demo_name}/config.toml && yd-submit"
194
- f" -D -c {demo_name}/config.toml && yd-instantiate -D -c"
195
- f" {demo_name}/config.toml"
196
- )
197
- assert result.exit_code == 0
@@ -0,0 +1 @@
1
+ __version__ = "7.17.2"
@@ -25,6 +25,7 @@ from yellowdog_client.model import (
25
25
  CloudProvider,
26
26
  Group,
27
27
  ImageOsType,
28
+ InternalUser,
28
29
  Keyring,
29
30
  MachineImage,
30
31
  MachineImageFamily,
@@ -39,7 +40,9 @@ from yellowdog_client.model import (
39
40
  from yellowdog_client.model.exceptions import InvalidRequestException
40
41
 
41
42
  from yellowdog_cli.utils.entity_utils import (
43
+ clear_application_caches,
42
44
  clear_compute_source_template_cache,
45
+ clear_group_caches,
43
46
  clear_image_family_search_cache,
44
47
  find_compute_requirement_template_id_by_name,
45
48
  find_compute_source_template_id_by_name,
@@ -831,7 +834,6 @@ def _get_model_object(class_name: str, resource: Dict, **kwargs):
831
834
  Return a populated YellowDog model object for the resource.
832
835
  Discard unexpected keywords.
833
836
  """
834
-
835
837
  def _patch_aws_fleet_enums():
836
838
  if isinstance(model_object, AwsFleetComputeSource):
837
839
  try:
@@ -1017,6 +1019,10 @@ def create_group(resource: Dict):
1017
1019
 
1018
1020
  current_role_ids = {role.role.id for role in group.roles}
1019
1021
 
1022
+ if current_role_ids == new_role_ids:
1023
+ print_log("No Role additions or deletions required")
1024
+ return
1025
+
1020
1026
  role_ids_to_remove = current_role_ids - new_role_ids
1021
1027
  for role_id in role_ids_to_remove:
1022
1028
  CLIENT.account_client.remove_role_from_group(group.id, role_id)
@@ -1041,6 +1047,7 @@ def create_group(resource: Dict):
1041
1047
  _get_model_object(RN_ADD_GROUP_REQUEST, resource)
1042
1048
  )
1043
1049
  print_log(f"Created Group '{group.name}' ({group.id})")
1050
+ clear_group_caches()
1044
1051
  update_roles(group)
1045
1052
 
1046
1053
  def update_group(group_id: str):
@@ -1095,6 +1102,10 @@ def create_application(resource: Dict):
1095
1102
  group.id for group in get_application_groups(CLIENT, app.id)
1096
1103
  }
1097
1104
 
1105
+ if current_group_ids == new_group_ids:
1106
+ print_log("No Group additions or deletions required")
1107
+ return
1108
+
1098
1109
  group_ids_to_remove = current_group_ids - new_group_ids
1099
1110
  for group_id in group_ids_to_remove:
1100
1111
  CLIENT.account_client.remove_application_from_group(group_id, app.id)
@@ -1128,6 +1139,7 @@ def create_application(resource: Dict):
1128
1139
  app = app_response.application
1129
1140
  print_log(f"Created Application '{app.name}' ({app.id})")
1130
1141
  show_key_and_secret(app_response.apiKey)
1142
+ clear_application_caches()
1131
1143
  update_groups(app)
1132
1144
 
1133
1145
  def update_application(app_id: str):
@@ -1159,10 +1171,9 @@ def create_application(resource: Dict):
1159
1171
 
1160
1172
  def update_user(resource: Dict):
1161
1173
  """
1162
- Update a user. Will also add or remove groups specified
1163
- by their names or IDs.
1174
+ Update a user (specified by name or ID). Will also add or remove
1175
+ groups specified by their names or IDs.
1164
1176
  """
1165
-
1166
1177
  name = resource.get(PROP_NAME)
1167
1178
  id = resource.get(PROP_ID)
1168
1179
 
@@ -1178,11 +1189,11 @@ def update_user(resource: Dict):
1178
1189
  # Convert group names to IDs
1179
1190
  new_group_ids = set()
1180
1191
  for group_name in groups:
1181
- app_id = get_group_id_by_name(CLIENT, group_name)
1182
- if app_id is None:
1192
+ group_id = get_group_id_by_name(CLIENT, group_name)
1193
+ if group_id is None:
1183
1194
  print_warning(f"Group name '{group_name}' not found ... ignoring")
1184
1195
  else:
1185
- new_group_ids.add(app_id)
1196
+ new_group_ids.add(group_id)
1186
1197
 
1187
1198
  def update_groups(user: User):
1188
1199
  """
@@ -1193,12 +1204,16 @@ def update_user(resource: Dict):
1193
1204
 
1194
1205
  current_group_ids = {group.id for group in get_user_groups(CLIENT, user.id)}
1195
1206
 
1207
+ if current_group_ids == new_group_ids:
1208
+ print_log("No Group additions or deletions required")
1209
+ return
1210
+
1196
1211
  group_ids_to_remove = current_group_ids - new_group_ids
1197
1212
  for group_id in group_ids_to_remove:
1198
1213
  CLIENT.account_client.remove_user_from_group(group_id, user.id)
1199
1214
  print_log(
1200
1215
  f"Removed Group '{get_group_name_by_id(CLIENT, group_id)}' "
1201
- f"from User ({group_id})"
1216
+ f"({group_id})"
1202
1217
  )
1203
1218
 
1204
1219
  group_ids_to_add = new_group_ids - current_group_ids
@@ -1206,13 +1221,16 @@ def update_user(resource: Dict):
1206
1221
  CLIENT.account_client.add_user_to_group(group_id, user.id)
1207
1222
  print_log(
1208
1223
  f"Added Group '{get_group_name_by_id(CLIENT, group_id)}' "
1209
- f"to User ({group_id})"
1224
+ f"({group_id})"
1210
1225
  )
1211
1226
 
1212
- # Main logic: try name then ID if present
1227
+ # Main logic: try name then ID if present; check for ID match
1213
1228
  user = None
1214
1229
  if name is not None:
1215
1230
  user = get_user_by_name_or_id(CLIENT, name)
1231
+ if user is not None and id is not None:
1232
+ if user.id != id:
1233
+ raise Exception(f"User name '{name}' and supplied ID do not match")
1216
1234
  if user is None and id is not None:
1217
1235
  user = get_user_by_name_or_id(CLIENT, id)
1218
1236
 
@@ -1221,11 +1239,15 @@ def update_user(resource: Dict):
1221
1239
  f"User '{name}' not found; Users cannot be created using "
1222
1240
  "the CLI, please use the YellowDog Portal"
1223
1241
  )
1224
- elif groups is not None:
1225
- print_log(f"Updating Groups for User '{name}' ({user.id})")
1242
+ return
1243
+
1244
+ username = user.username if isinstance(user, InternalUser) else user.name
1245
+
1246
+ if groups is not None:
1247
+ print_log(f"Updating Groups for User '{username}' ({user.id})")
1226
1248
  update_groups(user)
1227
1249
  else:
1228
- print_log(f"Nothing to do for User '{name}' ({user.id})")
1250
+ print_log(f"Nothing to do for User '{username}' ({user.id})")
1229
1251
 
1230
1252
 
1231
1253
  # Entry point
@@ -34,6 +34,7 @@ from yellowdog_client.model import (
34
34
  NodeSearch,
35
35
  NodeStatus,
36
36
  ObjectDetail,
37
+ PermissionDetail,
37
38
  Role,
38
39
  Task,
39
40
  TaskGroup,
@@ -131,6 +132,8 @@ def main():
131
132
  list_groups()
132
133
  elif ARGS_PARSER.roles:
133
134
  list_roles()
135
+ elif ARGS_PARSER.permissions:
136
+ list_permissions()
134
137
 
135
138
 
136
139
  def check_for_valid_option() -> bool:
@@ -151,8 +154,9 @@ def check_for_valid_option() -> bool:
151
154
  ARGS_PARSER.namespace_storage_configurations,
152
155
  ARGS_PARSER.nodes,
153
156
  ARGS_PARSER.object_paths,
154
- ARGS_PARSER.source_templates,
157
+ ARGS_PARSER.permissions,
155
158
  ARGS_PARSER.roles,
159
+ ARGS_PARSER.source_templates,
156
160
  ARGS_PARSER.task_groups,
157
161
  ARGS_PARSER.tasks,
158
162
  ARGS_PARSER.users,
@@ -964,6 +968,21 @@ def list_roles():
964
968
  print_yd_object(selected_users)
965
969
 
966
970
 
971
+ def list_permissions():
972
+ """
973
+ List all permissions in the account.
974
+ """
975
+ permissions: List[PermissionDetail] = CLIENT.account_client.list_permissions()
976
+ permissions.sort(key=lambda permission: permission.name)
977
+
978
+ if not ARGS_PARSER.details:
979
+ print_numbered_object_list(CLIENT, permissions, object_type_name="Permission")
980
+ return
981
+
982
+ for permission in select(CLIENT, permissions, object_type_name="Permission"):
983
+ print_yd_object(permission)
984
+
985
+
967
986
  def get_autoscaling_capacity(namespace: str) -> Dict:
968
987
  """
969
988
  Get the current autoscaling values for a namespace.
@@ -16,6 +16,7 @@ from yellowdog_client.model import (
16
16
  AutoShutdown,
17
17
  ComputeRequirementTemplateUsage,
18
18
  NodeWorkerTarget,
19
+ NodeWorkerTargetType,
19
20
  ProvisionedWorkerPoolProperties,
20
21
  )
21
22
 
@@ -176,6 +177,23 @@ def create_worker_pool_from_json(wp_json_file: str) -> None:
176
177
  # provisionedProperties insertions
177
178
  provisioned_properties = wp_data["provisionedProperties"]
178
179
 
180
+ # Three options for the number of workers per node
181
+ if CONFIG_WP.workers_custom_command is not None:
182
+ create_node_workers = {
183
+ "customTargetCommand": CONFIG_WP.workers_custom_command,
184
+ "targetType": "CUSTOM",
185
+ }
186
+ elif CONFIG_WP.workers_per_vcpu is not None:
187
+ create_node_workers = {
188
+ "targetCount": CONFIG_WP.workers_per_vcpu,
189
+ "targetType": "PER_VCPU",
190
+ }
191
+ else: # Default option
192
+ create_node_workers = {
193
+ "targetCount": CONFIG_WP.workers_per_node,
194
+ "targetType": "PER_NODE",
195
+ }
196
+
179
197
  for key, value in [
180
198
  (WORKER_TAG, CONFIG_WP.worker_tag),
181
199
  (
@@ -208,20 +226,7 @@ def create_worker_pool_from_json(wp_json_file: str) -> None:
208
226
  else {"enabled": False}
209
227
  ),
210
228
  ),
211
- (
212
- "createNodeWorkers",
213
- (
214
- {
215
- "targetCount": CONFIG_WP.workers_per_node,
216
- "targetType": "PER_NODE",
217
- }
218
- if CONFIG_WP.workers_per_vcpu is None
219
- else {
220
- "targetCount": CONFIG_WP.workers_per_vcpu,
221
- "targetType": "PER_VCPU",
222
- }
223
- ),
224
- ),
229
+ ("createNodeWorkers", create_node_workers),
225
230
  ("metricsEnabled", CONFIG_WP.metrics_enabled),
226
231
  ]:
227
232
  if provisioned_properties.get(key) is None and value is not None:
@@ -313,7 +318,11 @@ def create_worker_pool_from_toml():
313
318
  )
314
319
 
315
320
  # Establish the number of Workers to create
316
- if CONFIG_WP.workers_per_vcpu is not None:
321
+ if CONFIG_WP.workers_custom_command is not None:
322
+ node_workers = NodeWorkerTarget.per_custom_command(
323
+ CONFIG_WP.workers_custom_command
324
+ )
325
+ elif CONFIG_WP.workers_per_vcpu is not None:
317
326
  node_workers = NodeWorkerTarget.per_vcpus(CONFIG_WP.workers_per_vcpu)
318
327
  else:
319
328
  node_workers = NodeWorkerTarget.per_node(CONFIG_WP.workers_per_node)
@@ -325,13 +334,21 @@ def create_worker_pool_from_toml():
325
334
  )
326
335
 
327
336
  # Create the Worker Pool
328
- print_log(
329
- f"Provisioning {CONFIG_WP.target_instance_count:,d} node(s) "
330
- f"with {node_workers.targetCount} worker(s) "
331
- f"{node_workers.targetType} "
332
- f"(minNodes: {CONFIG_WP.min_nodes:,d}, "
333
- f"maxNodes: {CONFIG_WP.max_nodes:,d})"
334
- )
337
+ if node_workers.targetType == NodeWorkerTargetType.CUSTOM:
338
+ print_log(
339
+ f"Provisioning {CONFIG_WP.target_instance_count:,d} node(s) "
340
+ f"with a custom number of workers per node "
341
+ f"(minNodes: {CONFIG_WP.min_nodes:,d}, "
342
+ f"maxNodes: {CONFIG_WP.max_nodes:,d})"
343
+ )
344
+ else:
345
+ print_log(
346
+ f"Provisioning {CONFIG_WP.target_instance_count:,d} node(s) "
347
+ f"with {node_workers.targetCount} worker(s) "
348
+ f"{node_workers.targetType} "
349
+ f"(minNodes: {CONFIG_WP.min_nodes:,d}, "
350
+ f"maxNodes: {CONFIG_WP.max_nodes:,d})"
351
+ )
335
352
  batches: List[WPBatch] = _allocate_nodes_to_batches(
336
353
  CONFIG_WP.compute_requirement_batch_size,
337
354
  CONFIG_WP.target_instance_count,
@@ -19,6 +19,8 @@ from yellowdog_client.model import (
19
19
  )
20
20
 
21
21
  from yellowdog_cli.utils.entity_utils import (
22
+ clear_application_caches,
23
+ clear_group_caches,
22
24
  find_compute_requirement_template_id_by_name,
23
25
  find_compute_source_template_id_by_name,
24
26
  get_application_id_by_name,
@@ -521,6 +523,7 @@ def remove_group(resource: Dict):
521
523
  try:
522
524
  CLIENT.account_client.delete_group(group_id)
523
525
  print_log(f"Removed Group '{group_name}' ({group_id})")
526
+ clear_group_caches()
524
527
  except Exception as e:
525
528
  print_error(f"Unable to delete Group '{group_name}' ({group_id}): {e}")
526
529
 
@@ -543,6 +546,7 @@ def remove_application(resource: Dict):
543
546
  try:
544
547
  CLIENT.account_client.delete_application(app_id)
545
548
  print_log(f"Removed Application '{app_name}' ({app_id})")
549
+ clear_application_caches()
546
550
  except Exception as e:
547
551
  print_error(f"Unable to delete Application '{app_name}' ({app_id}): {e}")
548
552
 
@@ -651,6 +651,12 @@ class CLIParser:
651
651
  required=False,
652
652
  help="list roles",
653
653
  )
654
+ parser.add_argument(
655
+ "--permissions",
656
+ action="store_true",
657
+ required=False,
658
+ help="list permissions",
659
+ )
654
660
  parser.add_argument(
655
661
  "--output-file",
656
662
  type=str,
@@ -1551,6 +1557,11 @@ class CLIParser:
1551
1557
  def roles(self) -> Optional[bool]:
1552
1558
  return self.args.roles
1553
1559
 
1560
+ @property
1561
+ @allow_missing_attribute
1562
+ def permissions(self) -> Optional[bool]:
1563
+ return self.args.permissions
1564
+
1554
1565
  @property
1555
1566
  @allow_missing_attribute
1556
1567
  def show_keyring_passwords(self) -> Optional[bool]:
@@ -102,5 +102,6 @@ class ConfigWorkerPool:
102
102
  user_data_files: Optional[List[str]] = None
103
103
  worker_pool_data_file: Optional[str] = None
104
104
  worker_tag: Optional[str] = None
105
- workers_per_vcpu: Optional[int] = None
105
+ workers_custom_command: Optional[str] = None
106
106
  workers_per_node: int = 1
107
+ workers_per_vcpu: Optional[int] = None
@@ -574,8 +574,11 @@ def get_role_id_by_name(client: PlatformClient, role_name: str) -> Optional[str]
574
574
  if get_ydid_type(role_name) == YDIDType.ROLE:
575
575
  return role_name
576
576
 
577
- for role in get_all_roles(client):
578
- if role_name == role.name:
577
+ role_search = RoleSearch(name=role_name)
578
+ search_client: SearchClient = client.account_client.get_roles(role_search)
579
+
580
+ for role in search_client.list_all():
581
+ if role.name == role_name:
579
582
  return role.id
580
583
 
581
584
  return None
@@ -606,8 +609,11 @@ def get_all_roles(client: PlatformClient) -> List[RoleSummary]:
606
609
  @lru_cache
607
610
  def get_group_id_by_name(client: PlatformClient, group_name: str) -> Optional[str]:
608
611
  """
609
- Get a group's ID by its name.
612
+ Get a group's ID by its name. Accept IDs and return unchanged.
610
613
  """
614
+ if get_ydid_type(group_name) == YDIDType.GROUP:
615
+ return group_name
616
+
611
617
  group_search = GroupSearch(name=group_name)
612
618
  search_client: SearchClient = client.account_client.get_groups(group_search)
613
619
  group_summaries: List[GroupSummary] = search_client.list_all()
@@ -641,6 +647,15 @@ def get_all_groups(client: PlatformClient) -> List[GroupSummary]:
641
647
  return search_client.list_all()
642
648
 
643
649
 
650
+ def clear_group_caches():
651
+ """
652
+ Clear the group caches.
653
+ """
654
+ get_all_groups.cache_clear()
655
+ get_group_name_by_id.cache_clear()
656
+ get_group_id_by_name.cache_clear()
657
+
658
+
644
659
  @lru_cache
645
660
  def get_all_applications(client: PlatformClient) -> List[Application]:
646
661
  """
@@ -656,8 +671,11 @@ def get_all_applications(client: PlatformClient) -> List[Application]:
656
671
  @lru_cache
657
672
  def get_application_id_by_name(client: PlatformClient, app_name: str) -> Optional[str]:
658
673
  """
659
- Get an application ID by its name.
674
+ Get an application ID by its name. Accept IDs and return unchanged.
660
675
  """
676
+ if get_ydid_type(app_name) == YDIDType.APPLICATION:
677
+ return app_name
678
+
661
679
  for app in get_all_applications(client):
662
680
  if app.name == app_name:
663
681
  return app.id
@@ -665,6 +683,14 @@ def get_application_id_by_name(client: PlatformClient, app_name: str) -> Optiona
665
683
  return None
666
684
 
667
685
 
686
+ def clear_application_caches():
687
+ """
688
+ Clear the application caches.
689
+ """
690
+ get_all_applications.cache_clear()
691
+ get_application_id_by_name.cache_clear()
692
+
693
+
668
694
  def get_application_groups(client: PlatformClient, app_id: str) -> List[GroupSummary]:
669
695
  """
670
696
  Get the groups to which an application belongs.
@@ -679,6 +705,7 @@ def get_user_groups(client: PlatformClient, user_id: str) -> List[GroupSummary]:
679
705
  return client.account_client.get_user_groups(user_id).list_all()
680
706
 
681
707
 
708
+ @lru_cache
682
709
  def get_user_by_name_or_id(
683
710
  client: PlatformClient, user_name_or_id: str
684
711
  ) -> Optional[User]:
@@ -20,6 +20,7 @@ from yellowdog_client.model import (
20
20
  NamespaceStorageConfiguration,
21
21
  Node,
22
22
  ObjectPath,
23
+ PermissionDetail,
23
24
  ProvisionedWorkerPool,
24
25
  Role,
25
26
  Task,
@@ -50,6 +51,7 @@ Item = TypeVar(
50
51
  NamespaceStorageConfiguration,
51
52
  Node,
52
53
  ObjectPath,
54
+ PermissionDetail,
53
55
  ProvisionedWorkerPool,
54
56
  Role,
55
57
  Task,
@@ -430,6 +430,7 @@ def load_config_worker_pool() -> ConfigWorkerPool:
430
430
  user_data_files=wp_section.get(USERDATAFILES, None),
431
431
  worker_pool_data_file=worker_pool_data_file,
432
432
  worker_tag=worker_tag,
433
+ workers_custom_command=wp_section.get(WORKERS_CUSTOM_COMMAND, None),
433
434
  workers_per_vcpu=workers_per_vcpu,
434
435
  workers_per_node=int(wp_section.get(WORKERS_PER_NODE, 1)),
435
436
  )
@@ -21,6 +21,7 @@ from yellowdog_cli.utils.settings import (
21
21
  RN_SOURCE_TEMPLATE,
22
22
  RN_STORAGE_CONFIGURATION,
23
23
  RN_STRING_ATTRIBUTE_DEFINITION,
24
+ RN_USER,
24
25
  )
25
26
  from yellowdog_cli.utils.variables import (
26
27
  load_json_file_with_variable_substitutions,
@@ -113,6 +114,7 @@ def _resequence_resources(
113
114
  RN_CONFIGURED_POOL,
114
115
  RN_GROUP,
115
116
  RN_APPLICATION,
117
+ RN_USER,
116
118
  ]
117
119
 
118
120
  try:
@@ -41,6 +41,7 @@ from yellowdog_client.model import (
41
41
  Node,
42
42
  ObjectDetail,
43
43
  ObjectPath,
44
+ PermissionDetail,
44
45
  ProvisionedWorkerPool,
45
46
  ProvisionedWorkerPoolProperties,
46
47
  Role,
@@ -230,6 +231,7 @@ TYPE_MAP = {
230
231
  NamespacePolicy: "Namespace Policy",
231
232
  Node: "Node",
232
233
  ObjectPath: "Object Path",
234
+ PermissionDetail: "Permission",
233
235
  ProvisionedWorkerPool: "Provisioned Worker Pool",
234
236
  Task: "Task",
235
237
  TaskGroup: "Task Group",
@@ -831,6 +833,29 @@ def roles_table(
831
833
  return headers, table
832
834
 
833
835
 
836
+ def permissions_table(
837
+ permissions: List[PermissionDetail],
838
+ ) -> (List[str], List[List]):
839
+ headers = [
840
+ "#",
841
+ "Name",
842
+ "Description",
843
+ "Includes",
844
+ ]
845
+ table = []
846
+ for index, permission in enumerate(permissions):
847
+ includes = ", ".join(sorted([x for x in permission.includes]))
848
+ table.append(
849
+ [
850
+ index + 1,
851
+ permission.name,
852
+ permission.title,
853
+ includes,
854
+ ]
855
+ )
856
+ return headers, table
857
+
858
+
834
859
  def print_numbered_object_list(
835
860
  client: PlatformClient,
836
861
  objects: List[Union[Item, str, Dict]],
@@ -899,6 +924,8 @@ def print_numbered_object_list(
899
924
  headers, table = groups_table(objects)
900
925
  elif isinstance(objects[0], Role):
901
926
  headers, table = roles_table(objects)
927
+ elif isinstance(objects[0], PermissionDetail):
928
+ headers, table = permissions_table(objects)
902
929
  else:
903
930
  table = []
904
931
  for index, obj in enumerate(objects):
@@ -96,6 +96,7 @@ VARIABLES = "variables" # Dictionary
96
96
  VCPUS = "vcpus" # List of two Floats
97
97
  VERIFY_AT_START = "verifyAtStart" # List of Strings
98
98
  VERIFY_WAIT = "verifyWait" # List of Strings
99
+ WORKERS_CUSTOM_COMMAND = "workersCustomCommand" # String
99
100
  WORKERS_PER_NODE = "workersPerNode" # Integer
100
101
  WORKERS_PER_VCPU = "workersPerVCPU" # Integer
101
102
  WORKER_POOL_DATA_FILE = "workerPoolData" # String
@@ -202,6 +203,7 @@ ALL_KEYS = [
202
203
  VCPUS,
203
204
  VERIFY_AT_START,
204
205
  VERIFY_WAIT,
206
+ WORKERS_CUSTOM_COMMAND,
205
207
  WORKERS_PER_NODE,
206
208
  WORKERS_PER_VCPU,
207
209
  WORKER_POOL_DATA_FILE,
@@ -82,7 +82,7 @@ HIGHLIGHTED_STATES = [
82
82
  r"(?P<starved>STARVED)",
83
83
  r"(?P<transitioning>CONFIGURING)",
84
84
  r"(?P<transitioning>DOWNLOADING)",
85
- r"(?P<transitioning>LATE)",
85
+ # r"(?P<transitioning>LATE$)",
86
86
  r"(?P<transitioning>NEW)",
87
87
  r"(?P<transitioning>PROVISIONING)",
88
88
  r"(?P<transitioning>STOPPING)",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yellowdog-python-examples
3
- Version: 7.17.0
3
+ Version: 7.17.2
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
@@ -1 +0,0 @@
1
- __version__ = "7.17.0"