From 5897569ebac7d71922c8001e1e029216af77c819 Mon Sep 17 00:00:00 2001 From: Chris Rasmussen Date: Tue, 17 Sep 2024 16:59:59 +1000 Subject: [PATCH 1/2] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 605f614..1b0e186 100755 --- a/readme.md +++ b/readme.md @@ -36,4 +36,4 @@ In addition, please also be advised that these scripts may contain code that doe **Changes will be required before these scripts can be used in production environments.** -Please see the `.disclaimer` file distributed with this repository. +See the `.disclaimer` file distributed with this repository. From 055301d4d1552cda5114532261492fc3cddc82c4 Mon Sep 17 00:00:00 2001 From: Chris Rasmussen Date: Fri, 20 Dec 2024 11:17:11 +1100 Subject: [PATCH 2/2] Add categories demos (Nikhil Saraswat) --- ...flow Documentation.postman_collection.json | 652 ++++++++++++++++++ python/v4api_sdk/categories/LICENSE | 21 + python/v4api_sdk/categories/NOTICES | 7 + python/v4api_sdk/categories/README.md | 34 + .../categories/categories_v4_workflow.py | 622 +++++++++++++++++ python/v4api_sdk/categories/requirements.txt | 4 + 6 files changed, 1340 insertions(+) create mode 100644 python/v4api_sdk/categories/Categories V4 Workflow Documentation.postman_collection.json create mode 100644 python/v4api_sdk/categories/LICENSE create mode 100644 python/v4api_sdk/categories/NOTICES create mode 100644 python/v4api_sdk/categories/README.md create mode 100755 python/v4api_sdk/categories/categories_v4_workflow.py create mode 100644 python/v4api_sdk/categories/requirements.txt diff --git a/python/v4api_sdk/categories/Categories V4 Workflow Documentation.postman_collection.json b/python/v4api_sdk/categories/Categories V4 Workflow Documentation.postman_collection.json new file mode 100644 index 0000000..e188f75 --- /dev/null +++ b/python/v4api_sdk/categories/Categories V4 Workflow Documentation.postman_collection.json @@ -0,0 +1,652 @@ +{ + "info": { + "_postman_id": "cf008f5f-dc26-4171-ae12-b360650bbbe3", + "name": "Categories V4 Workflow Documentation", + "description": "# ๐Ÿ“„ Get started here\n\nThis template contains a boilerplate for documentation that you can quickly customize and reuse.\n\n## ๐Ÿ”– How to use this template\n\n- Replace the content given brackets (()) with your API's details.\n \n- Tips are formatted in `codespan` - feel free to read and remove them.\n \n\n---\n\n`Start with a brief overview of what your API offers.`\n\nThe ((product name)) provides many API products, tools, and resources that enable you to ((add product value here)).\n\n`You can also list the APIs you offer, link to the relevant pages, or do both in this section.`\n\n## **Getting started guide**\n\n`List the steps or points required to start using your APIs. Make sure to cover everything required to reach success with your API as quickly as possible.`\n\nTo start using the ((add APIs here)), you need to -\n\n`The points given below are from The Postman API's documentation. You can reference it to write your own getting started guide.`\n\n- You must use a valid API Key to send requests to the API endpoints. You can get your API key from Postman's [integrations dashboard](https://go.postman.co/settings/me/api-keys).\n \n- The API has [rate and usage limits](https://learning.postman.com/docs/developer/postman-api/postman-api-rate-limits/).\n \n- The API only responds to HTTPS-secured communications. Any requests sent via HTTP return an HTTP 301 redirect to the corresponding HTTPS resources.\n \n- The API returns request responses in JSON format. When an API request returns an error, it is sent in the JSON response as an error key.\n \n\n## Authentication\n\n`Add details on the authorization keys/tokens required, steps that cover how to get them, and the relevant error codes.`\n\nThe ((product name)) API uses ((add your API's authorization type)) for authentication.\n\n`The details given below are from the Postman API's documentation. You can reference it to write your own authentication section.`\n\nPostman uses API keys for authentication. You can generate a Postman API key in the [API keys](https://postman.postman.co/settings/me/api-keys) section of your Postman account settings.\n\nYou must include an API key in each request to the Postman API with the X-Api-Key request header.\n\n### Authentication error response\n\nIf an API key is missing, malformed, or invalid, you will receive an HTTP 401 Unauthorized response code.\n\n## Rate and usage limits\n\n`Use this section to cover your APIs' terms of use. Include API limits, constraints, and relevant error codes, so consumers understand the permitted API usage and practices.`\n\n`The example given below is from The Postman API's documentation. Use it as a reference to write your APIs' terms of use.`\n\nAPI access rate limits apply at a per-API key basis in unit time. The limit is 300 requests per minute. Also, depending on your plan, you may have usage limits. If you exceed either limit, your request will return an HTTP 429 Too Many Requests status code.\n\nEach API response returns the following set of headers to help you identify your use status:\n\n| Header | Description |\n| --- | --- |\n| `X-RateLimit-Limit` | The maximum number of requests that the consumer is permitted to make per minute. |\n| `X-RateLimit-Remaining` | The number of requests remaining in the current rate limit window. |\n| `X-RateLimit-Reset` | The time at which the current rate limit window resets in UTC epoch seconds. |\n\n### 503 response\n\nAn HTTP `503` response from our servers indicates there is an unexpected spike in API access traffic. The server is usually operational within the next five minutes. If the outage persists or you receive any other form of an HTTP `5XX` error, [contact support](https://support.postman.com/hc/en-us/requests/new/).\n\n### **Need some help?**\n\n`Add links that customers can refer to whenever they need help.`\n\nIn case you have questions, go through our tutorials ((link to your video or help documentation here)). Or visit our FAQ page ((link to the relevant page)).\n\nOr you can check out our community forum, thereโ€™s a good chance our community has an answer for you. Visit our developer forum ((link to developer forum)) to review topics, ask questions, and learn from others.\n\n`You can also document or add links to libraries, code examples, and other resources needed to make a request.`", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "27442998" + }, + "item": [ + { + "name": "Create a Category", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"Key of the category\",\n \"value\": \"Value of the category\",\n \"description\": \"Description of the category\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories" + }, + "response": [] + }, + { + "name": "Update a Category", + "request": { + "method": "PUT", + "header": [ + { + "key": "If-Match", + "value": "ETag", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}" + }, + "response": [ + { + "name": "Update the description", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "If-Match", + "value": "ETag", + "description": "Call fetch category API and copy the Etag value from response header", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"Key of the category\",\n \"value\": \"Value of the category\",\n \"description\": \"New description of the category\",\n \"ownerUuid\": \"00000000-0000-0000-0000-000000000000\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}" + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "Update the Owner-UUID", + "originalRequest": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + }, + { + "key": "If-Match", + "value": "ETag", + "description": "Call fetch category API and copy the Etag value from response header", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"key\": \"Key of the category\",\n \"value\": \"Value of the category\",\n \"description\": \"Description of the category\",\n \"Owner-UUID\": \"New Owner-UUID of the category\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}" + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + } + ] + }, + { + "name": "Fetch a Category", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}?$mode=pretty", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories", + "{{extId}}" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + } + ] + } + }, + "response": [] + }, + { + "name": "List Categories", + "request": { + "method": "GET", + "header": [], + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories" + }, + "response": [ + { + "name": "List all categories", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "Use expansion to show associated counts", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$expand=associations", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$expand", + "value": "associations", + "description": "List all the categories with their count of associations" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "Use expansion to show detailed associations", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}?mode=pretty&$expand=detailedAssociations", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories", + "{{extId}}" + ], + "query": [ + { + "key": "mode", + "value": "pretty" + }, + { + "key": "$expand", + "value": "detailedAssociations", + "description": "List all the associations for category (extId)" + }, + { + "key": "$expand", + "value": "associations", + "description": "List all the associated kinds with respective counts for category (extId)", + "disabled": true + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "sort by key", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$orderby=key", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$orderby", + "value": "key", + "description": "sort the categories in increasing order of keys" + }, + { + "key": "$orderby", + "value": "key desc", + "description": "sort the categories in decreasing order of keys", + "disabled": true + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "List only certain attributes in the result", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$select=value,type,description", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$select", + "value": "value,type,description", + "description": "list only value, type and description attributes of categories" + }, + { + "key": "$select", + "value": "key,value", + "description": "list only key and value attributes of categories", + "disabled": true + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "filter categories matching a certain key/value content", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?mode=pretty&$filter=key eq 'example_key' and value eq 'example_value'", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "mode", + "value": "pretty" + }, + { + "key": "$filter", + "value": "key eq 'example_key' and value eq 'example_value'", + "description": "List Category with matching key and value" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "filter categories whose key or value starting with a certain string", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$filter=startswith(key, 'exam') or startswith(value, 'ex')", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$filter", + "value": "startswith(key, 'exam') or startswith(value, 'ex')", + "description": "List categories whose key is starting with 'exam' or value starting with 'ex' (it is a case-sensitive matching)" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "sort by key and value", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$orderby=key,value", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$orderby", + "value": "key,value", + "description": "sort the categories in increasing order of keys and values" + }, + { + "key": "$orderby", + "value": "key desc,value asc", + "description": "sort the categories in decreasing order of keys and increasing order of values", + "disabled": true + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "filter for user defined categories/system/internal", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$filter=type eq Prism.Config.CategoryType'USER'", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$filter", + "value": "type eq Prism.Config.CategoryType'INTERNAL'", + "description": "list INTERNAL categories only", + "disabled": true + }, + { + "key": "$filter", + "value": "type eq Prism.Config.CategoryType'SYSTEM'", + "description": "list SYSTEM defined categories only", + "disabled": true + }, + { + "key": "$filter", + "value": "type eq Prism.Config.CategoryType'USER'", + "description": "list USER defined categories only" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "use filters and expansion to show only those categories that have at least one association", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$expand=associations($filter=count ge 1)", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$expand", + "value": "associations($filter=count ge 1)", + "description": "list all those categories whose association count >= 1" + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + }, + { + "name": "use filters and expansion to show associations with a particular resourceType", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories?$mode=pretty&$expand=associations($filter=resourceType eq Prism.Config.ResourceType'VM')", + "protocol": "https", + "host": [ + "{{pc-ip}}" + ], + "port": "9440", + "path": [ + "api", + "prism", + "v4.0", + "config", + "categories" + ], + "query": [ + { + "key": "$mode", + "value": "pretty" + }, + { + "key": "$expand", + "value": "associations($filter=resourceType eq Prism.Config.ResourceType'VM')", + "description": "list all associations of resourceType = 'VM'" + }, + { + "key": "$expand", + "value": "associations($filter=resourceType eq Prism.Config.ResourceType'VM_TEMPLATE')", + "description": "list all associations of resourceType = 'VM_TEMPLATE'", + "disabled": true + } + ] + } + }, + "_postman_previewlanguage": null, + "header": null, + "cookie": [], + "body": null + } + ] + }, + { + "name": "Delete a Category", + "request": { + "method": "DELETE", + "header": [], + "url": "https://{{pc-ip}}:9440/api/prism/v4.0/config/categories/{{extId}}" + }, + "response": [] + } + ], + "auth": { + "type": "basic", + "basic": { + "password": "", + "username": "admin" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "https://farming-simulator.pstmn.io" + }, + { + "key": "extId", + "value": "", + "type": "default" + } + ] +} \ No newline at end of file diff --git a/python/v4api_sdk/categories/LICENSE b/python/v4api_sdk/categories/LICENSE new file mode 100644 index 0000000..591e9a4 --- /dev/null +++ b/python/v4api_sdk/categories/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nutanix Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/python/v4api_sdk/categories/NOTICES b/python/v4api_sdk/categories/NOTICES new file mode 100644 index 0000000..eaca11e --- /dev/null +++ b/python/v4api_sdk/categories/NOTICES @@ -0,0 +1,7 @@ +Dependencies and Licenses +------------------------- + +urllib3: MIT License, https://github.com/urllib3/urllib3/blob/main/LICENSE.txt +ntnx-clustermgmt-py-client: Nutanix Proprietary License, https://developers.nutanix.com/license +ntnx-prism-py-client: Nutanix Proprietary License, https://developers.nutanix.com/license +ntnx-vmm-py-client: Nutanix Proprietary License, https://developers.nutanix.com/license diff --git a/python/v4api_sdk/categories/README.md b/python/v4api_sdk/categories/README.md new file mode 100644 index 0000000..ea50ca3 --- /dev/null +++ b/python/v4api_sdk/categories/README.md @@ -0,0 +1,34 @@ +# Categories v4 Workflow Sample Scripts + +Code sample to demonstrate use of the Categories v4 APIs via Python client. + +Requires Prism Central 2024.3 or later and AOS 6.8 or later. + +## Usage + +- Create and activate a Python virtual environment: + + ``` + python -m venv venv + . venv/bin/activate + ``` + +- Install required packages: + + ``` + pip install -r requirements.txt + ``` +- Update the following variables in the script: + + ``` + CLUSTER_NAME = "" + ``` + Note: For updating owner_uuid of a category, provide the user's UUID in the script and it must be correct. +- Run script: + + ``` + python categories_v4_workflow.py --pc_ip --username + ``` + + Note: User will be prompted for password + diff --git a/python/v4api_sdk/categories/categories_v4_workflow.py b/python/v4api_sdk/categories/categories_v4_workflow.py new file mode 100755 index 0000000..5ec2bae --- /dev/null +++ b/python/v4api_sdk/categories/categories_v4_workflow.py @@ -0,0 +1,622 @@ +''' +This script demonstrates the usage of the Categories API in the Nutanix v4 Python SDK. +''' +import urllib3 +import ntnx_prism_py_client +import ntnx_vmm_py_client +import ntnx_vmm_py_client.models.vmm.v4.ahv.config as AhvVmConfig +import ntnx_clustermgmt_py_client +from ntnx_vmm_py_client.models.vmm.v4.ahv.config.DisassociateVmCategoriesParams import DisassociateVmCategoriesParams as AhvConfigDisassociateVmCategoriesParams +from ntnx_vmm_py_client.models.vmm.v4.ahv.config.AssociateVmCategoriesParams import AssociateVmCategoriesParams as AhvConfigAssociateVmCategoriesParams +import argparse +import getpass + +''' +suppress warnings about insecure connections +you probably shouldn't do this in production +''' +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +''' +setup our connection details for the Prism Central +''' +# Parse command line arguments +parser = argparse.ArgumentParser(description='Nutanix Categories API Workflow') +parser.add_argument('--pc_ip', required=True, help='IP address of the Prism Central') +parser.add_argument('--username', required=True, help='Username to connect to the cluster') +args = parser.parse_args() + +# Prompt user for password +password = getpass.getpass(prompt='Enter password: ') + +PC_IP = args.pc_ip +PORT = 9440 +CLUSTER_NAME = "CLUSTER-NAME" +USERNAME = args.username +PASSWORD = password + +''' +setup the categories API client +''' +def setupCategoriesApi(): + # Configure the categories client + config = ntnx_prism_py_client.Configuration() + # True or False for ssl verification + config.verify_ssl = False + # IP of the PC + config.host = PC_IP + # Port to which to connect to + config.port = PORT + # Max retry attempts while reconnecting on a loss of connection + config.max_retry_attempts = 3 + # Backoff factor to use during retry attempts + config.backoff_factor = 3 + # UserName to connect to the cluster + config.username = USERNAME + # Password to connect to the cluster + config.password = PASSWORD + client_ = ntnx_prism_py_client.ApiClient(configuration=config) + # Create an instance of CategoriesApi class + categories_api = ntnx_prism_py_client.CategoriesApi( + api_client=client_) + return categories_api + +''' +setup the vmm API client +''' +def setupVmmApi(): + # Configure the vmm client + config = ntnx_vmm_py_client.Configuration() + # True or False for ssl verification + config.verify_ssl = False + config.host = PC_IP + config.port = PORT + # Max retry attempts while reconnecting on a loss of connection + config.max_retry_attempts = 3 + # Max retry attempts while reconnecting on a loss of connection + config.backoff_factor = 3 + # UserName to connect to the cluster + config.username = USERNAME + # Password to connect to the cluster + config.password = PASSWORD + client = ntnx_vmm_py_client.ApiClient(configuration=config) + # Create an instance of VmApi class + vm_api = ntnx_vmm_py_client.VmApi(api_client=client) + return vm_api + +''' +setup the clustermgmt API client +''' +def setupClusterApi(): + # Configure the clustermgmt client + config = ntnx_clustermgmt_py_client.Configuration() + # IP of the PC + config.host = PC_IP + # Port to which to connect to + config.port = PORT + # UserName to connect to the cluster + config.username = USERNAME + # Password to connect to the cluster + config.password = PASSWORD + config.debug = True + # True or False for ssl verification + config.verify_ssl = False + clustermgmt_api_client = ntnx_clustermgmt_py_client.ApiClient( + configuration=config) + # Create an instance of ClustersApi class + cluster_api = ntnx_clustermgmt_py_client.ClustersApi( + api_client=clustermgmt_api_client) + return cluster_api + + +def setup(): + cluster_api = setupClusterApi() + vm_api = setupVmmApi() + categories_api = setupCategoriesApi() + return cluster_api, vm_api, categories_api + +''' +Method to get the cluster UUID by name +''' +def GetClusterUuidByName(cluster_api, cluster_name): + try: + api_response = cluster_api.list_clusters( + _filter=f"name eq '{cluster_name}'") + cluster_uuid = api_response.data[0].ext_id + return cluster_uuid + except Exception as e: + print("Exception when calling ClustersApi->list_clusters: %s\n" % e) + return None + +''' +Method to create a category +''' +def CreateCategory(categories_api, key="example_key", value="example_value_1", description="Description of the category"): + cat = ntnx_prism_py_client.Category( + key=key, value=value, description=description) + try: + resp = categories_api.create_category(body=cat) + cat_ext_id = resp.data.ext_id + print("Category specs: ", cat.to_dict()) + return cat_ext_id + except Exception as e: + print("Exception when calling CategoriesApi->create_category: %s\n" % e) + return None + +''' +Method to get the VM UUID by name +''' +def GetVmUuidByName(vm_api, vm_name): + try: + api_response = vm_api.list_vms(_filter=f"name eq '{vm_name}'") + vm_uuid = api_response.data[0].ext_id + return vm_uuid + except Exception as e: + print("Exception when calling VmApi->list_vms: %s\n" % e) + return None + +''' +Method to create a VM +''' +def CreateVm(vm_api, cluster_uuid, vm_name="example_vm", description="VM created using Nutanix v4 Python SDK"): + vm = AhvVmConfig.Vm.Vm( + name=vm_name, + description=description, + cluster=ntnx_vmm_py_client.AhvConfigClusterReference( + ext_id=cluster_uuid) + ) + try: + api_response = vm_api.create_vm(body=vm, async_req=False) + vm_uuid = GetVmUuidByName(vm_api, vm_name) + print("VM specs: ", vm.to_dict()) + return vm_uuid + except Exception as e: + print("Exception when calling VmApi->create_vm: %s\n" % e) + return None + +''' +Method to associate a category to a VM +''' +def AssociateCategoryToVm(vm_api, vm_uuid, cat_ext_id): + try: + resp = vm_api.get_vm_by_id(vm_uuid) + eTag = ntnx_prism_py_client.ApiClient().get_etag(resp) + kwargs = {"if_match": eTag} + resp = vm_api.associate_categories(extId=vm_uuid, body=AhvConfigAssociateVmCategoriesParams( + [ntnx_vmm_py_client.AhvConfigCategoryReference(ext_id=cat_ext_id)]), **kwargs) + return resp + except Exception as e: + print("Exception when calling VmApi->associate_categories: %s\n" % e) + return None + +''' +Method to get a category by ID +''' +def GetCategoryById(categories_api, cat_ext_id): + try: + resp = categories_api.get_category_by_id(extId=cat_ext_id) + return resp + except Exception as e: + print("Exception when calling CategoriesApi->get_category_by_id: %s\n" % e) + return None + +''' +Method to list the detailed associations of a category +''' +def ListDetailedAssociationsOfCategory(categories_api, cat_ext_id): + try: + resp = categories_api.get_category_by_id( + extId=cat_ext_id, _expand="detailedAssociations") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->get_category_associations: %s\n" % e) + return None + +''' +Method to update the description of a category +''' +def UpdateDescriptionOfCategory(categories_api, cat_ext_id, newDescription): + resp = GetCategoryById(categories_api, cat_ext_id) + owner_uuid = resp.data.owner_uuid + eTag = ntnx_prism_py_client.ApiClient().get_etag(resp) + cat = ntnx_prism_py_client.Category( + key="example_key", value="example_value_1", description=newDescription, owner_uuid=owner_uuid) + kwargs = {"if_match": eTag} + try: + resp = categories_api.update_category_by_id( + extId=cat_ext_id, body=cat, **kwargs) + return resp + except Exception as e: + print("Exception when calling CategoriesApi->update_category_by_id (new description): %s\n" % e) + return None + +''' +Method to update the owner_uuid of a category +''' +def UpdateOwnerUuidOfCategory(categories_api, cat_ext_id, owner_uuid): + resp = GetCategoryById(categories_api, cat_ext_id) + description = resp.data.description + eTag = ntnx_prism_py_client.ApiClient().get_etag(resp) + cat = ntnx_prism_py_client.Category( + key="example_key", value="example_value_1", description=description, owner_uuid=owner_uuid) + kwargs = {"if_match": eTag} + try: + resp = categories_api.update_category_by_id( + extId=cat_ext_id, body=cat, **kwargs) + return resp + except Exception as e: + print("Exception when calling CategoriesApi->update_category_by_id (new owner_uuid): %s\n" % e) + return None + +''' +Method to list all categories +''' +def ListAllCategories(categories_api): + try: + resp = categories_api.list_categories() + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories with associated counts +''' +def ListCategoriesWithCount(categories_api): + try: + resp = categories_api.list_categories(_expand="associations") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to sort categories by key +''' +def SortCategoriesByKey(categories_api): + try: + resp = categories_api.list_categories(_orderby="key") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories with certain attributes (e.g. value, type, description) +''' +def ListCategoriesWithAttributes(categories_api, attributes): + try: + resp = categories_api.list_categories(_select=attributes) + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories matching a certain key/value content (e.g. example_key/example_value) +''' +def ListCategoriesMatchingKeyAndValue(categories_api, key, value): + try: + resp = categories_api.list_categories( + _filter=f"key eq '{key}' and value eq '{value}'") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories starting with a certain string in key and value +''' +def ListCategoriesStartingWithCertainStringInKeyAndValue(categories_api, str_key, str_value): + try: + resp = categories_api.list_categories( + _filter=f"startswith(key,'{str_key}') and startswith(value,'{str_value}')") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories sorted by key and value +''' +def ListCategoriesSortedByKeyAndValue(categories_api): + try: + resp = categories_api.list_categories(_orderby="key,value") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories of a certain type (USER / SYSTEM / INTERNAL) +''' +def ListCategoriesOfCertainType(categories_api, category_type): + try: + resp = categories_api.list_categories( + _filter=f"type eq Prism.Config.CategoryType'{category_type}'") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to list categories having at least one association +''' +def ListCategoriesWithAtLeastOneAssociation(categories_api): + try: + resp = categories_api.list_categories( + _expand="associations($filter=count ge 1)") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None +''' +Method to list categories having associated with a particular resource type +''' +def ListCategoriesWithAssociationsOfResourceType(categories_api, resource_type): + try: + resp = categories_api.list_categories( + _expand="associations($filter=resourceType eq Prism.Config.ResourceType'" + resource_type + "')") + return resp + except Exception as e: + print("Exception when calling CategoriesApi->list_categories: %s\n" % e) + return None + +''' +Method to disassociate a category from a VM +''' +def DisassociateCategoryFromVm(vm_api, vm_uuid, cat_ext_id): + try: + resp = vm_api.get_vm_by_id(vm_uuid) + eTag = ntnx_prism_py_client.ApiClient().get_etag(resp) + kwargs = {"if_match": eTag} + resp = vm_api.disassociate_categories(extId=vm_uuid, body=AhvConfigDisassociateVmCategoriesParams( + [ntnx_vmm_py_client.AhvConfigCategoryReference(ext_id=cat_ext_id)]), **kwargs) + return resp + except Exception as e: + print("Exception when calling VmApi->disassociate_categories: %s\n" % e) + return None + +''' +Method to delete a VM +''' +def DeleteVm(vm_api, vm_uuid): + try: + resp = vm_api.get_vm_by_id(vm_uuid) + eTag = ntnx_prism_py_client.ApiClient().get_etag(resp) + kwargs = {"if_match": eTag} + resp = vm_api.delete_vm_by_id(extId=vm_uuid, **kwargs) + return resp + except Exception as e: + print("Exception when calling VmApi->delete_vm_by_id: %s\n" % e) + return None + +''' +Method to delete a category +''' +def DeleteCategory(categories_api, cat_ext_id): + try: + resp = categories_api.delete_category_by_id(extId=cat_ext_id) + return resp + except Exception as e: + print("Exception when calling CategoriesApi->delete_category_by_id: %s\n" % e) + return None + +''' +Main workflow to demonstrate the usage of the Categories API +''' +def workflow(cluster_api, vm_api, categories_api): + # Get the cluster UUID + cluster_uuid = None + try: + cluster_uuid = GetClusterUuidByName( + cluster_api, CLUSTER_NAME) + print("Cluster UUID: ", cluster_uuid) + except Exception as e: + print("Exception when calling GetClusterUuidByName: %s\n" % e) + return + + # ------------------------------------------------------------# + print("\n#", "-"*50, " CREATE THE CATEGORY ", "-"*50, "#\n") + try: + cat_ext_id = CreateCategory(categories_api, key="example_key", + value="example_value_1", description="Description of the category") + print("Category created with ext_id: ", cat_ext_id) + except Exception as e: + print("Exception when calling CreateCategory: %s\n" % e) + return + + # ------------------------------------------------------------# + print("\n#", "-"*50, " FETCH THE CATEGORY ", "-"*50, "#\n") + # Fetch a category + try: + resp = GetCategoryById(categories_api, cat_ext_id) + print("Fetched category: ", resp.data) + except Exception as e: + print("Exception when calling GetCategoryById: %s\n" % e) + + # ------------------------------------------------------------# + vm_uuid = None + print("\n#", "-"*50, " CREATE THE VM ", "-"*50, "#\n") + try: + vm_uuid = CreateVm(vm_api, cluster_uuid, vm_name="example_vm", + description="VM created using Nutanix v4 Python SDK") + print("VM created with ext_id: ", vm_uuid) + except Exception as e: + print("Exception when calling CreateVm: %s\n" % e) + return + + # ------------------------------------------------------------# + print("\n#", "-"*50, " ASSOCIATE THE CATEGORY TO VM ", "-"*50, "#\n") + # Associate a category from a VM + try: + resp = AssociateCategoryToVm(vm_api, vm_uuid, cat_ext_id) + print("Category associated with VM: ", resp) + except Exception as e: + print("Exception when calling AssociateCategoryToVm: %s\n" % e) + return + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST THE ASSOCIATIONS OF THE CATEGORY ", "-"*50, "#\n") + # List the associations of a category + try: + resp = ListDetailedAssociationsOfCategory(categories_api, cat_ext_id) + print("Associations of the category: ", resp) + except Exception as e: + print("Exception when calling ListDetailedAssociationsOfCategory: %s\n" % e) + + # ------------------------------------------------------------# + # Update a category + print("\n#", "-"*50, " UPDATE THE DESCRIPTION OF THE CATEGORY ", "-"*50, "#\n") + # 1. Update the description of the category + newDescription = "New description of the category" + try: + resp = UpdateDescriptionOfCategory( + categories_api, cat_ext_id, newDescription) + print("category description updated: ", resp) + except Exception as e: + print("Exception when calling UpdateDescriptionOfCategory: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " UPDATE THE OWNER_UUID OF THE CATEGORY ", "-"*50, "#\n") + # 2. Update the Owner UUID of the category + # Note: The owner_uuid should be a valid user_uuid + newOwnerUuid = "NEW_OWNER_UUID" + try: + resp = UpdateOwnerUuidOfCategory( + categories_api, cat_ext_id, newOwnerUuid) + print("category owner_uuid updated: ", resp) + except Exception as e: + print("Exception when calling UpdateOwnerUuidOfCategory: %s\n" % e) + + # ------------------------------------------------------------# + # List categories + print("\n#", "-"*50, " LIST ALL THE CATEGORIES ", "-"*50, "#\n") + # 1. List all categories + try: + resp = ListAllCategories(categories_api) + print("List of categories: ", resp) + except Exception as e: + print("Exception when calling ListAllCategories: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST CATEGORIES WITH ASSOCIATED COUNTS ", "-"*50, "#\n") + # 2. Use expansion to show associated counts + try: + resp = ListCategoriesWithCount(categories_api) + print("List of categories with count: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesWithCount: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " SORT THE CATEGORIES BY KEY ", "-"*50, "#\n") + # 3. sort by key + try: + resp = SortCategoriesByKey(categories_api) + print("List of categories sorted by key: ", resp) + except Exception as e: + print("Exception when calling SortCategoriesByKey: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST CATEGORIES WITH CERTAIN ATTRIBUTES ", "-"*50, "#\n") + # 4. return only certain attributes in the result (e.g. value, type, description) + try: + resp = ListCategoriesWithAttributes( + categories_api, "value,type,description") + print("List of categories with only key, value and type: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesWithAttributes: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, + " LIST CATEGORIES MATCHING A CERTAIN KEY/VALUE CONTENT ", "-"*50, "#\n") + # 5. filter categories matching a certain key/value content + try: + resp = ListCategoriesMatchingKeyAndValue( + categories_api, "example_key", "example_value_1") + print("List of categories with key 'my_category': ", resp) + except Exception as e: + print("Exception when calling ListCategoriesMatchingKeyAndValue: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST CATEGORIES STARTING WITH A CERTAIN STRING IN KEY AND VALUE ", "-"*50, "#\n") + # 6. filter categories whose key or value starting with a certain string + try: + resp = ListCategoriesStartingWithCertainStringInKeyAndValue( + categories_api, "ex", "ex") + print("List of categories with key starting with 'ex' and value starting with 'ex': ", resp) + except Exception as e: + print("Exception when calling ListCategoriesStartingWithCertainStringInKeyAndValue: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST CATEGORIES SORTED BY KEY AND VALUE ", "-"*50, "#\n") + # 7. sort by key and value + try: + resp = ListCategoriesSortedByKeyAndValue(categories_api) + print("List of categories sorted by key and value: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesSortedByKeyAndValue: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " LIST CATEGORIES OF A CERTAIN TYPE (USER / SYSTEM / INTERNAL) ", "-"*50, "#\n") + # 8. filter categories w.r.t type USER / SYSTEM / INTERVAL + try: + resp = ListCategoriesOfCertainType(categories_api, "USER") + print("List of categories with type USER: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesOfCertainType: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, + " LIST CATEGORIES HAVING AT LEAST ONE ASSOCIATION ", "-"*50, "#\n") + # 9. use filters and expansion to show only those categories that have at least one association + try: + resp = ListCategoriesWithAtLeastOneAssociation(categories_api) + print("List of categories with at least one association: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesWithAtLeastOneAssociation: %s\n" % e) + # ------------------------------------------------------------# + print("\n#", "-"*50, + " LIST CATEGORIES HAVING ASSOCIATION WITH A PARTICULAR RESOURCE TYPE ", "-"*50, "#\n") + # 9. use filters and expansion to show only those categories that have at least one association + try: + resp = ListCategoriesWithAssociationsOfResourceType(categories_api, "VM") + print("List of categories associated with resource type VM: ", resp) + except Exception as e: + print("Exception when calling ListCategoriesWithAtLeastOneAssociation: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-"*50, " DISASSOCIATE THE CATEGORY FROM VM ", "-"*50, "#\n") + + # Disassociate a category from a VM + try: + resp = DisassociateCategoryFromVm(vm_api, vm_uuid, cat_ext_id) + print(f"Disassociated category {cat_ext_id} from VM {vm_uuid}") + except Exception as e: + print("Exception when calling DisassociateCategoryFromVm: %s\n" % e) + + print("\n#", "-"*50, " DELETE THE VM ", "-"*50, "#\n") + # Delete the VM + try: + resp = DeleteVm(vm_api, vm_uuid) + print(f"Deleted VM with ext_id: {vm_uuid}") + except Exception as e: + print("Exception when calling DeleteVm: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-" * 50, " DELETE THE CATEGORY ", "-" * 50, "#\n") + + # Delete a category + try: + resp = DeleteCategory(categories_api, cat_ext_id) + print(f"Deleted category with ext_id: {cat_ext_id}") + except Exception as e: + print("Exception when calling DeleteCategory: %s\n" % e) + + # ------------------------------------------------------------# + print("\n#", "-" * 120, "#\n") + + +if __name__ == '__main__': + cluster_api, vm_api, categories_api = setup() + workflow(cluster_api, vm_api, categories_api) diff --git a/python/v4api_sdk/categories/requirements.txt b/python/v4api_sdk/categories/requirements.txt new file mode 100644 index 0000000..ed60fe1 --- /dev/null +++ b/python/v4api_sdk/categories/requirements.txt @@ -0,0 +1,4 @@ +urllib3==1.26.20 +ntnx-clustermgmt-py-client==4.0.1 +ntnx-prism-py-client==4.0.1 +ntnx-vmm-py-client==4.0.1 \ No newline at end of file