Skip to content

Commit f3baba9

Browse files
add workspace
1 parent 981cdec commit f3baba9

11 files changed

+1096
-2
lines changed

c7n/__pycache__/varfmt.cpython-312.pyc.140643178709184

Whitespace-only changes.

tools/c7n_huaweicloud/c7n_huaweicloud/client.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@
9999
SearchResourceShareAssociationsReqBody,
100100
)
101101
from huaweicloudsdkram.v1.region.ram_region import RamRegion
102+
# 导入华为云Workspace SDK
103+
from huaweicloudsdkworkspace.v2 import WorkspaceClient, ListDesktopsDetailRequest
104+
from huaweicloudsdkworkspace.v2.region.workspace_region import WorkspaceRegion
102105

103106

104107
log = logging.getLogger("custodian.huaweicloud.client")
@@ -294,6 +297,13 @@ def client(self, service):
294297
.with_region(ImsRegion.value_of(self.region))
295298
.build()
296299
)
300+
elif service == "workspace":
301+
client = (
302+
WorkspaceClient.new_builder()
303+
.with_credentials(credentials)
304+
.with_region(WorkspaceRegion.value_of(self.region))
305+
.build()
306+
)
297307
elif (
298308
service == "cbr-backup" or service == "cbr-vault" or service == "cbr-policy"
299309
):
@@ -452,7 +462,8 @@ def request(self, service):
452462
request = ListOrganizationalUnitsRequest()
453463
elif service == "org-account":
454464
request = ListAccountsRequest()
455-
465+
elif service == "workspace":
466+
request = ListDesktopsDetailRequest()
456467
elif service == "kms":
457468
request = ListKeysRequest()
458469
request.body = ListKeysRequestBody(key_spec="ALL")

