Coverage for awsutils / aws_resource_operator.py: 89%

177 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-21 13:47 +0900

1# -*- config: utf8 -*- 

2'''AwsResourceOperator module. 

3 

4Copyright ycookjp 

5https://github.com/ycookjp/ 

6 

7''' 

8 

9import boto3 

10import logging 

11 

12class AwsResourceOperator: 

13 '''Base class for operating AWS resource. 

14  

15 ''' 

16 def get_status(self, resource_id: str): 

17 '''Gets resource status. 

18  

19 Args: 

20 resource_id (str): Resource id. 

21  

22 Returns: 

23 Returns resource status. 

24  

25 ''' 

26 pass 

27 

28 def _get_client(self, service_name: str, region_name: str, 

29 access_key_id: str=None, secret_access_key: str=None): 

30 '''Gets boto3 client. 

31  

32 Args: 

33 seervice_name (str): service name such as ec2, rds ... 

34 aws_access_key_id (str): access key id. 

35 aws_secret_access_key (str): secret access key. 

36 region_name (str): region name. 

37  

38 Returns: 

39 Returns boto3's client instance. 

40  

41 ''' 

42 client = boto3.client(service_name, aws_access_key_id=access_key_id, 

43 aws_secret_access_key=secret_access_key, region_name=region_name) 

44 

45 return client 

46 

47 def start(self, instance_id: str): 

48 '''Starts AWS service instance. 

49  

50 Starts AWS service instance. This method is a abstruct method. 

51  

52 Args: 

53 instance_id (str): Instance ID. 

54  

55 Returns: 

56 Number of started instance(s). 

57  

58 Raises: 

59 Exception: Raises exception if fail to start service instance. 

60  

61 ''' 

62 pass 

63 

64 def start_resources(self, instance_ids: list): 

65 '''Starts AWS resources. 

66  

67 Starts AWS resources. This method is a abstruct method. 

68  

69 Args: 

70 instance_ids (str ...): Instance IDs 

71  

72 Returns: 

73 Number of started instance(s). 

74  

75 Raises: 

76 Exception: Raises exception if fail to start service instance 

77 at latest time. 

78  

79 ''' 

80 pass 

81 

82 def stop(self, instance_id: str): 

83 '''Stops AWS resource instance. 

84  

85 Stops AWS resource instance. This method is a abstruct method. 

86  

87 Args: 

88 instance_id (str): Instance ID 

89  

90 Returns: 

91 Number of stopped instance(s). 

92  

93 Raises: 

94 Exception: Raises exception if fail to stop service instance. 

95  

96 ''' 

97 pass 

98 

99 def stop_resources(self, instance_ids: list): 

100 '''Stops AWS resources. 

101  

102 Stops AWS resources. This method is a abstruct method. 

103  

104 Args: 

105 instance_ids (str ...): Instance IDs 

106  

107 Returns: 

108 Number of stopped instance(s). 

109  

110 Raises: 

111 Exception: Raises exception if fail to stop service instance 

112 at latest time. 

113  

114 ''' 

115 pass 

116 

117class AwsEc2InstanceOperator(AwsResourceOperator): 

118 '''Operator class for EC2 instance. 

119  

120 ''' 

121 _client = None 

122 '''Boto3 ec2 client. 

123 ''' 

124 

125 def __init__(self, region_name: str, access_key_id: str=None, 

126 secret_access_key: str=None): 

127 '''Constructor. 

128  

129 Sets boto3 ec2 client to _client field. 

130  

131 Args: 

132 region_name (str): regian name. 

133 access_key_id (str, optional): access key id. 

134 secret_access_key (str, optional): secret access key. 

135  

136 ''' 

137 self._client = self._get_client('ec2', region_name, access_key_id, 

138 secret_access_key) 

139 

140 def get_status(self, ec2_instance_id: str): 

141 '''Gets EC2 instance status. 

142  

143 Args: 

144 ec2_instance_id (str): EC2 instance id. 

145  

146 Returns: 

147 Returns EC2 instance id status name. 

148 Status name is one of 'pending', 'running', 'shutting-down', 

149 'terminated', 'stopping', 'stopped'. 

150  

151 ''' 

152 response = self._client.describe_instance_status(InstanceIds=[ec2_instance_id]) 

153 

154 status = None 

155 if len(response['InstanceStatuses']) > 0: 

156 status = response['InstanceStatuses'][0]['InstanceState']['Name'] 

157 

158 return status 

159 

160 def start(self, instance_id: str): 

161 '''Starts EC2 instance. 

162  

163 Args: 

164 instance_id (str): EC2 instance id. 

165  

166 Returns: 

167 Number of started instance(s). 

168  

169 Raises: 

170 Exception: Raises exception if fail to start EC2 instance. 

171  

172 ''' 

