Skip to content

add workspace #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
13 changes: 12 additions & 1 deletion tools/c7n_huaweicloud/c7n_huaweicloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@
from huaweicloudsdkram.v1.region.ram_region import RamRegion
from huaweicloudsdkcc.v3 import CcClient, ListCentralNetworksRequest
from huaweicloudsdkcc.v3.region.cc_region import CcRegion
from huaweicloudsdkworkspace.v2 import WorkspaceClient, ListDesktopsDetailRequest
from huaweicloudsdkworkspace.v2.region.workspace_region import WorkspaceRegion


log = logging.getLogger("custodian.huaweicloud.client")

Expand Down Expand Up @@ -300,6 +303,13 @@ def client(self, service):
.with_region(ImsRegion.value_of(self.region))
.build()
)
elif service == "workspace":
client = (
WorkspaceClient.new_builder()
.with_credentials(credentials)
.with_region(WorkspaceRegion.value_of(self.region))
.build()
)
elif (
service == "cbr-backup" or service == "cbr-vault" or service == "cbr-policy"
):
Expand Down Expand Up @@ -474,7 +484,8 @@ def request(self, service):
request = ListOrganizationalUnitsRequest()
elif service == "org-account":
request = ListAccountsRequest()

elif service == "workspace":
request = ListDesktopsDetailRequest()
elif service == "kms":
request = ListKeysRequest()
request.body = ListKeysRequestBody(key_spec="ALL")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@
"huaweicloud.antiddos-eip": "c7n_huaweicloud.resources.antiddos.Eip",
"huaweicloud.kafka": "c7n_huaweicloud.resources.kafka.Kafka",
"huaweicloud.cc-cloud-connection": "c7n_huaweicloud.resources.cc.CloudConnection",
"huaweicloud.workspace-desktop": "c7n_huaweicloud.resources.workspace.Workspace"
}
272 changes: 272 additions & 0 deletions tools/c7n_huaweicloud/c7n_huaweicloud/resources/workspace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
# Copyright The Cloud Custodian Authors.
# SPDX-License-Identifier: Apache-2.0

import logging

from c7n.filters import Filter
from c7n.utils import type_schema, local_session

from c7n_huaweicloud.provider import resources
from c7n_huaweicloud.query import QueryResourceManager, TypeInfo
from c7n_huaweicloud.actions.base import HuaweiCloudBaseAction
from huaweicloudsdkworkspace.v2 import BatchDeleteDesktopsRequest

log = logging.getLogger('custodian.huaweicloud.workspace')


@resources.register('workspace-desktop')
class Workspace(QueryResourceManager):
"""Huawei Cloud Workspace Resource Manager

This resource type manages cloud desktop instances in Huawei Cloud Workspace service.
"""
class resource_type(TypeInfo):
service = 'workspace'
enum_spec = ('list_desktops_detail', 'desktops', 'offset')
id = 'desktop_id'
name = 'computer_name'
tag_resource_type = 'workspace-desktop'
date = 'created'
# Enable configuration audit support
config_resource_support = True

def augment(self, resources):
"""Enhance resource data

This method ensures each resource has a valid ID field and adds additional
information as needed.

:param resources: List of resource objects
:return: Enhanced resource object list
"""
for r in resources:
# Ensure each resource has an ID field
if 'id' not in r and self.resource_type.id in r:
r['id'] = r[self.resource_type.id]

# Convert tags to standard format
if 'tags' in r:
r['Tags'] = self.normalize_tags(r['tags'])

return resources

def normalize_tags(self, tags):
"""Convert tags to standard format

:param tags: Original tag data
:return: Normalized tag dictionary
"""
if not tags:
return {}

if isinstance(tags, dict):
return tags

normalized = {}
for tag in tags:
if isinstance(tag, dict):
if 'key' in tag and 'value' in tag:
normalized[tag['key']] = tag['value']
else:
for k, v in tag.items():
normalized[k] = v
elif isinstance(tag, str) and '=' in tag:
k, v = tag.split('=', 1)
normalized[k] = v

return normalized


@Workspace.filter_registry.register('connection-status')
class ConnectionStatusFilter(Filter):
"""Filter desktops based on user connection information

:example:

.. code-block:: yaml

policies:
- name: find-unregister-desktops
resource: huaweicloud.workspaces
filters:
- type: connection-status
op: eq
value: UNREGISTER
"""
schema = {
'type': 'object',
'properties': {
'type': {'enum': ['connection-status']},
'op': {'enum': ['eq', 'ne', 'in', 'not-in']},
'value': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}
},
'required': ['type', 'op', 'value']
}
schema_alias = False
annotation_key = 'c7n:ConnectionStatus'

def process(self, resources, event=None):
op = self.data.get('op')
expected = self.data.get('value')

results = []
for r in resources:
login_status = r.get('login_status')

if login_status is None:
continue

