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.
- {yellowdog_python_examples-7.18.4/yellowdog_python_examples.egg-info → yellowdog_python_examples-7.18.6}/PKG-INFO +2 -2
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/README.md +54 -15
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/requirements.txt +1 -1
- yellowdog_python_examples-7.18.6/yellowdog_cli/__init__.py +1 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/create.py +52 -6
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/list.py +66 -30
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/remove.py +10 -1
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/show.py +21 -4
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/submit.py +0 -3
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/args.py +21 -11
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/csv_data.py +5 -4
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/load_config.py +8 -32
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/misc_utils.py +32 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/printing.py +24 -13
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/settings.py +3 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6/yellowdog_python_examples.egg-info}/PKG-INFO +2 -2
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/requires.txt +1 -1
- yellowdog_python_examples-7.18.4/yellowdog_cli/__init__.py +0 -1
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/LICENSE +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/PYPI_README.md +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/pyproject.toml +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/setup.cfg +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_create_remove.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_demos.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_dryruns.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_entrypoints.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_gui.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_list.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_objects.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_variable_processing.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/abort.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/admin.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/boost.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cancel.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cloudwizard.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/compare.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/delete.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/download.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/follow.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/format_json.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/hold.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/instantiate.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/jsonnet2json.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/provision.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/resize.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/shutdown.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/start.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/terminate.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/upload.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/__init__.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/check_imports.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_aws.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_aws_types.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_azure.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_common.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/cloudwizard_gcp.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/compact_json.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/config_types.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/entity_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/follow_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/interactive.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/items.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/load_resources.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/property_names.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/provision_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/rich_console_input_fixed.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/start_hold_common.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/submit_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/type_check.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/upload_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/validate_properties.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/variables.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/wrapper.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/ydid_utils.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/version.py +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/SOURCES.txt +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/dependency_links.txt +0 -0
- {yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_python_examples.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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:
|
|
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
|
|
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
|
-
|
|
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`
|
|
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 --
|
|
2004
|
-
yd-list --
|
|
2005
|
-
yd-list --
|
|
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`.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "7.18.6"
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/create.py
RENAMED
|
@@ -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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
[
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
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(
|
|
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
|
-
|
|
962
|
-
|
|
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
|
-
|
|
990
|
-
|
|
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():
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/remove.py
RENAMED
|
@@ -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
|
|
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
|
|
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.
|
|
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=(
|
|
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:
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/submit.py
RENAMED
|
@@ -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 (
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/args.py
RENAMED
|
@@ -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
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
1141
|
+
print_to_file("[", ARGS_PARSER.output_file)
|
|
1127
1142
|
|
|
1128
|
-
for index, (object_,
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "7.18.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_create_remove.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/tests/test_entrypoints.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/abort.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/admin.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/boost.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cancel.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/cloudwizard.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/compare.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/delete.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/download.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/follow.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/format_json.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/instantiate.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/jsonnet2json.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/provision.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/resize.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/shutdown.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/start.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/terminate.py
RENAMED
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/items.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/utils/wrapper.py
RENAMED
|
File without changes
|
|
File without changes
|
{yellowdog_python_examples-7.18.4 → yellowdog_python_examples-7.18.6}/yellowdog_cli/version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|