173 count = 0 

174 

175 self._client.start_instances(InstanceIds=[instance_id]) 

176 count = count + 1 

177 logging.info(f'ec2: \'{instance_id}\' starting ...') 

178 

179 return count 

180 

181 def start_resources(self, instance_ids: list): 

182 '''Starts EC2 instances. 

183  

184 Args: 

185 instance_ids (str ...): EC2 instance IDs 

186  

187 Returns: 

188 Number of started instance(s). 

189  

190 Raises: 

191 Exception: raises exception if fail to start EC2 instance 

192 at latest time. 

193  

194 ''' 

195 count = 0 

196 error = None 

197 

198 for instance_id in instance_ids: 

199 try: 

200 result = self.start(instance_id) 

201 count = count + result 

202 except Exception as e: 

203 error = e 

204 

205 if error != None: 

206 raise error 

207 

208 return count 

209 

210 def stop(self, instance_id: str): 

211 '''Stops EC2 instance. 

212  

213 Args: 

214 instance_id (str): EC2 instance ID. 

215  

216 Returns: 

217 Number of stopped instance(s). 

218  

219 Raises: 

220 Exception: Raises exception if fail to stop EC2 instance. 

221  

222 ''' 

223 count = 0 

224 

225 self._client.stop_instances(InstanceIds=[instance_id]) 

226 count = count + 1 

227 logging.info(f'ec2: \'{instance_id}\' stopping ...') 

228 

229 return count 

230 

231 def stop_resources(self, instance_ids: list): 

232 '''Stops EC2 instances. 

233  

234 Args: 

235 instance_ids (str ...): EC2 instance IDs. 

236  

237 Returns: 

238 Number of stopped instance(s). 

239  

240 Raises: 

241 Exception: Raises exception if fail to stop EC2 instance 

242 at latest time. 

243  

244 ''' 

245 count = 0 

246 error = None 

247 

248 for instance_id in instance_ids: 

249 try: 

250 result = self.stop(instance_id) 

251 count = count + result 

252 except Exception as e: 

253 error = e 

254 

255 if error != None: 

256 raise error 

257 

258 return count 

259 

260class AwsRdsDbClusterOperator(AwsResourceOperator): 

261 '''Operator class for RDS DB cluster. 

262  

263 ''' 

264 _client = None 

265 '''Boto3 rds client. 

266 ''' 

267 

268 def __init__(self, region_name: str, access_key_id: str=None, 

269 secret_access_key: str=None): 

270 '''Constructor. 

271  

272 Sets boto3 rds client to _client field. 

273  

274 Args: 

275 region_name (str): regian name. 

276 access_key_id (str, optional): access key id. 

277 secret_access_key (str, optional): secret access key. 

278  

279 ''' 

280 self._client = self._get_client('rds', region_name, access_key_id, 

281 secret_access_key) 

282 

283 def get_status(self, db_cluster_id: str): 

284 '''Gets RDS DB cluster status. 

285  

286 Args: 

287 db_cluster_id (str): RDS DB cluster id. 

288  

289 Returns: 

290 Returns RDS DB cluster status name. 

291 Status name is on of 'available', 'backing-up', 'backtracking', 

292 'cloning-failed', 'creating', 'deleting', 'failing-over', 

293 'inaccessible-encryption-credentials'. 

294 'inaccessible-encryption-credentials-recoverable', 'maintenance', 

295 'migrating', 'migration-failed', 'modifying', 'promoting', 

296 'renaming', 'resetting-master-credentials', 'starting', 'stopped', 

297 'stopping', 'storage-optimization', 'update-iam-db-auth', 

298 'upgrading'. 

299  

300 ''' 

301 response = self._client.describe_db_clusters(DBClusterIdentifier=db_cluster_id) 

302 

303 status = None 

304 if len(response['DBClusters']) > 0: 304 ↛ 307line 304 didn't jump to line 307 because the condition on line 304 was always true

305 status = response['DBClusters'][0]['Status'] 

306 

307 return status 

308 

309 def start(self, instance_id: str): 

310 '''Starts RDS DB cluster. 

311  

312 If RDS DB cluster status is 'stopped', then starts RDS DB cluster. 

313 Else do nothing. 

314  

315 Args: 

316 instance_id (str): RDS DB cluster ID. 

317  

318 Returns: 

319 Number of started cluster(s). 

320  

321 Raises: 

322 Exception: Raises exception if fail to start RDS cluster. 

323  

324 ''' 

325 count = 0 

326 status = self.get_status(instance_id) 

327 

328 # starts cluster instance only if status is stopped 