if op == 'eq' and login_status == expected:
results.append(r)
elif op == 'ne' and login_status != expected:
results.append(r)
elif op == 'in' and login_status in expected:
results.append(r)
elif op == 'not-in' and login_status not in expected:
results.append(r)

return results


@Workspace.action_registry.register('terminate')
class TerminateWorkspace(HuaweiCloudBaseAction):
"""Terminate cloud desktops

This action uses DeleteDesktop or BatchDeleteDesktops API to terminate one or more cloud desktop instances.

:example:

.. code-block:: yaml

policies:
- name: terminate-inactive-workspaces
resource: huaweicloud.workspaces
filters:
- type: connection-status
op: eq
value: UNREGISTER
actions:
- terminate
"""

schema = type_schema('terminate')

def process(self, resources):
"""Process resources in batch

:param resources: List of resources to process
:return: Operation results
"""
if not resources:
return []

return self.batch_terminate(resources)

def batch_terminate(self, resources):
"""Terminate cloud desktops in batch

:param resources: List of resources
:return: Operation results
"""
session = local_session(self.manager.session_factory)
client = session.client('workspace')

# Extract desktop IDs
desktop_ids = [r['id'] for r in resources]

# Process up to 100 at a time
results = []
for i in range(0, len(desktop_ids), 100):
batch = desktop_ids[i:i+100]
try:
request = BatchDeleteDesktopsRequest()
request.body = {"desktop_ids": batch}
response = client.batch_delete_desktops(request)
results.append(response.to_dict())
self.log.info(f"Successfully submitted termination request for {len(batch)} desktops")
except Exception as e:
self.log.error(f"Failed to terminate desktops: {e}")

return results

def perform_action(self, resource):
return super().perform_action(resource)

# Example Policies
"""
Here are some common Huawei Cloud Workspace policy examples:

1. Mark Inactive Desktops Policy:
```yaml
policies:
- name: terminate-inactive-workspaces
resource: huaweicloud.workspaces
filters:
- type: connection-status
op: eq
value: UNREGISTER
actions:
- terminate
```

2. Terminate Marked Inactive Desktops Policy:
```yaml
policies:
- name: terminate-marked-workspaces
resource: huaweicloud.workspaces
description: |
Terminate desktops marked for cleanup
filters:
- type: marked-for-op
op: terminate
tag: custodian_cleanup
actions:
- terminate
```

3. Tag Untagged Desktops:
```yaml
policies:
- name: tag-untagged-workspaces
resource: huaweicloud.workspaces
description: |
Add Owner tag to desktops missing it
filters:
- tag:Owner: absent
actions:
- type: tag
key: Owner
value: Unknown
```

4. Auto-tag Desktop Creator:
```yaml
policies:
- name: tag-workspace-creator
resource: huaweicloud.workspaces
description: |
Listen for desktop creation events and auto-tag creator
mode:
type: cloudtrace
events:
- source: "Workspace"
event: "createDesktop"
ids: "desktop_id"
actions:
- type: auto-tag-user
tag: Creator
```

5. Find Non-compliant Desktops:
```yaml
policies:
- name: find-noncompliant-workspaces
resource: huaweicloud.workspaces
description: |
Find desktops that don't comply with security rules
filters:
- type: config-compliance
rules:
- workspace-security-rule
```
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate, zstd
Connection:
- keep-alive
Content-Type:
- application/json
Host:
- workspace.ap-southeast-1.myhuaweicloud.com
User-Agent:
- huaweicloud-usdk-python/3.0
X-Project-Id:
- ap-southeat-1
X-Sdk-Date:
- 20250427T114555Z
method: GET
uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0
response:
body:
string: '{"desktops":[{"desktop_id":"test-desktop-id","computer_name":"test-desktop","status":"ACTIVE","login_status":"UNREGISTER","user_name":"test-user","created":"2025-04-27T11:45:55Z","tags":[{"key":"environment","value":"testing"}],"security_groups":[{"id":"sg-12345678","name":"default"}],"product":{"product_id":"product-123","flavor_id":"flavor-123","type":"DEDICATED","cpu":"4","memory":"8GB"}}],"total_count":1}'
headers:
Connection:
- keep-alive
Content-Type:
- application/json
Date:
- Sun, 27 Apr 2025 11:45:55 GMT
Server:
- CloudWAF
Set-Cookie:
- HWWAFSESTIME=1745754355448; path=/
- HWWAFSESID=a452b73af1f922b862; path=/
Strict-Transport-Security:
- max-age=31536000; includeSubdomains;
Transfer-Encoding:
- chunked
X-Content-Type-Options:
- nosniff
X-Download-Options:
- noopen
X-Frame-Options:
- SAMEORIGIN
X-Request-Id:
- e81554430f5027311dda7372e5c00f0d
X-XSS-Protection:
- 1; mode=block;
status:
code: 200
message: success
version: 1
Loading