tools/c7n_huaweicloud/c7n_huaweicloud/resources/resource_map.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@
4141
"huaweicloud.org-unit": "c7n_huaweicloud.resources.organizations.OrgUnit",
4242
"huaweicloud.ram-shared-principals": "c7n_huaweicloud.resources.ram.RAMSharedPrincipals",
4343
"huaweicloud.antiddos-eip": "c7n_huaweicloud.resources.antiddos.Eip",
44-
"huaweicloud.kafka": "c7n_huaweicloud.resources.kafka.Kafka"
44+
"huaweicloud.kafka": "c7n_huaweicloud.resources.kafka.Kafka",
45+
"huaweicloud.workspace-desktop": "c7n_huaweicloud.resources.workspace.Workspace"
4546
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
# Copyright The Cloud Custodian Authors.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
import logging
5+
6+
from c7n.filters import Filter
7+
from c7n.utils import type_schema, local_session
8+
9+
from c7n_huaweicloud.provider import resources
10+
from c7n_huaweicloud.query import QueryResourceManager, TypeInfo
11+
from c7n_huaweicloud.actions.base import HuaweiCloudBaseAction
12+
from huaweicloudsdkworkspace.v2 import BatchDeleteDesktopsRequest
13+
14+
log = logging.getLogger('custodian.huaweicloud.workspace')
15+
16+
17+
@resources.register('workspace-desktop')
18+
class Workspace(QueryResourceManager):
19+
"""Huawei Cloud Workspace Resource Manager
20+
21+
This resource type manages cloud desktop instances in Huawei Cloud Workspace service.
22+
"""
23+
class resource_type(TypeInfo):
24+
service = 'workspace'
25+
enum_spec = ('list_desktops_detail', 'desktops', 'offset')
26+
id = 'desktop_id'
27+
name = 'computer_name'
28+
tag_resource_type = 'workspace-desktop'
29+
date = 'created'
30+
# Enable configuration audit support
31+
config_resource_support = True
32+
33+
def augment(self, resources):
34+
"""Enhance resource data
35+
36+
This method ensures each resource has a valid ID field and adds additional
37+
information as needed.
38+
39+
:param resources: List of resource objects
40+
:return: Enhanced resource object list
41+
"""
42+
for r in resources:
43+
# Ensure each resource has an ID field
44+
if 'id' not in r and self.resource_type.id in r:
45+
r['id'] = r[self.resource_type.id]
46+
47+
# Convert tags to standard format
48+
if 'tags' in r:
49+
r['Tags'] = self.normalize_tags(r['tags'])
50+
51+
return resources
52+
53+
def normalize_tags(self, tags):
54+
"""Convert tags to standard format
55+
56+
:param tags: Original tag data
57+
:return: Normalized tag dictionary
58+
"""
59+
if not tags:
60+
return {}
61+
62+
if isinstance(tags, dict):
63+
return tags
64+
65+
normalized = {}
66+
for tag in tags:
67+
if isinstance(tag, dict):
68+
if 'key' in tag and 'value' in tag:
69+
normalized[tag['key']] = tag['value']
70+
else:
71+
for k, v in tag.items():
72+
normalized[k] = v
73+
elif isinstance(tag, str) and '=' in tag:
74+
k, v = tag.split('=', 1)
75+
normalized[k] = v
76+
77+
return normalized
78+
79+
80+
@Workspace.filter_registry.register('connection-status')
81+
class ConnectionStatusFilter(Filter):
82+
"""Filter desktops based on user connection information
83+
84+
:example:
85+
86+
.. code-block:: yaml
87+
88+
policies:
89+
- name: find-unregister-desktops
90+
resource: huaweicloud.workspaces
91+
filters:
92+
- type: connection-status
93+
op: eq
94+
value: UNREGISTER
95+
"""
96+
schema = {
97+
'type': 'object',
98+
'properties': {
99+
'type': {'enum': ['connection-status']},
100+
'op': {'enum': ['eq', 'ne', 'in', 'not-in']},
101+
'value': {'oneOf': [{'type': 'string'}, {'type': 'array', 'items': {'type': 'string'}}]}
102+
},
103+
'required': ['type', 'op', 'value']
104+
}
105+
schema_alias = False
106+
annotation_key = 'c7n:ConnectionStatus'
107+
108+
def process(self, resources, event=None):
109+
op = self.data.get('op')
110+
expected = self.data.get('value')
111+
112+
results = []
113+
for r in resources:
114+
login_status = r.get('login_status')
115+
116+
if login_status is None:
117+
continue
118+
119+
if op == 'eq' and login_status == expected:
120+
results.append(r)
121+
elif op == 'ne' and login_status != expected:
122+
results.append(r)
123+
elif op == 'in' and login_status in expected:
124+
results.append(r)
125+
elif op == 'not-in' and login_status not in expected:
126+
results.append(r)
127+
128+
return results
129+
130+
131+
@Workspace.action_registry.register('terminate')
132+
class TerminateWorkspace(HuaweiCloudBaseAction):
133+
"""Terminate cloud desktops
134+
135+
This action uses DeleteDesktop or BatchDeleteDesktops API to terminate one or more cloud desktop instances.
136+
137+
:example:
138+
139+
.. code-block:: yaml
140+
141+
policies:
142+
- name: terminate-inactive-workspaces
143+
resource: huaweicloud.workspaces
144+
filters:
145+
- type: connection-status
146+
op: eq
147+
value: UNREGISTER
148+
actions:
149+
- terminate
150+
"""
151+
152+
schema = type_schema('terminate')
153+
154+
def process(self, resources):
155+
"""Process resources in batch
156+
157+
:param resources: List of resources to process
158+
:return: Operation results
159+
"""
160+
if not resources:
161+
return []
162+
163+
return self.batch_terminate(resources)
164+
165+
def batch_terminate(self, resources):
166+
"""Terminate cloud desktops in batch
167+
168+
:param resources: List of resources
169+
:return: Operation results
170+
"""
171+
session = local_session(self.manager.session_factory)
172+
client = session.client('workspace')
173+
174+
# Extract desktop IDs
175+
desktop_ids = [r['id'] for r in resources]
176+
177+
# Process up to 100 at a time
178+
results = []
179+
for i in range(0, len(desktop_ids), 100):
180+
batch = desktop_ids[i:i+100]
181+
try:
182+
request = BatchDeleteDesktopsRequest()
183+
request.body = {"desktop_ids": batch}
184+
response = client.batch_delete_desktops(request)
185+
results.append(response.to_dict())
186+
self.log.info(f"Successfully submitted termination request for {len(batch)} desktops")
187+
except Exception as e:
188+
self.log.error(f"Failed to terminate desktops: {e}")
189+
190+
return results
191+
192+
def perform_action(self, resource):
193+
return super().perform_action(resource)
194+
195+
# Example Policies
196+
"""
197+
Here are some common Huawei Cloud Workspace policy examples:
198+
199+
1. Mark Inactive Desktops Policy:
200+
```yaml
201+
policies:
202+
- name: terminate-inactive-workspaces
203+
resource: huaweicloud.workspaces
204+
filters:
205+
- type: connection-status
206+
op: eq
207+
value: UNREGISTER
208+
actions:
209+
- terminate
210+
```
211+
212+
2. Terminate Marked Inactive Desktops Policy:
213+
```yaml
214+
policies:
215+
- name: terminate-marked-workspaces
216+
resource: huaweicloud.workspaces
217+
description: |
218+
Terminate desktops marked for cleanup
219+
filters:
220+
- type: marked-for-op
221+
op: terminate
222+
tag: custodian_cleanup
223+
actions:
224+
- terminate
225+
```
226+
227+
3. Tag Untagged Desktops:
228+
```yaml
229+
policies:
230+
- name: tag-untagged-workspaces
231+
resource: huaweicloud.workspaces
232+
description: |
233+
Add Owner tag to desktops missing it
234+
filters:
235+
- tag:Owner: absent
236+
actions:
237+
- type: tag
238+
key: Owner
239+
value: Unknown
240+
```
241+
242+
4. Auto-tag Desktop Creator:
243+
```yaml
244+
policies:
245+
- name: tag-workspace-creator
246+
resource: huaweicloud.workspaces
247+
description: |
248+
Listen for desktop creation events and auto-tag creator
249+
mode:
250+
type: cloudtrace
251+
events:
252+
- source: "Workspace"
253+
event: "createDesktop"
254+
ids: "desktop_id"
255+
actions:
256+
- type: auto-tag-user
257+
tag: Creator
258+
```
259+
260+
5. Find Non-compliant Desktops:
261+
```yaml
262+
policies:
263+
- name: find-noncompliant-workspaces
264+
resource: huaweicloud.workspaces
265+
description: |
266+
Find desktops that don't comply with security rules
267+
filters:
268+
- type: config-compliance
269+
rules:
270+
- workspace-security-rule
271+
```
272+
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
interactions:
2+
- request:
3+
body: null
4+
headers:
5+
Accept:
6+
- '*/*'
7+
Accept-Encoding:
8+
- gzip, deflate, zstd
9+
Connection:
10+
- keep-alive
11+
Content-Type:
12+
- application/json
13+
Host:
14+
- workspace.ap-southeast-1.myhuaweicloud.com
15+
User-Agent:
16+
- huaweicloud-usdk-python/3.0
17+
X-Project-Id:
18+
- ap-southeat-1
19+
X-Sdk-Date:
20+
- 20250427T114555Z
21+
method: GET
22+
uri: https://workspace.ap-southeast-1.myhuaweicloud.com/v2/ap-southeat-1/desktops/detail?limit=100&offset=0
23+
response:
24+
body:
25+
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}'
26+
headers:
27+
Connection:
28+
- keep-alive
29+
Content-Type:
30+
- application/json
31+
Date:
32+
- Sun, 27 Apr 2025 11:45:55 GMT
33+
Server:
34+
- CloudWAF
35+
Set-Cookie:
36+
- HWWAFSESTIME=1745754355448; path=/
37+
- HWWAFSESID=a452b73af1f922b862; path=/
38+
Strict-Transport-Security:
39+
- max-age=31536000; includeSubdomains;
40+
Transfer-Encoding:
41+
- chunked
42+
X-Content-Type-Options:
43+
- nosniff
44+
X-Download-Options:
45+
- noopen
46+
X-Frame-Options:
47+
- SAMEORIGIN
48+
X-Request-Id:
49+
- e81554430f5027311dda7372e5c00f0d
50+
X-XSS-Protection:
51+
- 1; mode=block;
52+
status:
53+
code: 200
54+
message: success
55+
version: 1

0 commit comments

Comments
 (0)