329 if status == 'stopped': 

330 self._client.start_db_cluster(DBClusterIdentifier=instance_id) 

331 count = count + 1 

332 logging.info(f'RDS DB cluster: \'{instance_id}\' starting ...') 

333 

334 return count 

335 

336 def start_resources(self, instance_ids: list): 

337 '''Starts RDS DB clusters. 

338  

339 If RDS DB cluster status is 'stopped', then starts RDS DB cluster. 

340 Else do nothing. 

341  

342 Args: 

343 instance_ids (list): RDS DB cluster IDs 

344  

345 Returns: 

346 Number of started cluster(s). 

347  

348 Raises: 

349 Exception: Raises exception if fail to start RDS cluster at 

350 latest time. 

351  

352 ''' 

353 count = 0 

354 error = None 

355 

356 for instance_id in instance_ids: 

357 try: 

358 result = self.start(instance_id) 

359 count = count + result 

360 except Exception as e: 

361 error = e 

362 

363 if error != None: 

364 raise error 

365 

366 return count 

367 

368 def stop(self, instance_id: str): 

369 '''Stops RDS DB cluster. 

370  

371 If RDS DB instance status is 'available', then stops RDS DB instance. 

372 Else do nothing. 

373  

374 Args: 

375 instance_id (str): RDS DB cluster ID. 

376  

377 Returns: 

378 Number of stopped RDS DB cluster. 

379  

380 Raises: 

381 Exception: Raises exception if fail to stop RDS cluster. 

382  

383 ''' 

384 count = 0 

385 status = self.get_status(instance_id) 

386 

387 # Stops cluster instance only if status is available 

388 if status == 'available': 

389 self._client.stop_db_cluster(DBClusterIdentifier=instance_id) 

390 count = count + 1 

391 logging.info(f'RDS DB cluster: \'{instance_id}\' stopping ...') 

392 

393 return count 

394 

395 def stop_resources(self, instance_ids: list): 

396 '''Stops RDS DB clusters. 

397  

398 If RDS DB instance status is 'available', then stops RDS DB instance. 

399 Else do nothing. 

400  

401 Args: 

402 instance_ids (str ...): RDS DB cluster IDs. 

403  

404 Returns: 

405 Number of stopped RDS DB cluster(s). 

406  

407 Raises: 

408 Exception: Raises exception if fail to stop RDS cluster at latest time. 

409  

410 ''' 

411 count = 0 

412 error = None 

413 

414 for instance_id in instance_ids: 

415 try: 

416 result = self.stop(instance_id) 

417 count = count + result 

418 except Exception as e: 

419 error = e 

420 

421 if error != None: 

422 raise error 

423 

424 return count 

425 

426class AwsRdsDbInstanceOperator(AwsResourceOperator): 

427 '''Operator class for RDS instance. 

428  

429 ''' 

430 _client = None 

431 '''Boto3 rds client. 

432 ''' 

433 

434 def __init__(self, region_name: str, access_key_id: str=None, 

435 secret_access_key: str=None): 

436 '''Constructor. 

437  

438 Sets boto3 rds client to _client instance variable. 

439  

440 Args: 

441 region_name (str): regian name. 

442 access_key_id (str, optional): access key id. 

443 secret_access_key (str, optional): secret access key. 

444  

445 ''' 

446 self._client = self._get_client('rds', region_name,access_key_id, 

447 secret_access_key) 

448 

449 def get_status(self, db_instance_id: str): 

450 '''Gets RDS DB instance status. 

451  

452 Args: 

453 db_instance_id (str): RDS DB instance id. 

454  

455 Returns: 

456 Returns RDS DB instance status name. 

457 Status name is one of 'available', 'backing-up', 

458 'configuring-enhanced-monitoring', 'configuring-iam-database-auth', 

459 'configuring-log-exports', 'converting-to-vpc', 'creating', 

460 'deleting', 'failed', 'inaccessible-encryption-credentials', 

461 'inaccessible-encryption-credentials-recoverable', 

462 'incompatible-network', 'incompatible-option-group', 

463 'incompatible-parameters', 'incompatible-restore', 

464 'insufficient-capacity', 'maintenance', 'modifying', 

465 'moving-to-vpc', 'rebooting', 'resetting-master-credentials', 

466 'renaming', 'restore-error', 'starting', 'stopped', 'stopping', 

467 'storage-full', 'storage-optimization', 'upgrading'. 

468  

469 ''' 

470 response = self._client.describe_db_instances(DBInstanceIdentifier=db_instance_id) 

471 

472 status = None 

473 if len(response['DBInstances']) > 0: 473 ↛ 476line 473 didn't jump to line 476 because the condition on line 473 was always true

