
Huawei Cloud Flexus L Server Manage
- 71 installs
- 19 repo stars
- Updated July 31, 2026
- huaweicloud/huaweicloud-skills
Manage Huawei Cloud Flexus L server lifecycle: query regions/images/specs and create, renew, or unsubscribe lightweight instances.
About
Handles Huawei Cloud Flexus L server lifecycle including querying regions, images, and specs, plus creating, renewing, and unsubscribing instances. A developer uses it to purchase and manage lightweight cloud servers.
- Create, renew, and unsubscribe Flexus L instances
- Query available regions, images, and specs first
Huawei Cloud Flexus L Server Manage by the numbers
- 71 all-time installs (skills.sh)
- +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #648 of 1,042 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/huaweicloud/huaweicloud-skills --skill huawei-cloud-flexus-l-server-manageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 71 |
|---|---|
| repo stars | ★ 19 |
| Last updated | July 31, 2026 |
| Repository | huaweicloud/huaweicloud-skills ↗ |
What it does
Manage Huawei Cloud Flexus L server lifecycle: query regions/images/specs and create, renew, or unsubscribe lightweight instances.
Files
⚠️ Security Execution Rules (Highest Priority): 1. All scripts MUST be executed via skill action=exec, NEVER run directly in shell 2. NEVER print script contents or commands containing AK/SK/Token in conversation 3. NEVER create temporary script files, prefer inline execution (python -c) 4. On execution failure, only return error info, do NOT rewrite scripts or print full commands 5. AK/SK/Token MUST be passed via environment variables, NEVER appear in conversation 6. ⚠️ ABSOLUTELY NEVER expose, log, or print AK/SK/Token values in any form - this is a critical security requirement
Huawei Cloud Flexus L Instance Lifecycle Management Skill
Overview
This skill provides core lifecycle management capabilities for Huawei Cloud Flexus L instances:
| Module | Description | Command |
|---|---|---|
| Query Regions | Show available regions | show-regions |
| Query Images | Show available images | show-images |
| Query Specs | Show available specs | show-specs |
| Create | Purchase new Flexus L instances | create-instance |
| Renewal | Renew existing instances | renewal |
| Unsubscribe | Cancel instance subscription | unsubscribe |
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Flexus L Lifecycle Skill │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Create │ │ Renewal │ │ Unsubscribe │ │
│ └────┬─────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────┴────────────────┘ │
│ │ │
│ ┌─────────────▼─────────────┐ │
│ │ flexus_specs_extractor │ │
│ └─────────────┬─────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ HCSS API │ (Instance Management) │
│ └──────┬──────┘ │
│ ┌──────▼──────┐ │
│ │ BSS API │ (Billing & Subscription) │
│ └──────┬──────┘ │
│ ┌──────▼──────┐ │
│ │ IAM API │ (Project ID) │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘Data Source
- Region, Image, Spec Information: Dynamically fetched from official documentation, no local config file needed
- Official Documentation: https://support.huaweicloud.com/api-flexusl/create_instance_0001.html
- On-Demand Fetch: Automatically fetches latest data when executing
show-regions,show-images,show-specs,create-instance
Use Cases
1. Automated Server Provisioning - Create Flexus L instances for development, testing, or production environments 2. Cost Optimization - Renew instances during promotional periods to save costs 3. Resource Cleanup - Safely unsubscribe instances when projects end 4. Multi-Region Deployment - Create instances across different regions for disaster recovery
Applicable Scenarios
- Development and testing environment setup
- Production server lifecycle management
- Cost control and budget management
- Project resource cleanup and decommissioning
Important Notes
⚠️ All scripts and environment check scripts are inside the skill package. You must use skill action=exec to execute them; do not run them directly in the shell.
Prerequisites
Before using this skill, ensure the following conditions are met:
1. Huawei Cloud Account
- Valid Huawei Cloud account
- Account has completed real-name verification
- Account has sufficient balance or bound payment method
2. AK/SK Credentials
- Created Huawei Cloud access keys (AK/SK)
- AK/SK has the following permissions:
BSS: Billing service (order query, renewal, unsubscribe)HCSS: Flexus L instance managementIAM: Project ID query (read-only)- How to obtain: Huawei Cloud Console → My Credentials → Access Keys → Create Access Key
Credential Configuration Methods:
| Method | Description | Priority |
|---|---|---|
| Environment Variables | HW_ACCESS_KEY, HW_SECRET_KEY, HW_SECURITY_TOKEN | Highest (Recommended) |
| Command-line Parameters | --ak, --sk, --security-token | Lower (Accepted but not recommended) |
Credential Types (by recommendation order):
| Priority | Type | Parameters | Description |
|---|---|---|---|
| Primary | Temporary AK/SK | Environment variables + HW_SECURITY_TOKEN | Temporary credentials with security token, higher security |
| Secondary | Permanent AK/SK | Environment variables only | Long-term access keys from Huawei Cloud console |
Environment Variables (Primary Method - Strongly Recommended):
Pass credentials via environment variables HW_ACCESS_KEY, HW_SECRET_KEY, HW_SECURITY_TOKEN. Temporary credentials require HW_SECURITY_TOKEN, permanent credentials do not.
Command-line Parameters (Secondary Method - Accepted):
# Temporary credentials
python scripts/flexus_lifecycle.py create-instance \
--ak <AK> --sk <SK> --security-token <TOKEN> \
--image Ubuntu --cpu 2 --memory 4
# Permanent credentials
python scripts/flexus_lifecycle.py create-instance \
--ak <AK> --sk <SK> \
--image Ubuntu --cpu 2 --memory 4⚠️ Security Token Notes:
- Temporary AK/SK (Primary): Requires
HW_SECURITY_TOKENenvironment variable, higher security, strongly recommended - Permanent AK/SK (Secondary): Only needs
HW_ACCESS_KEYandHW_SECRET_KEY, no security token needed
3. IAM Permissions
| Service | Policy | Required Actions |
|---|---|---|
| HCSS | HCSS FullAccess | hcss:lightInstances:* |
| BSS | BSS Administrator | bss:order:*, bss:renewal:*, bss:unsubscribe:* |
| IAM | IAM ReadOnlyAccess | iam:projects:list |
Permission Failure Handling:
| Error | Cause | Solution |
|---|---|---|
403 Forbidden | Missing policy | Add required IAM policy to user |
APIGW.0101 | Service not enabled | Enable Flexus L in target region |
BSS.0501 | No access to resource | Verify resource belongs to account |
See references/permission-guide.md for detailed permission setup.
4. Runtime Environment
- Python 3.8 or higher
- Required dependencies installed (see Dependencies section below)
5. Network Environment
- Able to access Huawei Cloud API endpoints
- Required endpoints:
hcss.cn-north-4.myhuaweicloud.comiam.myhuaweicloud.combss.myhuaweicloud.com- If using proxy, configure environment variables correctly
Trigger Rules
Activate this skill when users mention:
Create related:
- "Purchase Huawei Cloud server", "Create Flexus instance", "Huawei Cloud lightweight server"
- "hcss instance", "New Flexus L"
Renewal related:
- "Renew Flexus L instance", "Flexus renewal", "Renew lightweight server"
- "Huawei Cloud renewal", "renew flexus"
Unsubscribe related:
- "Unsubscribe Flexus L instance", "Cancel Flexus instance", "Unsubscribe lightweight server"
- "Huawei Cloud unsubscribe", "cancel subscription flexus"
---
⚠️ Conversation Display Guidelines (Important)
When displaying "Available Specifications" or "Available Images" to users in conversation, you MUST immediately append the following note:
Note
>
- Spec codes vary by region and image version. Please refer to the official documentation Appendix 1 (spec codes for each image type) and Appendix 2 (spec details for each code) before purchasing.
- Official Link: <https://support.huaweicloud.com/api-flexusl/create_instance_0001.html#create_instance_0001__section1881914176434>
This applies to all conversation scenarios, including dry-run previews.
---
Security Notes
⚠️ AK/SK Security Requirements (CRITICAL):
- Environment Variables (Primary - Strongly Recommended):
HW_ACCESS_KEY,HW_SECRET_KEY,HW_SECURITY_TOKEN - Command-line Parameters (Secondary - Accepted):
--ak,--sk,--security-token - Priority: Environment variables > Command-line parameters
- Security Token: Recommended - Temporary credentials have higher security
⚠️ ABSOLUTE SECURITY RULES - NEVER VIOLATE:
1. NEVER print, log, or display AK/SK/Token values in any form 2. NEVER include AK/SK/Token in error messages or debug output 3. NEVER store AK/SK/Token in files or configuration 4. NEVER transmit AK/SK/Token over insecure channels 5. When command-line parameters are used, they MUST be masked in any output
Credential Usage (by recommendation order):
# Primary: With security token (temporary credentials, higher security)
BasicCredentials(ak, sk).with_security_token(security_token)
# Secondary: Without security token (permanent credentials)
BasicCredentials(ak, sk)---
Core Commands
| Command | Function | Required Params | Optional Params |
|---|---|---|---|
show-regions | Show available regions | None | None |
show-images | Show available images | None | --region |
show-specs | Show available specs | --image | --region |
create-instance | Create instance | --ak, --sk, --security-token (recommended) | --region, --image, --plan-spec, --cpu, --memory, --period-num, --period-type, --instance-name, --auto-renew, --auto-pay, --dry-run, --confirm |
renewal | Renew instance | --ak, --sk, --security-token (recommended), --resource-ids | --period-num, --period-type, --auto-pay, --dry-run, --confirm |
unsubscribe | Unsubscribe instance | --ak, --sk, --security-token (recommended), --resource-ids | --type, --reason, --dry-run, --confirm |
---
Parameter Confirmation
Required Parameter Validation:
| Parameter | Description | Validation Rule |
|---|---|---|
--ak | Huawei Cloud Access Key ID | Non-empty (or set HW_ACCESS_KEY env var) |
--sk | Huawei Cloud Secret Access Key | Non-empty (or set HW_SECRET_KEY env var) |
--security-token | Security Token | Recommended (or set HW_SECURITY_TOKEN env var) |
--resource-ids | Resource IDs | Non-empty, comma-separated for multiple |
--image | Image name | Format: name:version, e.g. Ubuntu:22.04 |
--region | Region ID | Must be in supported regions list |
Parameter Relationships:
| Parameter Group | Description |
|---|---|
--plan-spec vs --cpu/--memory | Choose one; --plan-spec takes priority; if not specified, auto-match based on --cpu/--memory |
--dry-run vs --confirm | Choose one or neither; --dry-run previews only, --confirm skips confirmation, neither triggers interactive confirmation |
Default Values:
| Parameter | Default | Description |
|---|---|---|
--region | cn-north-4 | North China - Beijing 4 |
--image | Ubuntu | Ubuntu system image |
--period-num | 1 | Purchase/renew for 1 month |
--period-type | month | Monthly billing |
--type (unsubscribe) | 1 | Immediate unsubscribe |
--auto-renew | True | Enable auto renewal |
--auto-pay | True | Auto payment |
---
⚠️ Mandatory Confirmation Mechanism
Creation, renewal, and cancellation operations involve actual costs and must be executed only after explicit confirmation from the user!
Confirmation methods (choose one):
1. Dialog confirmation: Reply "confirm" or "yes" in conversation 2. Command-line confirmation: Use --confirm flag 3. Dry-run preview: Use --dry-run to preview without executing
⚠️ Failure Handling Rule:
When user confirms the preview order and the purchase fails:
- Only make ONE request - do not retry automatically
- Return the failure reason to the user
- Guide user to repurchase - let user decide next steps
- NEVER change parameters (region, spec, image, etc.) without user's explicit request
---
Usage
Basic Command Format
python scripts/flexus_lifecycle.py <command> [options]Global Parameters
| Parameter | Description | Required | Environment Variable |
|---|---|---|---|
--ak | Huawei Cloud Access Key ID | Yes (or env var) | HW_ACCESS_KEY |
--sk | Huawei Cloud Secret Access Key | Yes (or env var) | HW_SECRET_KEY |
--security-token | Security Token | Recommended | HW_SECURITY_TOKEN |
--region | Region ID (default: cn-north-4) | No | - |
--dry-run | Dry run, don't actually execute | No | - |
--confirm | Force confirmation, skip interactive | No | - |
Credential Configuration Examples
Method 1: Using Environment Variables (Primary - Strongly Recommended)
python scripts/flexus_lifecycle.py create-instance --image Ubuntu --cpu 2 --memory 4Method 2: Using Command-line Parameters (Secondary - Accepted)
# Temporary credentials
python scripts/flexus_lifecycle.py create-instance \
--ak <AK> --sk <SK> --security-token <TOKEN> \
--image Ubuntu \
--cpu 2 \
--memory 4
# Permanent credentials
python scripts/flexus_lifecycle.py create-instance \
--ak <AK> --sk <SK> \
--image Ubuntu \
--cpu 2 \
--memory 4---
Module Details
0️⃣ Query Functions
Show available regions:
python scripts/flexus_lifecycle.py show-regionsShow available images for a region:
python scripts/flexus_lifecycle.py --region cn-north-4 show-imagesShow available specs for an image:
python scripts/flexus_lifecycle.py --region cn-north-4 show-specs --image Ubuntu---
1️⃣ Create Instance (create-instance)
Purchase new Flexus L instances, supports Windows/Linux.
Method 1: Auto-match Spec (Recommended)
python scripts/flexus_lifecycle.py create-instance \
--region cn-north-4 \
--image Ubuntu \
--cpu 2 \
--memory 4Method 2: Specify Spec
python scripts/flexus_lifecycle.py create-instance \
--region cn-north-4 \
--image Ubuntu \
--plan-spec hf.medium.1.linuxMethod 3: Use Default Spec
python scripts/flexus_lifecycle.py create-instance \
--region cn-north-4 \
--image UbuntuCreate Parameters:
| Parameter | Description | Default |
|---|---|---|
--image | Image name | Ubuntu |
--plan-spec | Instance specification | Auto-match or config default |
--cpu | CPU cores (for auto-match) | - |
--memory | Memory GB (for auto-match) | - |
--period-num | Purchase duration (months) | 1 |
--period-type | Period type (month/year) | month |
--instance-name | Instance name | Auto-generated |
--auto-renew | Auto renewal | True |
--auto-pay | Auto payment | True |
Available Specifications Reference:
See references/image-specs-guide.md for detailed specs.
---
2️⃣ Renewal Instance (renewal)
Renew existing Flexus L instances.
# Preview renewal (recommended)
python scripts/flexus_lifecycle.py renewal \
--resource-ids <resource-id> \
--period-num 1 \
--period-type month \
--dry-run
# Confirm renewal
python scripts/flexus_lifecycle.py renewal \
--resource-ids <resource-id> \
--period-num 6 \
--period-type month \
--confirm
# Renew multiple instances
python scripts/flexus_lifecycle.py renewal \
--resource-ids id1,id2,id3 \
--period-num 1 \
--period-type year \
--confirmRenewal Parameters:
| Parameter | Description | Default |
|---|---|---|
--resource-ids | Resource IDs (comma-separated) | Required |
--period-num | Renewal period count | 1 |
--period-type | Period type (month/year) | month |
--auto-pay | Auto payment | True |
---
3️⃣ Unsubscribe Instance (unsubscribe)
Cancel Flexus L instance subscription.
# Preview unsubscribe (recommended)
python scripts/flexus_lifecycle.py unsubscribe \
--resource-ids <resource-id> \
--dry-run
# Immediate unsubscribe (type 1)
python scripts/flexus_lifecycle.py unsubscribe \
--resource-ids <resource-id> \
--type 1 \
--confirm
# Expiry unsubscribe (type 2)
python scripts/flexus_lifecycle.py unsubscribe \
--resource-ids <resource-id> \
--type 2 \
--confirm
# Batch unsubscribe
python scripts/flexus_lifecycle.py unsubscribe \
--resource-ids id1,id2,id3 \
--type 1 \
--reason "Project ended" \
--confirmUnsubscribe Parameters:
| Parameter | Description | Default |
|---|---|---|
--resource-ids | Resource IDs (comma-separated) | Required |
--type | Unsubscribe type (1=immediate, 2=expiry) | 1 |
--reason | Unsubscribe reason | None |
Unsubscribe Types:
| Type | Description | Effect |
|---|---|---|
| 1 | Unsubscribe resource and renewed periods | Resource stops immediately, pro-rated refund |
| 2 | Only unsubscribe renewed periods | Resource continues until expiry |
---
Available Regions
⚠️ Note: Flexus L instances currently support only the following regions:
| Region ID | Region Name | Spec Prefix |
|---|---|---|
| cn-north-4 | North China - Beijing 4 | hf.* |
| cn-east-3 | East China - Shanghai 1 | hf.* |
| cn-south-1 | South China - Guangzhou | hf.* |
| cn-southwest-2 | Southwest China - Guiyang 1 | ahf.* |
| ap-southeast-1 | Hong Kong, China | hf.* |
| ap-southeast-3 | Asia Pacific - Singapore | hf.* |
---
Dependencies
Python Dependencies
Install via pip:
pip install requests huaweicloudsdkcore huaweicloudsdkbssOr use pyproject.toml:
cd scripts
pip install -e .pyproject.toml
[project]
name = "flexus-lifecycle"
version = "1.0.0"
dependencies = [
"requests>=2.28.0",
"huaweicloudsdkcore>=3.0.0",
"huaweicloudsdkbss>=3.0.0",
]---
File Structure
skills/huawei-cloud-flexus-l-server-manage/
├── SKILL.md # This file
├── scripts/
│ ├── flexus_lifecycle.py # Main lifecycle script
│ ├── flexus_specs_extractor.py # Dynamic specs fetcher
│ └── pyproject.toml # Dependencies
└── references/
├── api-reference.md # API reference
├── iam-policies.md # IAM policies
├── image-specs-guide.md # Image specs guide
├── permission-guide.md # Permission setup
└── troubleshooting.md # Troubleshooting---
Error Handling
Common Errors
| Error Code | Description | Solution |
|---|---|---|
401 Unauthorized | Invalid AK/SK | Verify AK/SK is correct and active |
403 Forbidden | Permission denied | Add required IAM policies |
APIGW.0101 | API not found | Check service is enabled in region |
APIGW.0301 | Signature verification failed | Check SK is correct |
BSS.0501 | Resource not found | Verify resource ID is correct |
BSS.0502 | Resource state invalid | Check resource status |
400 Bad Request | Invalid parameters | Check spec/image compatibility |
See references/troubleshooting.md for detailed error handling.
---
References
- API Reference
- IAM Policies
- Image Specs Guide
- Permission Guide
- Troubleshooting
External References
- Flexus L Instance Purchase Guide - Official API Documentation
- AK/SK Authentication - IAM Authentication Guide
- Huawei Cloud Console - Resource Management Console
---
API Reference
Huawei Cloud APIs Used
1. HCSS API - Flexus L Instance Management
Base URL: https://hcss.cn-north-4.myhuaweicloud.com
| Endpoint | Method | Description | Full URL |
|---|---|---|---|
/v1/light-instances | POST | Create Flexus L instance | https://hcss.cn-north-4.myhuaweicloud.com/v1/light-instances |
Create Instance Request Body:
{
"instance_name": "string",
"description": "string",
"plan_spec": "hf.small.1.win",
"image_ref": {
"image_name": "WindowsServer",
"image_version": "2012R2_standard_ch"
},
"region": "cn-north-4",
"charging_mode": "prePaid",
"period_type": "month",
"period_num": 1,
"purchase_quantity": 1,
"is_auto_renew": true,
"is_auto_pay": true,
"extra_resources": [
{"type": "evs", "size": 20},
{"type": "cbr", "size": 20},
{"type": "hss"}
]
}2. BSS API - Billing Service
Base URL: https://bss.myhuaweicloud.com
| Endpoint | Method | Description | Full URL |
|---|---|---|---|
/v2/orders/subscriptions/resources/renew | POST | Renew resources | https://bss.myhuaweicloud.com/v2/orders/subscriptions/resources/renew |
/v2/orders/subscriptions/resources/unsubscribe | POST | Unsubscribe resources | https://bss.myhuaweicloud.com/v2/orders/subscriptions/resources/unsubscribe |
3. IAM API - Identity Management
Endpoint: https://iam.myhuaweicloud.com/v3/projects
Used to get Project ID by region.
IAM Permissions Guide
Required Permissions
This skill requires the following Huawei Cloud IAM permissions to manage Flexus L instances.
HCSS (Flexus L Instance) Permissions
| Permission | Description | Actions |
|---|---|---|
hcss:lightInstance:create | Create Flexus L instance | create-instance |
hcss:lightInstance:list | List Flexus L instances | show-regions, show-images, show-specs |
hcss:lightInstance:get | Get instance details | All operations |
hcss:lightInstance:renew | Renew instance | renewal |
hcss:lightInstance:unsubscribe | Unsubscribe instance | unsubscribe |
BSS (Billing) Permissions
| Permission | Description | Actions |
|---|---|---|
bss:order:list | List orders | renewal, unsubscribe |
bss:order:pay | Pay orders | create-instance, renewal |
bss:refund:apply | Apply refund | unsubscribe |
IAM Permissions
| Permission | Description | Actions |
|---|---|---|
iam:project:list | List projects | All operations |
---
Minimum Policy Template
{
"Version": "1.1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"hcss:lightInstance:create",
"hcss:lightInstance:list",
"hcss:lightInstance:get",
"hcss:lightInstance:renew",
"hcss:lightInstance:unsubscribe",
"bss:order:list",
"bss:order:pay",
"bss:refund:apply",
"iam:project:list"
],
"Resource": "*"
}
]
}---
Setup Instructions
Option 1: Use Built-in Policy
1. Go to IAM Console → Policies 2. Search for HCSS FullAccess policy 3. Assign to your user/role
Option 2: Create Custom Policy
1. Go to IAM Console → Policies → Create Custom Policy 2. Select JSON format 3. Paste the policy template above 4. Name it (e.g., FlexusLLifecyclePolicy) 5. Assign to your user/role
---
Permission Verification
To verify your permissions, run:
python3 scripts/flexus_lifecycle.py show-regionsIf you see region list, permissions are correctly configured.
---
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
403 Forbidden | Missing permission | Add required IAM policy |
No project found | Missing iam:project:list | Add IAM read permission |
Order creation failed | Missing bss:order:pay | Add BSS permission |
For more details, see Huawei Cloud IAM Documentation.
Flexus L System Images and Specifications Reference
Data Source
Image and specification information is dynamically fetched from official documentation, no local configuration file needed:
- Data Source: https://support.huaweicloud.com/api-flexusl/create_instance_0001.html
- Fetch Method: Automatically retrieves latest data when executing
show-regions,show-images,show-specs - Script:
scripts/flexus_specs_extractor.py
Supported System Images
| Image Name | Versions | Description |
|---|---|---|
| Ubuntu | 24.04, 22.04, 20.04, 18.04, 16.04 | Linux system |
| CentOS | 8.2, 8.1, 8.0, 7.9, 7.8, 7.7... | Linux system |
| CentOS_Stream | 9.0, 8.0 | Linux system |
| Debian | 12.0, 11.1, 9.0 | Linux system |
| Huawei Cloud EulerOS | 2.0 | Huawei EulerOS |
| openEuler | 20.03, 22.03 | Open source EulerOS |
| AlmaLinux | 9.0, 9.3, 9.4 | Linux system |
| Rocky Linux | 8.4, 8.5, 8.8, 8.10, 9.0... | Linux system |
| OpenSUSE | 15.0 | Linux system |
| CoreOS | 2079.4.0 | Container OS |
| WindowsServer | 2012R2~2022 | Windows system |
Available Specifications Reference
⚠️ Important: Spec code prefixes vary by region!
>
| Region | Spec Prefix | Example |
| ------ | ----------- | ------- |
| North China-Beijing 4, East China-Shanghai 1, South China-Guangzhou, etc. |hf.*|hf.small.1.win|
| Southwest China-Guiyang 1 (cn-southwest-2) |ahf.*|ahf.small.1.win|
>
Using the wrong prefix will result in `HCSS.14000001` error!
Standard Specifications (hf.* prefix)
Applies to Beijing 4, Shanghai 1, Guangzhou, and other regions:
| Spec Code | OS | CPU | Memory |
|---|---|---|---|
hf.small.1.linux | Linux | 2 vCPUs | 2GB |
hf.small.2.linux | Linux | 2 vCPUs | 2GB |
hf.medium.1.linux | Linux | 2 vCPUs | 4GB |
hf.medium.2.linux | Linux | 2 vCPUs | 4GB |
hf.large.1.linux | Linux | 2 vCPUs | 8GB |
hf.xlarge.1.linux | Linux | 4 vCPUs | 8GB |
hf.small.1.win | Windows | 2 vCPUs | 2GB |
hf.medium.1.win | Windows | 2 vCPUs | 4GB |
hf.large.1.win | Windows | 2 vCPUs | 8GB |
Guiyang 1 Specifications (ahf.* prefix)
Applies to cn-southwest-2 region:
| Spec Code | OS | CPU | Memory |
|---|---|---|---|
ahf.small.1.win | Windows | 2 vCPUs | 2GB |
ahf.medium.1.win | Windows | 2 vCPUs | 4GB |
ahf.large.1.win | Windows | 2 vCPUs | 8GB |
ahf.small.1.linux | Linux | 2 vCPUs | 2GB |
ahf.medium.1.linux | Linux | 2 vCPUs | 4GB |
ahf.large.1.linux | Linux | 2 vCPUs | 8GB |
Available Images Reference
Windows Images:
WindowsServer:2012R2_standard_chWindowsServer:2016_standard_chWindowsServer:2019_standard_chWindowsServer:2022_standard_ch
Linux Images:
Ubuntu:24.04Ubuntu:22.04CentOS:7.9CentOS:8.2Debian:12.0Huawei Cloud EulerOS:2.0
💡 Note: Spec codes vary by region and image version. Please refer to the official documentation Flexus L Instance Purchase Guide before purchasing:
>
- Appendix 1: Spec codes for each image type
- Appendix 2: Spec details for each code
>
Or use the command-line tool for real-time queries:
>
```bash
python scripts/flexus_lifecycle.py --region cn-north-4 show-images
python scripts/flexus_lifecycle.py --region cn-north-4 show-specs --image Ubuntu
```
Permission Guide
Required IAM Permissions
Minimum Required Permissions
| Service | Policy | Actions | Description |
|---|---|---|---|
| HCSS | HCSS FullAccess | hcss:*:* | Flexus L instance management |
| BSS | BSS Administrator | bss:*:* | Billing and subscription management |
| IAM | IAM ReadOnlyAccess | iam:projects:list | Get project ID by region |
Custom Policy (Recommended)
{
"Version": "1.1",
"Statement": [
{
"Effect": "Allow",
"Action": [
"hcss:lightInstances:create"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"bss:renewal:create",
"bss:unsubscribe:create"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iam:projects:list"
],
"Resource": "*"
}
]
}Permission Failure Handling
| Error Code | Cause | Solution |
|---|---|---|
401 | Invalid AK/SK | Verify AK/SK is correct and active |
403 | Insufficient permissions | Add required policies to user/role |
APIGW.0101 | API not found | Check if service is enabled in region |
APIGW.0301 | Authentication failed | Check AK/SK or token validity |
Common Permission Issues
1. "Permission denied" when creating instance
- Ensure
HCSS FullAccesspolicy is attached - Check if Flexus L service is enabled in the region
2. "Cannot get project ID"
- Ensure
IAM ReadOnlyAccesspolicy is attached
How to Grant Permissions
1. Login to Huawei Cloud Console 2. Go to Identity and Access Management → Users 3. Select the user → Authorize 4. Add required policies:
HCSS FullAccessBSS AdministratorIAM ReadOnlyAccess
5. Click OK to save
Troubleshooting Guide
Common Errors
1. Authentication Errors
| Error | Cause | Solution |
|---|---|---|
401 Unauthorized | Invalid AK/SK | Verify AK/SK is correct and active |
APIGW.0301 | Signature verification failed | Check SK is correct |
APIGW.0303 | Token expired | Regenerate AK/SK |
2. Permission Errors
| Error | Cause | Solution |
|---|---|---|
403 Forbidden | Insufficient permissions | Add required IAM policies |
APIGW.0101 | API not found | Check service is enabled in region |
3. Resource Errors
| Error | Cause | Solution |
|---|---|---|
BSS.0501 | Resource not found | Verify resource ID is correct |
BSS.0502 | Resource state invalid | Check resource status |
400 Bad Request | Invalid parameters | Check spec/image compatibility |
4. Network Errors
| Error | Cause | Solution |
|---|---|---|
DNS resolution failed | Cannot resolve API endpoint | Check network/DNS settings |
Connection timeout | Network unreachable | Check firewall/proxy settings |
SSL certificate error | Certificate verification failed | Update CA certificates |
Diagnostic Steps
Step 1: Verify AK/SK
# Test with IAM API to get project ID
python scripts/flexus_lifecycle.py get-project-id \
--ak <AK> --sk <SK> --region cn-north-4Step 2: Test Network
# Test API endpoint connectivity
curl -v https://hcss.cn-north-4.myhuaweicloud.com
curl -v https://iam.myhuaweicloud.comStep 3: Dry Run
# Preview operation without executing
python scripts/flexus_lifecycle.py create-instance \
--ak <AK> --sk <SK> \
--dry-runFAQ
Q: Why does creation fail with "spec not available"? A: Spec codes vary by region and image. Check the official documentation for supported combinations.
Q: Why can't I renew my instance? A: Ensure the instance is in active status and you have BSS Administrator permission.
Q: How to handle "insufficient balance" error? A: Top up your Huawei Cloud account or bind a payment method.
Q: Why does DNS resolution fail? A: The server may not be able to access Huawei Cloud API. Check network settings or use a proxy.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Shared configuration for Flexus L lifecycle management
"""
# Region configuration (from official documentation)
REGIONS = {
'cn-north-4': {'name': '华北-北京四', 'short': 'beijing'},
'cn-south-1': {'name': '华南-广州', 'short': 'guangzhou'},
'cn-east-3': {'name': '华东-上海一', 'short': 'shanghai'},
'cn-southwest-2': {'name': '西南-贵阳一', 'short': 'guizhou'},
'ap-southeast-1': {'name': '中国-香港', 'short': 'hongkong'},
'ap-southeast-3': {'name': '亚太-新加坡', 'short': 'singapore'},
}#!/usr/bin/env python3
"""
Huawei Cloud Flexus L Instance Lifecycle Management Tool
Integrates three core functions: create, renewal, and unsubscribe
Supports automatic spec matching for system images
Data is fetched dynamically from official documentation
"""
import os
import sys
import json
import argparse
import requests
import uuid
import subprocess
from pathlib import Path
from typing import Optional, List, Dict, Any, Tuple
from datetime import datetime
# Disable SSL warnings
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Import Huawei Cloud SDK
try:
from huaweicloudsdkcore.auth.credentials import BasicCredentials, GlobalCredentials
from huaweicloudsdkcore.signer.signer import Signer
from huaweicloudsdkcore.sdk_request import SdkRequest
from huaweicloudsdkcore.exceptions import exceptions
from huaweicloudsdkcore.http.http_config import HttpConfig
from huaweicloudsdkbss.v2.region.bss_region import BssRegion
from huaweicloudsdkbss.v2 import *
SDK_AVAILABLE = True
except ImportError as e:
print(f"Warning: Huawei Cloud BSS SDK import failed: {e}")
print("Please install: pip install huaweicloudsdkcore huaweicloudsdkbss")
SDK_AVAILABLE = False
from urllib.parse import urlparse
from config import REGIONS
# ============================================================================
# Dynamic Data Fetching
# ============================================================================
def get_script_dir() -> str:
"""Get the directory where this script is located"""
return os.path.dirname(os.path.abspath(__file__))
def fetch_specs_data(data_type: str = "all") -> Dict:
"""
Call flexus_specs_extractor.py to fetch latest data
Args:
data_type: all / regions / specs / images
Returns:
Fetched data dictionary
"""
extractor_path = os.path.join(get_script_dir(), "flexus_specs_extractor.py")
if not os.path.exists(extractor_path):
print(f"❌ Extractor script not found: {extractor_path}")
return {}
try:
cmd = ["python3", extractor_path]
if data_type != "all":
cmd.append(f"--{data_type}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30
)
if result.returncode == 0:
return json.loads(result.stdout)
else:
print(f"❌ Failed to fetch data: {result.stderr}")
return {}
except subprocess.TimeoutExpired:
print("❌ Fetch timeout (30s)")
return {}
except json.JSONDecodeError as e:
print(f"❌ Failed to parse data: {e}")
return {}
except Exception as e:
print(f"❌ Fetch error: {e}")
return {}
def get_region_ids() -> List[str]:
"""Get all region ID list"""
regions = fetch_specs_data("regions")
return list(regions.keys())
def get_region_by_name(region_name: str) -> Optional[str]:
"""Get region ID by region name"""
regions = fetch_specs_data("regions")
for region_id, info in regions.items():
if info.get("name") == region_name:
return region_id
return None
def get_region_name_by_id(region_id: str) -> Optional[str]:
"""Get region name by region ID"""
regions = fetch_specs_data("regions")
return regions.get(region_id, {}).get("name")
# ============================================================================
# Spec Query Functions
# ============================================================================
def get_available_images(region: str) -> Dict[str, Dict]:
"""
Get available images for a specified region
Args:
region: Region ID
Returns:
Mapping from image names to version/specs info
"""
system_images = fetch_specs_data("images")
result = {}
region_short = REGIONS.get(region, {}).get("short", "beijing")
for img_name, img_info in system_images.items():
region_data = img_info.get(region_short, {})
version = region_data.get("version", "")
specs = region_data.get("specs", [])
if specs:
result[img_name] = {"version": version, "specs": specs}
return result
def get_available_specs(region: str, image_name: str) -> List[str]:
"""
Get available specs for a specified region and image
Args:
region: Region ID
image_name: Image name
Returns:
List of available spec codes
"""
system_images = fetch_specs_data("images")
region_short = REGIONS.get(region, {}).get("short", "beijing")
if image_name in system_images:
return system_images[image_name].get(region_short, {}).get("specs", [])
return []
def get_spec_info(spec_code: str) -> Optional[Dict]:
"""
Get detailed information for a spec code
Args:
spec_code: Spec code
Returns:
Spec info dictionary
"""
data = fetch_specs_data("specs")
return data.get(spec_code)
def find_matching_spec(region: str, cpu: int, memory: int, os_type: str = "linux", image_name: str = None) -> Optional[str]:
"""
Find matching spec based on CPU, memory and OS type (region-aware)
Args:
region: Region ID
cpu: CPU cores
memory: Memory in GB
os_type: OS type (linux/windows)
image_name: Optional, image name for precise matching
Returns:
Matching spec code, or None if not found
"""
data = fetch_specs_data("all")
spec_defs = data.get("spec_definitions", {})
system_images = data.get("system_images", {})
region_short = REGIONS.get(region, {}).get("short", "beijing")
# If image is specified, search from that image's available specs first
if image_name and image_name in system_images:
available_specs = system_images[image_name].get(region_short, {}).get("specs", [])
if available_specs:
# Prefer exact match
for spec_code in available_specs:
spec_info = spec_defs.get(spec_code, {})
if spec_info.get("os") == os_type:
if spec_info.get("vcpu") == cpu and spec_info.get("memory") == memory:
return spec_code
# No exact match, find closest
best_match = None
best_diff = float('inf')
for spec_code in available_specs:
spec_info = spec_defs.get(spec_code, {})
if spec_info.get("os") == os_type:
diff = abs(spec_info.get("vcpu", 0) - cpu) + abs(spec_info.get("memory", 0) - memory)
if diff < best_diff:
best_diff = diff
best_match = spec_code
if best_match:
return best_match
# Search from all specs
for spec_code, spec_info in spec_defs.items():
if spec_info.get("os") == os_type:
if spec_info.get("vcpu") == cpu and spec_info.get("memory") == memory:
return spec_code
# No exact match, find closest
best_match = None
best_diff = float('inf')
for spec_code, spec_info in spec_defs.items():
if spec_info.get("os") == os_type:
diff = abs(spec_info.get("vcpu", 0) - cpu) + abs(spec_info.get("memory", 0) - memory)
if diff < best_diff:
best_diff = diff
best_match = spec_code
return best_match
def is_region_supported(region: str) -> Tuple[bool, str]:
"""
Check if a region supports Flexus L
Args:
region: Region ID
Returns:
(is_supported, reason)
"""
regions = fetch_specs_data("regions")
if region not in regions:
return False, f"Region {region} not supported"
return True, "Supported"
# ============================================================================
# Authentication
# ============================================================================
def get_project_id_by_region(ak: str, sk: str, security_token: Optional[str], region: str) -> Optional[str]:
"""Get Project ID for a specified region"""
iam_endpoint = "https://iam.myhuaweicloud.com/v3/projects"
try:
if security_token:
credentials = BasicCredentials(ak, sk).with_security_token(security_token)
else:
credentials = BasicCredentials(ak, sk)
signer = Signer(credentials)
request = SdkRequest()
request.method = "GET"
request.schema = "https"
request.host = "iam.myhuaweicloud.com"
request.resource_path = "/v3/projects"
request.body = ""
request.header_params = {
"Content-Type": "application/json",
"Client-Request-Id": str(uuid.uuid4())
}
if security_token:
request.header_params["X-Security-Token"] = security_token
request.query_params = []
signed_request = signer.sign(request)
headers = {}
for key, value in signed_request.header_params.items():
if isinstance(value, bytes):
headers[key] = value.decode('iso-8859-1')
else:
headers[key] = str(value)
resp = requests.get(iam_endpoint, headers=headers, timeout=30)
if resp.status_code == 200:
data = resp.json()
projects = data.get('projects', [])
if projects:
for project in projects:
project_name = project.get('name', '')
if project_name == region:
return project.get('id')
return projects[0].get('id')
return None
except Exception as e:
print(f"Failed to get Project ID: {e}")
return None
def create_bss_client(ak: str, sk: str, security_token: Optional[str], region: str = "cn-north-1"):
"""Create BSS client"""
if not SDK_AVAILABLE:
raise ImportError("Huawei Cloud BSS SDK not installed")
if security_token:
credentials = GlobalCredentials(ak, sk).with_security_token(security_token)
else:
credentials = GlobalCredentials(ak, sk)
config = HttpConfig.get_default_config()
config.ignore_ssl_verification = True
client = BssClient.new_builder() \
.with_credentials(credentials) \
.with_http_config(config) \
.with_region(BssRegion.value_of(region)) \
.build()
return client
# ============================================================================
# Create Instance
# ============================================================================
def create_flexus_l_instance(
ak: str,
sk: str,
security_token: Optional[str],
region: str = "cn-north-4",
plan_spec: str = "hf.small.1.win",
image_name: str = "WindowsServer",
image_version: str = "2012R2_standard_ch",
period_num: int = 1,
period_type: str = "month",
instance_name: Optional[str] = None,
auto_renew: bool = True,
auto_pay: bool = True,
dry_run: bool = False
) -> Dict[str, Any]:
"""
Create Flexus L instance
"""
# Check if region is supported
supported, reason = is_region_supported(region)
if not supported:
return {"success": False, "error": reason}
# Get project ID
project_id = get_project_id_by_region(ak, sk, security_token, region)
if not project_id:
return {"success": False, "error": f"Failed to get project ID for region {region}"}
# Generate instance name
if not instance_name:
instance_name = f"flexus{int(uuid.uuid4().hex[:8], 16)}"
else:
if instance_name[0].isdigit():
instance_name = f"flexus{instance_name}"
instance_name = instance_name.replace("_", "-")
# Build request body
request_body = {
"instance_name": instance_name,
"description": "Flexus L instance created via API",
"plan_spec": plan_spec,
"image_ref": {
"image_name": image_name,
"image_version": image_version
},
"region": region,
"charging_mode": "prePaid",
"period_type": period_type,
"period_num": period_num,
"purchase_quantity": 1,
"is_auto_renew": auto_renew,
"is_auto_pay": auto_pay,
"extra_resources": [
{"type": "evs", "size": 20},
{"type": "cbr", "size": 20},
{"type": "hss"}
]
}
# Dry run
if dry_run:
return {
"success": True,
"dry_run": True,
"message": "Dry run successful, parameters validated",
"params": {
"instance_name": instance_name,
"plan_spec": plan_spec,
"image": f"{image_name}:{image_version}",
"region": region,
"region_name": get_region_name_by_id(region),
"period_num": period_num,
"period_type": period_type,
"auto_renew": auto_renew,
"auto_pay": auto_pay
}
}
# Actual creation - URL hardcoded to cn-north-4 as per API requirement
try:
if security_token:
credentials = BasicCredentials(ak, sk, project_id).with_security_token(security_token)
else:
credentials = BasicCredentials(ak, sk, project_id)
signer = Signer(credentials)
# API URL is hardcoded to cn-north-4 (global endpoint)
url = "https://hcss.cn-north-4.myhuaweicloud.com/v1/light-instances"
parsed_url = urlparse(url)
body_json = json.dumps(request_body, ensure_ascii=False)
header_params = {
"X-Project-Id": project_id,
"Content-Type": "application/json",
"Client-Request-Id": str(uuid.uuid4())
}
if security_token:
header_params["X-Security-Token"] = security_token
request = SdkRequest(
method="POST",
schema=parsed_url.scheme,
host=parsed_url.netloc,
resource_path=parsed_url.path,
query_params=[],
header_params=header_params,
body=body_json
)
signed_request = signer.sign(request)
full_url = f"{signed_request.schema}://{signed_request.host}{signed_request.resource_path}"
resp = requests.request(
signed_request.method,
full_url,
headers=signed_request.header_params,
data=signed_request.body,
verify=False,
timeout=60
)
if resp.status_code == 202:
result = resp.json()
return {
"success": True,
"order_id": result.get('order_id'),
"instance_ids": result.get('instance_ids', []),
"instance_name": instance_name,
"message": "Instance creation request submitted successfully"
}
else:
error_data = resp.json() if resp.text else {}
return {
"success": False,
"error": f"Creation failed: {resp.status_code}",
"error_code": error_data.get('error_code'),
"error_msg": error_data.get('error_msg'),
"response": resp.text
}
except Exception as e:
return {"success": False, "error": f"Creation exception: {str(e)}"}
# ============================================================================
# Renewal
# ============================================================================
def renewal_resources(
ak: str,
sk: str,
security_token: Optional[str],
resource_ids: List[str],
period_num: int = 1,
period_type: str = "month",
auto_pay: bool = True,
dry_run: bool = False
) -> Dict[str, Any]:
"""Renew resources"""
if not isinstance(resource_ids, list):
resource_ids = [resource_ids]
period_type_map = {"month": 2, "year": 3}
period_type_value = period_type_map.get(period_type, 2)
if dry_run:
return {
"success": True,
"dry_run": True,
"message": "Dry run successful",
"params": {
"resource_ids": resource_ids,
"period_num": period_num,
"period_type": period_type,
"auto_pay": auto_pay
}
}
try:
client = create_bss_client(ak, sk, security_token)
request = RenewalResourcesRequest()
request.body = RenewalResourcesReq(
is_auto_pay=1 if auto_pay else 0,
period_num=period_num,
period_type=period_type_value,
resource_ids=resource_ids
)
response = client.renewal_resources(request)
if hasattr(response, 'order_ids') and response.order_ids:
return {
"success": True,
"order_ids": response.order_ids,
"message": "Renewal successful"
}
else:
return {"success": False, "error": "Renewal successful but no order ID returned"}
except exceptions.ClientRequestException as e:
return {"success": False, "error": f"Client request exception: {e.error_code} - {e.error_msg}"}
except Exception as e:
return {"success": False, "error": f"Renewal exception: {str(e)}"}
# ============================================================================
# Unsubscribe
# ============================================================================
def unsubscribe_resources(
ak: str,
sk: str,
security_token: Optional[str],
resource_ids: List[str],
unsubscribe_type: int = 1,
reason: Optional[str] = None,
dry_run: bool = False
) -> Dict[str, Any]:
"""Unsubscribe resources"""
if not isinstance(resource_ids, list):
resource_ids = [resource_ids]
if dry_run:
type_desc = "Immediate unsubscribe" if unsubscribe_type == 1 else "Expire unsubscribe"
return {
"success": True,
"dry_run": True,
"message": "Dry run successful",
"params": {
"resource_ids": resource_ids,
"unsubscribe_type": unsubscribe_type,
"unsubscribe_type_desc": type_desc,
"reason": reason
}
}
try:
client = create_bss_client(ak, sk, security_token)
request = CancelResourcesSubscriptionRequest()
request.body = UnsubscribeResourcesReq(
unsubscribe_type=unsubscribe_type,
resource_ids=resource_ids
)
if reason:
request.body.unsubscribe_reason = reason
response = client.cancel_resources_subscription(request)
if response and hasattr(response, 'order_ids'):
return {
"success": True,
"order_ids": response.order_ids,
"message": "Unsubscribe request submitted"
}
else:
return {"success": False, "error": "Invalid API response format"}
except exceptions.ClientRequestException as e:
return {"success": False, "error": f"Client request exception: {e.error_code} - {e.error_msg}"}
except Exception as e:
return {"success": False, "error": f"Unsubscribe exception: {str(e)}"}
# ============================================================================
# Helper Functions
# ============================================================================
def show_regions():
"""Display all available regions"""
print("Fetching region information...")
regions = fetch_specs_data("regions")
print("\n" + "=" * 60)
print("Flexus L Available Regions")
print("=" * 60)
print(f"{'Region ID':<25} {'Region Name':<20}")
print("-" * 60)
for region_id, region_info in regions.items():
region_name = region_info.get("name", "Unknown")
print(f"{region_id:<25} {region_name:<20}")
print("=" * 60)
def show_images(region: str):
"""Display available images for a region"""
supported, reason = is_region_supported(region)
if not supported:
print(f"[ERROR] {reason}")
return
print(f"Fetching image information for region {region}...")
images = get_available_images(region)
if not images:
print(f"Region {region} has no configured image information")
return
region_name = get_region_name_by_id(region) or region
print("\n" + "=" * 60)
print(f"Region {region} ({region_name}) Available Images")
print("=" * 60)
for img_name, img_data in images.items():
version = img_data.get("version", "")
specs = img_data.get("specs", [])
print(f"\n[IMAGE] {img_name}")
if version:
print(f" |-- Version: {version.replace(chr(10), ', ')}")
if specs:
print(f" |-- Available Specs: {', '.join(specs[:3])}{'...' if len(specs) > 3 else ''}")
print("\n" + "=" * 60)
def show_specs(region: str, image_name: str):
"""Display available specs for an image"""
print(f"Fetching spec information for {image_name} in {region}...")
specs = get_available_specs(region, image_name)
spec_defs = fetch_specs_data("specs")
if not specs:
print(f"[ERROR] Image {image_name} has no configured specs in region {region}")
return
print("\n" + "=" * 70)
print(f"Available Specs for {image_name} in Region {region}")
print("=" * 70)
print(f"{'Spec Code':<30} {'CPU':<8} {'Memory':<8} {'Disk':<8} {'Bandwidth':<8}")
print("-" * 70)
for spec_code in specs:
info = spec_defs.get(spec_code, {})
cpu = info.get("vcpu", "?")
memory = info.get("memory", "?")
disk = info.get("disk", "?")
bandwidth = info.get("bandwidth", "?")
print(f"{spec_code:<30} {cpu} cores {memory}GB {disk}GB {bandwidth}Mbps")
print("=" * 70)
def show_unsubscribe_policy():
"""Display unsubscribe policy"""
print("=" * 60)
print("Huawei Cloud Flexus L Instance Unsubscribe Policy")
print("=" * 60)
print()
print("[Unsubscribe Types]")
print()
print("Type 1: Unsubscribe resource and its renewed periods")
print(" - Effect: Resource stops and releases immediately")
print(" - Refund: Proportional refund based on remaining time")
print()
print("Type 2: Unsubscribe renewal period only")
print(" - Effect: Resource continues until expiration")
print(" - Refund: Refund the renewal portion")
print()
print("[General Rules]")
print(" * Please backup important data before unsubscribing")
print(" * Unsubscribe operation is irreversible")
print("=" * 60)
# ============================================================================
# CLI Interface
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description="Huawei Cloud Flexus L Instance Lifecycle Management Tool",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("--ak", help="Huawei Cloud Access Key AK (can be temporary AK, or set HW_ACCESS_KEY env var)")
parser.add_argument("--sk", help="Huawei Cloud Access Key SK (can be temporary SK, or set HW_SECRET_KEY env var)")
parser.add_argument("--security-token", help="Security token for temporary credentials (required when using temporary AK/SK, or set HW_SECURITY_TOKEN env var)")
parser.add_argument("--region", default="cn-north-4", help="Region ID")
parser.add_argument("--dry-run", action="store_true", help="Dry run")
parser.add_argument("--confirm", action="store_true", help="Force confirm")
subparsers = parser.add_subparsers(dest="command", help="Available commands")
subparsers.add_parser("show-regions", help="Show all available regions")
subparsers.add_parser("show-images", help="Show available images for a region")
show_specs_parser = subparsers.add_parser("show-specs", help="Show available specs for an image")
show_specs_parser.add_argument("--image", required=True, help="Image name")
create_parser = subparsers.add_parser("create-instance", help="Create Flexus L instance")
create_parser.add_argument("--plan-spec", help="Instance spec")
create_parser.add_argument("--image", default="Ubuntu", help="Image name")
create_parser.add_argument("--cpu", type=int, help="CPU cores")
create_parser.add_argument("--memory", type=int, help="Memory in GB")
create_parser.add_argument("--period-num", type=int, default=1)
create_parser.add_argument("--period-type", default="month", choices=["month", "year"])
create_parser.add_argument("--instance-name")
create_parser.add_argument("--auto-renew", type=lambda x: x.lower() != 'false', default=True)
create_parser.add_argument("--auto-pay", type=lambda x: x.lower() != 'false', default=True)
renewal_parser = subparsers.add_parser("renewal", help="Renew instance")
renewal_parser.add_argument("--resource-ids", required=True)
renewal_parser.add_argument("--period-num", type=int, default=1)
renewal_parser.add_argument("--period-type", default="month", choices=["month", "year"])
renewal_parser.add_argument("--auto-pay", type=lambda x: x.lower() != 'false', default=True)
unsubscribe_parser = subparsers.add_parser("unsubscribe", help="Unsubscribe instance")
unsubscribe_parser.add_argument("--resource-ids", required=True)
unsubscribe_parser.add_argument("--type", type=int, choices=[1, 2], default=1)
unsubscribe_parser.add_argument("--reason")
subparsers.add_parser("unsubscribe-policy", help="Show unsubscribe policy")
args = parser.parse_args()
if not args.command:
parser.print_help()
return
try:
if args.command == "show-regions":
show_regions()
elif args.command == "show-images":
show_images(args.region)
elif args.command == "show-specs":
show_specs(args.region, args.image)
elif args.command == "create-instance":
# Get credentials from args or environment variables
ak = args.ak or os.environ.get("HW_ACCESS_KEY")
sk = args.sk or os.environ.get("HW_SECRET_KEY")
security_token = args.security_token or os.environ.get("HW_SECURITY_TOKEN")
if not ak or not sk:
print("Error: --ak and --sk are required (or set HW_ACCESS_KEY and HW_SECRET_KEY env vars)")
print("Note: --security-token is recommended for temporary credentials")
return
supported, reason = is_region_supported(args.region)
if not supported:
print(f"[ERROR] {reason}")
return
# Parse image name and version
image_input = args.image
if ":" in image_input:
image_name, image_version = image_input.split(":", 1)
else:
image_name = image_input
image_version = None
plan_spec = args.plan_spec
if not plan_spec:
if args.cpu and args.memory:
os_type = "windows" if "win" in image_name.lower() or "windows" in image_name.lower() else "linux"
plan_spec = find_matching_spec(args.region, args.cpu, args.memory, os_type, image_name)
if not plan_spec:
print(f"[ERROR] Cannot find matching spec for {args.cpu} cores {args.memory}GB in region {args.region}")
return
print(f"[OK] Auto-matched spec: {plan_spec} (region: {args.region})")
else:
available_specs = get_available_specs(args.region, image_name)
if available_specs:
plan_spec = available_specs[0]
print(f"[OK] Using default spec: {plan_spec}")
else:
print(f"[ERROR] Image {image_name} has no configured specs in region {args.region}")
return
spec_info = get_spec_info(plan_spec)
print("\n" + "=" * 70)
print("Create Flexus L Instance")
print("=" * 70)
print(f"Region: {args.region} ({get_region_name_by_id(args.region) or 'Unknown'})")
print(f"Image: {image_name}")
print(f"Spec: {plan_spec}")
if spec_info:
print(f" |-- CPU: {spec_info.get('vcpu', '?')} cores, Memory: {spec_info.get('memory', '?')}GB")
print(f"Period: {args.period_num} {args.period_type}")
print("=" * 70)
if not args.dry_run and not args.confirm:
print("\n[WARNING] This operation will incur charges!")
confirm = input("Confirm creation? (type 'yes' to confirm): ")
if confirm.lower() != 'yes':
print("Cancelled")
return
result = create_flexus_l_instance(
ak=ak, sk=sk, region=args.region, security_token=security_token,
plan_spec=plan_spec, image_name=image_name, image_version=image_version or "22.04",
period_num=args.period_num, period_type=args.period_type,
instance_name=args.instance_name, auto_renew=args.auto_renew,
auto_pay=args.auto_pay, dry_run=args.dry_run
)
if result["success"]:
if args.dry_run:
print("\n[OK] Dry run successful")
else:
print("\n[OK] Creation successful!")
print(f"Order ID: {result.get('order_id')}")
print(f"Instance ID: {result.get('instance_ids')}")
else:
print(f"\n[ERROR] Creation failed: {result.get('error')}")
elif args.command == "renewal":
# Get credentials from args or environment variables
ak = args.ak or os.environ.get("HW_ACCESS_KEY")
sk = args.sk or os.environ.get("HW_SECRET_KEY")
security_token = args.security_token or os.environ.get("HW_SECURITY_TOKEN")
if not ak or not sk:
print("Error: --ak and --sk are required (or set HW_ACCESS_KEY and HW_SECRET_KEY env vars)")
print("Note: --security-token is recommended for temporary credentials")
return
resource_ids = [rid.strip() for rid in args.resource_ids.split(",")]
print("\n" + "=" * 60)
print("Renew Flexus L Instance")
print("=" * 60)
print(f"Instance: {resource_ids}")
print(f"Period: {args.period_num} {args.period_type}")
print("=" * 60)
if not args.dry_run and not args.confirm:
confirm = input("Confirm renewal? (type 'yes' to confirm): ")
if confirm.lower() != 'yes':
print("Cancelled")
return
result = renewal_resources(
ak=ak, sk=sk, resource_ids=resource_ids, security_token=security_token,
period_num=args.period_num, period_type=args.period_type,
auto_pay=args.auto_pay, dry_run=args.dry_run
)
if result["success"]:
print("\n[OK] Renewal successful!")
print(f"Order ID: {result.get('order_ids')}")
else:
print(f"\n[ERROR] Renewal failed: {result.get('error')}")
elif args.command == "unsubscribe":
# Get credentials from args or environment variables
ak = args.ak or os.environ.get("HW_ACCESS_KEY")
sk = args.sk or os.environ.get("HW_SECRET_KEY")
security_token = args.security_token or os.environ.get("HW_SECURITY_TOKEN")
if not ak or not sk:
print("Error: --ak and --sk are required (or set HW_ACCESS_KEY and HW_SECRET_KEY env vars)")
print("Note: --security-token is recommended for temporary credentials")
return
resource_ids = [rid.strip() for rid in args.resource_ids.split(",")]
print("\n" + "=" * 60)
print("Unsubscribe Flexus L Instance")
print("=" * 60)
print(f"Instance: {resource_ids}")
print(f"Unsubscribe Type: {args.type}")
print("=" * 60)
if not args.dry_run and not args.confirm:
print("\n[WARNING] This operation is irreversible!")
confirm = input("Confirm unsubscribe? (type 'yes' to confirm): ")
if confirm.lower() != 'yes':
print("Cancelled")
return
result = unsubscribe_resources(
ak=ak, sk=sk, resource_ids=resource_ids, security_token=security_token,
unsubscribe_type=args.type, reason=args.reason, dry_run=args.dry_run
)
if result["success"]:
print("\n[OK] Unsubscribe successful!")
print(f"Order ID: {result.get('order_ids')}")
else:
print(f"\n[ERROR] Unsubscribe failed: {result.get('error')}")
elif args.command == "unsubscribe-policy":
show_unsubscribe_policy()
except KeyboardInterrupt:
print("\nOperation cancelled by user")
except Exception as e:
print(f"\nExecution failed: {e}")
if __name__ == "__main__":
main()
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Huawei Cloud Flexus L Instance Spec Code Extractor
Features:
- Fetch image and spec info from official documentation in real-time
- No dependency on local config files
- On-demand calls, returns latest data
Usage:
python3 flexus_specs_extractor.py # Get all data
python3 flexus_specs_extractor.py --specs # Get spec definitions only
python3 flexus_specs_extractor.py --images # Get system images only
python3 flexus_specs_extractor.py --regions # Get region info
"""
import urllib.request
import re
import json
import sys
import time
import random
import gzip
from typing import List, Dict, Optional
from config import REGIONS
# Configuration constants
REQUEST_TIMEOUT = 20
MIN_DELAY = 0.3
MAX_DELAY = 1.0
# Known system image names
KNOWN_IMAGE_NAMES = [
'WindowsServer', 'Ubuntu', 'CentOS', 'Debian', 'Huawei Cloud EulerOS',
'AlmaLinux', 'Rocky Linux', 'openEuler', 'OpenSUSE', 'CoreOS',
'CentOS_Stream', 'Fedora', 'EulerOS',
]
class FlexusSpecsExtractor:
"""Huawei Cloud Flexus L instance spec code extractor"""
def __init__(self):
self.url = "https://support.huaweicloud.com/api-flexusl/create_instance_0001.html"
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9',
'Accept-Language': 'zh-CN,zh;q=0.9',
}
self.html_content = None
def fetch_page(self) -> bool:
"""Fetch page content"""
try:
delay = random.uniform(MIN_DELAY, MAX_DELAY)
time.sleep(delay)
req = urllib.request.Request(self.url, headers=self.headers)
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as response:
if response.status != 200:
return False
raw_data = response.read()
try:
self.html_content = raw_data.decode('utf-8')
except UnicodeDecodeError:
try:
self.html_content = gzip.decompress(raw_data).decode('utf-8')
except Exception:
return False
return True
except Exception:
return False
def extract_all_tables(self) -> List[Dict]:
"""Extract all tables"""
if not self.html_content:
return []
tables = []
table_pattern = r'<table[^>]*>(.*?)</table>'
table_matches = re.findall(table_pattern, self.html_content, re.DOTALL)
for i, table_html in enumerate(table_matches):
rows = self._parse_table_rows(table_html)
if rows:
table_info = {
'index': i,
'rows': rows,
'header': rows[0] if rows else [],
'data': rows[1:] if len(rows) > 1 else [],
'type': 'unknown',
}
table_info['type'] = self._identify_table_type(table_info)
tables.append(table_info)
return tables
def _parse_table_rows(self, table_html: str) -> List[List[str]]:
"""Parse table rows"""
rows = re.findall(r'<tr[^>]*>(.*?)</tr>', table_html, re.DOTALL)
result = []
for row in rows:
cells = re.findall(r'<t[dh][^>]*>(.*?)</t[dh]>', row, re.DOTALL)
clean_cells = []
for cell in cells:
clean = re.sub(r'<[^>]+>', ' ', cell)
clean = re.sub(r' ', ' ', clean)
clean = re.sub(r'\s+', ' ', clean).strip()
clean_cells.append(clean)
if clean_cells and any(c for c in clean_cells):
result.append(clean_cells)
return result
def _identify_table_type(self, table_info: Dict) -> str:
"""Identify table type"""
header = table_info['header']
data = table_info['data']
header_text = ' '.join(header).lower()
# Spec table identification
if 'vcpu' in header_text and '规格编码' in header_text:
if data:
first_col = ' '.join([r[0] if r else '' for r in data[:5]])
if '.win' in first_col:
return 'spec_windows'
elif '.linux' in first_col:
return 'spec_linux'
return 'spec_linux'
# System image table identification
if '系统镜像' in header_text or '镜像名称' in header_text:
return 'system_images'
if data:
first_col_samples = [row[0] if row else '' for row in data[:10]]
first_col_text = ' '.join(first_col_samples)
# Spec table identification
if '.linux' in first_col_text or '.win' in first_col_text:
return 'spec_windows' if '.win' in first_col_text else 'spec_linux'
# System image table identification
for name in KNOWN_IMAGE_NAMES:
if name in first_col_text:
return 'system_images'
return 'unknown'
def get_regions(self) -> Dict:
"""Get region information"""
return REGIONS
def get_spec_definitions(self) -> Dict:
"""Get spec definitions"""
if not self.html_content and not self.fetch_page():
return {}
tables = self.extract_all_tables()
spec_definitions = {}
spec_tables = [t for t in tables if t['type'] in ('spec_linux', 'spec_windows')]
for table in spec_tables:
header = table['header']
data = table['data']
col_map = self._identify_spec_columns(header)
for row in data:
try:
spec_info = self._parse_spec_row(row, col_map)
if spec_info and spec_info.get('spec_code'):
spec_code = spec_info['spec_code']
del spec_info['spec_code']
if self._validate_spec(spec_code, spec_info):
spec_definitions[spec_code] = spec_info
except Exception:
continue
return spec_definitions
def _identify_spec_columns(self, header: List[str]) -> Dict[str, int]:
"""Identify spec table columns"""
col_map = {}
for i, h in enumerate(header):
h_lower = h.lower()
if '规格编码' in h or 'spec' in h_lower:
col_map['spec_code'] = i
elif 'vcpu' in h_lower or 'cpu' in h_lower:
col_map['vcpu'] = i
elif '内存' in h or 'memory' in h_lower:
col_map['memory'] = i
elif '磁盘' in h or 'disk' in h_lower:
col_map['disk'] = i
elif '带宽' in h or 'bandwidth' in h_lower:
col_map['bandwidth'] = i
elif '流量' in h or 'traffic' in h_lower:
col_map['traffic'] = i
col_map.setdefault('spec_code', 0)
col_map.setdefault('vcpu', 1)
col_map.setdefault('memory', 2)
col_map.setdefault('disk', 3)
col_map.setdefault('bandwidth', 4)
col_map.setdefault('traffic', 5)
return col_map
def _parse_spec_row(self, row: List[str], col_map: Dict) -> Optional[Dict]:
"""Parse spec row"""
def extract_number(text: str) -> float:
match = re.search(r'[\d.]+', text)
return float(match.group()) if match else 0
def safe_get(key: str) -> str:
idx = col_map.get(key, -1)
return row[idx] if 0 <= idx < len(row) else ''
try:
spec_code = safe_get('spec_code')
if not spec_code or '规格编码' in spec_code:
return None
if not re.match(r'^[a-z]+\.', spec_code):
return None
os_type = 'windows' if '.win' in spec_code else 'linux'
return {
'spec_code': spec_code,
'vcpu': int(extract_number(safe_get('vcpu'))),
'memory': extract_number(safe_get('memory')),
'disk': int(extract_number(safe_get('disk'))),
'bandwidth': int(extract_number(safe_get('bandwidth'))),
'traffic': int(extract_number(safe_get('traffic'))),
'os': os_type,
}
except (ValueError, IndexError):
return None
def _validate_spec(self, spec_code: str, spec_info: Dict) -> bool:
"""Validate spec data"""
return spec_info['vcpu'] > 0 and spec_info['memory'] > 0 and spec_info['disk'] > 0
def get_system_images(self) -> Dict:
"""Get system images"""
if not self.html_content and not self.fetch_page():
return {}
tables = self.extract_all_tables()
system_images = {}
image_tables = [t for t in tables if t['type'] == 'system_images']
for table in image_tables:
data = table['data']
for row in data:
try:
if len(row) < 7:
continue
image_name = row[0].strip()
is_valid = False
for known in KNOWN_IMAGE_NAMES:
if known.lower() in image_name.lower() or image_name.lower() in known.lower():
is_valid = True
break
if not is_valid:
for i, cell in enumerate(row):
cell_clean = cell.strip()
for known in KNOWN_IMAGE_NAMES:
if known.lower() == cell_clean.lower():
image_name = known
is_valid = True
row = row[i:] + row[:i]
break
if is_valid:
break
if not is_valid:
continue
image_info = self._parse_image_row(row)
if image_info:
system_images[image_name] = image_info
except Exception:
continue
return system_images
def _parse_image_row(self, row: List[str]) -> Optional[Dict]:
"""Parse image row - supports all 6 regions"""
def parse_specs(text: str) -> List[str]:
specs = []
for line in text.split('\n'):
line = line.strip()
if not line or line == '-':
continue
line = re.sub(r'([^)]+)', '', line)
line = re.sub(r'\([^)]+\)', '', line)
for spec in line.split():
if spec and '.' in spec and (spec.endswith('.linux') or spec.endswith('.win')):
specs.append(spec)
return specs
def parse_version(text: str) -> str:
versions = [v.strip() for v in text.split('\n') if v.strip() and v.strip() != '-']
return '\n'.join(versions) if versions else ''
try:
if len(row) < 7:
return None
# Parse base regions from table columns
beijing_version = parse_version(row[1] if len(row) > 1 else '')
beijing_specs = parse_specs(row[2] if len(row) > 2 else '')
hongkong_version = parse_version(row[3] if len(row) > 3 else '')
hongkong_specs = parse_specs(row[4] if len(row) > 4 else '')
guizhou_version = parse_version(row[5] if len(row) > 5 else '')
guizhou_specs = parse_specs(row[6] if len(row) > 6 else '')
return {
# Primary regions from table
'beijing': {
'version': beijing_version,
'specs': beijing_specs,
},
'hongkong': {
'version': hongkong_version,
'specs': hongkong_specs,
},
'guizhou': {
'version': guizhou_version,
'specs': guizhou_specs,
},
# Derived regions (same as base regions per official docs)
'shanghai': {
'version': beijing_version,
'specs': beijing_specs,
},
'guangzhou': {
'version': beijing_version,
'specs': beijing_specs,
},
'singapore': {
'version': hongkong_version,
'specs': hongkong_specs,
},
}
except IndexError:
return None
def get_all(self) -> Dict:
"""Get all data"""
if not self.fetch_page():
return {'error': 'Unable to obtain page data'}
return {
'regions': self.get_regions(),
'spec_definitions': self.get_spec_definitions(),
'system_images': self.get_system_images(),
}
def main():
"""Main function"""
args = sys.argv[1:]
extractor = FlexusSpecsExtractor()
if '--regions' in args:
data = extractor.get_regions()
elif '--specs' in args:
if not extractor.fetch_page():
data = {'error': 'Unable to obtain page data'}
else:
data = extractor.get_spec_definitions()
elif '--images' in args:
if not extractor.fetch_page():
data = {'error': 'Unable to obtain page data'}
else:
data = extractor.get_system_images()
else:
data = extractor.get_all()
print(json.dumps(data, ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
[project]
name = "huawei-cloud-flexus-l-server-manage"
version = "1.0.0"
description = "Huawei Cloud Flexus L instance lifecycle management (create, renewal, unsubscribe)"
dependencies = [
"requests>=2.31.0",
"huaweicloudsdkcore>=3.1.70",
"huaweicloudsdkbss>=3.1.0",
"six>=1.16.0",
"defusedxml>=0.7.1",
"pyasn1>=0.5.0",
"pyyaml>=6.0.0",
"requests-toolbelt>=0.10.1",
"simplejson>=3.19.0",
]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"