1# encoding: utf-8
2'''
3Created on 2025年3月4日
4
5@author: jingyu.wy
6'''
7import json
8import os
9import types
10
11from aliyunsdkcore.auth.credentials import AccessKeyCredential,\
12 StsTokenCredential
13from aliyunsdkcore.client import AcsClient
14from aliyunsdkcore.request import CommonRequest
15from footmark.connection import ACSQueryConnection
16
17
18try:
19 import footmark
20 import footmark.ecs
21 import footmark.slb
22 import footmark.vpc
23 import footmark.rds
24 import footmark.ess
25 import footmark.sts
26 import footmark.dns
27 import footmark.ram
28 import footmark.ros
29 import footmark.oos
30 import footmark.oss
31 import footmark.market
32 HAS_FOOTMARK = True
33except ImportError:
34 HAS_FOOTMARK = False
35
36APSARASTACK_ENDPOINTS = {
37 "vpc": ("vpc-internal.%(domain)s", "vpc-internal.%(region)s.%(domain)s",),
38 "ecs": ("ecs-internal.%(domain)s", "ecs-internal.%(region)s.%(domain)s",),
39 "dns": ("dns-control.pop.%(domain)s", "dns-control.pop.%(region)s.%(domain)s",),
40 "rds": ("rds.%(domain)s", "rds.%(region)s.%(domain)s",),
41 "slb": ("slb-vpc.%(domain)s", "slb-vpc.%(region)s.%(domain)s",),
42 "ascm": ("ascm.%(domain)s", "ascm.%(region)s.%(domain)s",),
43 "ess": ("ess.%(domain)s", "ess.%(region)s.%(domain)s",),
44 "ram": ("ascm.%(domain)s", "ascm.%(region)s.%(domain)s",),
45 }
46
47
48class AnsibleACSError(Exception):
49 pass
50
51
52def get_acs_connection_info(params):
53
54 return dict(
55 acs_access_key_id=params.get('apsarastack_access_key'),
56 acs_secret_access_key=params.get('apsarastack_secret_key'),
57 security_token=params.get('apsarastack_security_token'),
58 ecs_role_name=params.get('ecs_role_name'),
59 user_agent='Ansible-Provider-Apsarastack',
60 )
61
62
63def get_assume_role(params):
64 """ Return new params """
65 sts_params = get_acs_connection_info(params)
66 assume_role = {}
67 if params.get('assume_role'):
68 assume_role['apsarastack_assume_role_arn'] = params['assume_role'].get('role_arn')
69 assume_role['apsarastack_assume_role_session_name'] = params['assume_role'].get('session_name')
70 assume_role['apsarastack_assume_role_session_expiration'] = params['assume_role'].get('session_expiration')
71 assume_role['apsarastack_assume_role_policy'] = params['assume_role'].get('policy')
72
73 assume_role_params = {
74 'role_arn': params.get('apsarastack_assume_role_arn') if params.get('apsarastack_assume_role_arn') else assume_role.get('apsarastack_assume_role_arn'),
75 'role_session_name': params.get('apsarastack_assume_role_session_name') if params.get('apsarastack_assume_role_session_name')
76 else assume_role.get('apsarastack_assume_role_session_name'),
77 'duration_seconds': params.get('apsarastack_assume_role_session_expiration') if params.get('apsarastack_assume_role_session_expiration')
78 else assume_role.get('apsarastack_assume_role_session_expiration', 3600),
79 'policy': assume_role.get('apsarastack_assume_role_policy', {})
80 }
81
82 try:
83 sts = connect_to_acs(footmark.sts, params.get('apsarastack_region'), **sts_params).assume_role(**assume_role_params).read()
84 sts_params['acs_access_key_id'], sts_params['acs_secret_access_key'], sts_params['security_token'] \
85 = sts['access_key_id'], sts['access_key_secret'], sts['security_token']
86 except AnsibleACSError as e:
87 params.fail_json(msg=str(e))
88 return sts_params
89
90
91def get_profile(params):
92 if not params['apsarastack_access_key'] and not params['ecs_role_name'] and params['profile']:
93 path = params['shared_credentials_file'] if params['shared_credentials_file'] else os.getenv('HOME') + '/.apsarastack/config.json'
94 auth = {}
95 with open(path, 'r') as f:
96 for pro in json.load(f)['profiles']:
97 if params['profile'] == pro['name']:
98 auth = pro
99 if auth:
100 if auth['mode'] == 'AK' and auth.get('access_key_id') and auth.get('access_key_secret'):
101 params['apsarastack_access_key'] = auth.get('access_key_id')
102 params['apsarastack_secret_key'] = auth.get('access_key_secret')
103 params['apsarastack_region'] = auth.get('region_id')
104 params = get_acs_connection_info(params)
105 elif auth['mode'] == 'StsToken' and auth.get('access_key_id') and auth.get('access_key_secret') and auth.get('sts_token'):
106 params['apsarastack_access_key'] = auth.get('access_key_id')
107 params['apsarastack_secret_key'] = auth.get('access_key_secret')
108 params['security_token'] = auth.get('sts_token')
109 params['apsarastack_region'] = auth.get('region_id')
110 params = get_acs_connection_info(params)
111 elif params.get('apsarastack_assume_role_arn') or params.get('assume_role'):
112 params = get_assume_role(params)
113 else:
114 params = get_acs_connection_info(params)
115 return params
116
117
118def get_endpoint(domain:str, popcode:str, region:str, is_center_region:bool) -> str:
119 index = 0 if is_center_region else 1
120 endpoint_template = APSARASTACK_ENDPOINTS[popcode][index]
121 return endpoint_template % {"domain":domain, "region":region}
122
123def connect_to_acs(acs_module, modules_params:dict, **params):
124 conn = acs_module.connect_to_region(modules_params['apsarastack_region'], **params)
125 popcode = acs_module.__name__.split('.')[-1]
126 conn._endpoint = get_endpoint(
127 modules_params['apsarastack_domain'], popcode,
128 modules_params['apsarastack_region'], modules_params['apsarastack_is_center_region']
129 )
130 conn._default_headers = {
131 "x-acs-organizationid": modules_params['apsarastack_department'],
132 "x-acs-resourcegroupid": modules_params['apsarastack_resourcegroup'],
133 "x-acs-regionid": modules_params['apsarastack_region'],
134 "x-acs-request-version": "v1",
135 # "RegionId": client.RegionId, // ASAPI
136 # "x-acs-organizationid": client.Department,
137 # "x-acs-resourcegroupid": client.ResourceGroup,
138 # "x-acs-regionid": client.RegionId,
139 # "x-acs-request-version": "v1",
140 # "x-acs-asapi-product": popcode,
141 # "x-ascm-product-name": popcode,
142 }
143 def import_request(self, action):
144 request = ACSQueryConnection.import_request(self, action)
145 request.set_endpoint(conn._endpoint)
146 request.set_headers(conn._default_headers)
147 return request
148
149 conn.import_request = types.MethodType(import_request, conn)
150
151 return conn
152
153
154class OssConn(object):
155 def __init__(self, modules_params):
156 self.region = modules_params.get('region')
157 self.security_token = modules_params.get('security_token')
158 self.acs_access_key_id = modules_params.get('acs_access_key_id')
159 self.acs_secret_access_key = modules_params.get('acs_secret_access_key')
160 self.user_agent = modules_params.get('user_agent')
161def connect_to_bucket(acs_module, modules_params:dict, **params):
162 conn = OssConn(params)
163 popcode = acs_module.__name__.split('.')[-1]
164 conn._endpoint = get_endpoint(
165 modules_params['apsarastack_domain'], popcode,
166 modules_params['apsarastack_region'], modules_params['apsarastack_is_center_region']
167 )
168 conn._default_headers = {
169 "x-acs-organizationid": modules_params['apsarastack_department'],
170 "x-acs-resourcegroupid": modules_params['apsarastack_resourcegroup'],
171 "x-acs-regionid": modules_params['apsarastack_region'],
172 "x-acs-request-version": "v1",
173 }
174 def import_request(self, action):
175 request = ACSQueryConnection.import_request(self, action)
176 request.set_endpoint(conn._endpoint)
177 request.set_headers(conn._default_headers)
178 return request
179
180 conn.import_request = types.MethodType(import_request, conn)
181
182 return conn
183
184def connect_to_universal(popcode, modules_params:dict, **params):
185 conn = ACSQueryConnection(region=modules_params['apsarastack_region'], **params)
186 popcode = "ascm"
187 conn._endpoint = get_endpoint(
188 modules_params['apsarastack_domain'], popcode,
189 modules_params['apsarastack_region'], modules_params['apsarastack_is_center_region']
190 )
191 conn._default_headers = {
192 "x-acs-organizationid": modules_params['apsarastack_department'],
193 "x-acs-resourcegroupid": modules_params['apsarastack_resourcegroup'],
194 "x-acs-regionid": modules_params['apsarastack_region'],
195 "x-acs-request-version": "v1",
196 }
197
198 def import_request(self, action):
199 request = ACSQueryConnection.import_request(self, action)
200 request.set_endpoint(conn._endpoint)
201 request.set_headers(conn._default_headers)
202 return request
203
204 conn.import_request = types.MethodType(import_request, conn)
205
206 return conn
207
208
209def do_common_request(conn, method:str, popcode: str, version:str, api_name:str, pattern:str="", headers:dict={}, query:dict={}, body:dict=None) -> dict:
210 if not conn.security_token:
211 credentials = AccessKeyCredential(conn.acs_access_key_id, conn.acs_secret_access_key)
212 else:
213 credentials = StsTokenCredential(conn.acs_access_key_id, conn.acs_secret_access_key, conn.security_token)
214 # 创建AcsClient连接,timeout设置请求超时时间(单位:ms)
215 client = AcsClient(region_id=conn.region, credential=credentials, timeout=10000)
216 # 创建API请求
217 request = CommonRequest()
218 # 产品接口信息参数
219 request.set_product(popcode)
220 request.set_version(version)
221 request.set_action_name(api_name)
222 if pattern:
223 request.set_uri_pattern(pattern)
224 request.add_header("x-acs-asapi-product", popcode)
225 request.add_header("x-ascm-product-name", popcode)
226 request.add_header("RegionId", conn.region)
227 # 云产品的Endpoint地址
228 request.set_domain(conn._endpoint)
229 request.set_endpoint(conn._endpoint)
230 # 设置请求方式
231 request.set_method(method)
232 # 设置请求协议类型
233 request.set_protocol_type('http')
234
235 # 阿里云核心库SDK发起API请求时,可以设置四种类型参数(Path/Query/Body/Header)
236 # Path参数用于对请求Request中设置的UriPattern进行变量替换
237 # Query参数用于对请求Request中的URL参数进行设置(一般用于GET请求)
238 # Body参数用于对请求Request中的HTTP Content进行设置(一般用于POST/PUT请求),在设置Body参数时,需要同时设置HTTP Content-Type,目前支持JSON和FORM两种格式
239 # Header参数用于对请求Request中的HTTP Header进行设置
240
241 # 设置Headers
242 # 设置身份标识,标识调用来源,无实际作用,可随意设置,必填项
243 for k, v in conn._default_headers.items():
244 request.add_header(k ,v)
245 for k, v in headers.items():
246 request.add_header(k ,v)
247
248 request.add_header("x-acs-caller-sdk-source", conn.user_agent)
249 request.set_user_agent(conn.user_agent)
250
251 if query:
252 request.set_query_params(query)
253
254 if body:
255 request.set_content_type('application/x-www-form-urlencoded')
256
257 request.set_body_params(body)
258
259 response = client.do_action_with_exception(request)
260
261 return json.loads(response)
262
263
264def ecs_connect(module):
265 """ Return an ecs connection"""
266 ecs_params = get_profile(module.params)
267 # If we have a region specified, connect to its endpoint.
268 region = module.params.get('apsarastack_region')
269 if region:
270 try:
271 ecs = connect_to_acs(footmark.ecs, module.params, **ecs_params)
272 except AnsibleACSError as e:
273 module.fail_json(msg=str(e))
274 # Otherwise, no region so we fallback to the old connection method
275 return ecs
276
277
278def vpc_connect(module):
279 """ Return an vpc connection"""
280 vpc_params = get_profile(module.params)
281 # If we have a region specified, connect to its endpoint.
282 try:
283 vpc = connect_to_acs(footmark.vpc, module.params, **vpc_params)
284 except AnsibleACSError as e:
285 module.fail_json(msg=str(e))
286 # Otherwise, no region so we fallback to the old connection method
287 return vpc
288
289
290def dns_connect(module):
291 """ Return an dns connection"""
292 dns_params = get_profile(module.params)
293 try:
294 dns = connect_to_acs(footmark.dns, module.params, **dns_params)
295 except AnsibleACSError as e:
296 module.fail_json(msg=str(e))
297 # Otherwise, no region so we fallback to the old connection method
298 return dns
299
300def rds_connect(module):
301 """ Return an rds connection"""
302 rds_params = get_profile(module.params)
303 try:
304 rds = connect_to_acs(footmark.rds, module.params, **rds_params)
305 except AnsibleACSError as e:
306 module.fail_json(msg=str(e))
307 # Otherwise, no region so we fallback to the old connection method
308 return rds
309
310
311def slb_connect(module):
312 """ Return an slb connection"""
313 slb_params = get_profile(module.params)
314 try:
315 slb = connect_to_acs(footmark.slb, module.params, **slb_params)
316 except AnsibleACSError as e:
317 module.fail_json(msg=str(e))
318 # Otherwise, no region so we fallback to the old connection method
319 return slb
320
321
322def ascm_connect(module):
323 """ Return an ascm connection"""
324 ascm_params = get_profile(module.params)
325 try:
326 ascm = connect_to_universal("ascm", module.params, **ascm_params)
327 except AnsibleACSError as e:
328 module.fail_json(msg=str(e))
329 # Otherwise, no region so we fallback to the old connection method
330 return ascm
331
332
333def ossbucket_connect(module):
334 """ Return an oss bucket connection"""
335 oss_params = get_profile(module.params)
336 del oss_params["ecs_role_name"]
337 try:
338 oss_bucket = connect_to_bucket(footmark.oss, module.params, **oss_params)
339 except AnsibleACSError as e:
340 module.fail_json(msg=str(e))
341 # Otherwise, no region so we fallback to the old connection method
342 return oss_bucket
343
344
345def ossservice_connect(module):
346 """ Return an oss service connection"""
347 rds_params = get_profile(module.params)
348 try:
349 oss_service = connect_to_acs(footmark.oss, module.params, **rds_params)
350 except AnsibleACSError as e:
351 module.fail_json(msg=str(e))
352 # Otherwise, no region so we fallback to the old connection method
353 return oss_service
354
355def ess_connect(module):
356 """ Return an ess service connection"""
357 ess_params = get_profile(module.params)
358 try:
359 ess = connect_to_acs(footmark.ess, module.params, **ess_params)
360 except AnsibleACSError as e:
361 module.fail_json(msg=str(e))
362 # Otherwise, no region so we fallback to the old connection method
363 return ess
364
365
366def ram_connect(module):
367 """ Return an ram service connection"""
368 ram_params = get_profile(module.params)
369 try:
370 ram = connect_to_acs(footmark.ram, module.params, **ram_params)
371 except AnsibleACSError as e:
372 module.fail_json(msg=str(e))
373 # Otherwise, no region so we fallback to the old connection method
374 return ram