474 status = response['DBInstances'][0]['DBInstanceStatus'] 

475 

476 return status 

477 

478 def start(self, instance_id: str): 

479 '''Starts RDS DB instance. 

480  

481 If RDS DB instance status is stopped, then starts RDS DB instance. 

482 Else do nothing. 

483  

484 Args: 

485 instance_id (str): RDS DB instance ID. 

486  

487 Returns: 

488 Number of started instance(s). 

489  

490 Raises: 

491 Exception: Raises exception if fail to start RDS instance. 

492  

493 ''' 

494 count = 0 

495 status = self.get_status(instance_id) 

496 

497 # starts cluster instance only if status is stopped 

498 if status == 'stopped': 

499 self._client.start_db_instance(DBInstanceIdentifier=instance_id) 

500 count = count + 1 

501 logging.info(f'RDS instance: \'{instance_id}\' starting ...') 

502 

503 return count 

504 

505 def start_resources(self, instance_ids: list): 

506 '''Starts RDS DB instances. 

507  

508 If RDS DB instance status is stopped, then starts RDS DB instance. 

509 Else do nothing. 

510  

511 Args: 

512 instance_ids (list): RDS DB instance IDs 

513  

514 Returns: 

515 Number of started instance(s). 

516  

517 Raises: 

518 Exception: Raises exception if fail to start RDS DB instance 

519 at latest time. 

520  

521 ''' 

522 count = 0 

523 error = None 

524 

525 for instance_id in instance_ids: 

526 try: 

527 result = self.start(instance_id) 

528 count = count + result 

529 except Exception as e: 

530 error = e 

531 

532 if error != None: 

533 raise error 

534 

535 return count 

536 

537 def stop(self, instance_id: str): 

538 '''Stops RDS DB instance. 

539  

540 If RDS DB instance status is available, then stops RDS DB instance. 

541 Else do nothing. 

542  

543 Args: 

544 instance_id (str): RDS DB instance ID. 

545  

546 Returns: 

547 Number of stopped instance(s). 

548  

549 Raises: 

550 Exception: Raises exception if fail to stop RDS DB instance. 

551  

552 ''' 

553 count = 0 

554 status = self.get_status(instance_id) 

555 

556 # starts cluster instance only if status is available 

557 if status == 'available': 

558 self._client.stop_db_instance(DBInstanceIdentifier=instance_id) 

559 count = count + 1 

560 logging.info(f'RDS instance: \'{instance_id}\' stopping ...') 

561 

562 return count 

563 

564 def stop_resources(self, instance_ids: list): 

565 '''Stops RDS DB instances. 

566  

567 If RDS DB instance status is available, then stops RDS DB instance. 

568 Else do nothing. 

569  

570 Args: 

571 instance_ids (str ...): RDS instance IDs. 

572  

573 Returns: 

574 Number of stopped instance(s). 

575  

576 Raises: 

577 Exception: Raises exception if fail to stop RDS DB instance 

578 at latest time. 

579  

580 ''' 

581 count = 0 

582 error = None 

583 

584 for instance_id in instance_ids: 

585 try: 

586 result = self.stop(instance_id) 

587 count = count + result 

588 except Exception as e: 

589 error = e 

590 

591 if error != None: 

592 raise error 

593 

594 return count 

595 

596class AwsResourceOperatorFactory: 

597 '''Factory class for generating AwsResourceOperator instance. 

598  

599 ''' 

600 

601 @staticmethod 

602 def create(resource_type: str, region_name: str, 

603 access_key_id: str=None, secret_access_key: str=None): 

604 '''Creates AWS instance operator's instance. 

605  

606 Args: 

607 service_name (str): AWS service name. Available service names 

608 are followings things. 

609 - ec2.instance 

610 - rds.db_cluster 

611 - rds.db_instance 

612  

613 Returns: 

614 AwsResourceOperator: Returns created AWS instance operator's 

615 instance. 

616  

617 Raises: 

618 RuntimeError: If service_name is incorrect. 

619  

620 ''' 

621 

622 operator: AwsResourceOperator = None 

623 

624 if resource_type == 'ec2.instance': 

625 operator = AwsEc2InstanceOperator( 

626 region_name, access_key_id, secret_access_key) 

627 elif resource_type == 'rds.db_cluster': 

628 operator = AwsRdsDbClusterOperator( 

629 region_name, access_key_id,secret_access_key) 

630 elif resource_type == 'rds.db_instance': 

631 operator = AwsRdsDbInstanceOperator( 

632 region_name, access_key_id,secret_access_key) 

633 else: 

634 raise RuntimeError(f'Cloud not create AwsResourceOperator class from class name:{resource_type}') 

635 

636 return operator