runbooks 0.7.0__py3-none-any.whl → 0.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/__init__.py +87 -37
- runbooks/cfat/README.md +300 -49
- runbooks/cfat/__init__.py +2 -2
- runbooks/finops/__init__.py +1 -1
- runbooks/finops/cli.py +1 -1
- runbooks/inventory/collectors/__init__.py +8 -0
- runbooks/inventory/collectors/aws_management.py +791 -0
- runbooks/inventory/collectors/aws_networking.py +3 -3
- runbooks/main.py +3389 -782
- runbooks/operate/__init__.py +207 -0
- runbooks/operate/base.py +311 -0
- runbooks/operate/cloudformation_operations.py +619 -0
- runbooks/operate/cloudwatch_operations.py +496 -0
- runbooks/operate/dynamodb_operations.py +812 -0
- runbooks/operate/ec2_operations.py +926 -0
- runbooks/operate/iam_operations.py +569 -0
- runbooks/operate/s3_operations.py +1211 -0
- runbooks/operate/tagging_operations.py +655 -0
- runbooks/remediation/CLAUDE.md +100 -0
- runbooks/remediation/DOME9.md +218 -0
- runbooks/remediation/README.md +26 -0
- runbooks/remediation/Tests/__init__.py +0 -0
- runbooks/remediation/Tests/update_policy.py +74 -0
- runbooks/remediation/__init__.py +95 -0
- runbooks/remediation/acm_cert_expired_unused.py +98 -0
- runbooks/remediation/acm_remediation.py +875 -0
- runbooks/remediation/api_gateway_list.py +167 -0
- runbooks/remediation/base.py +643 -0
- runbooks/remediation/cloudtrail_remediation.py +908 -0
- runbooks/remediation/cloudtrail_s3_modifications.py +296 -0
- runbooks/remediation/cognito_active_users.py +78 -0
- runbooks/remediation/cognito_remediation.py +856 -0
- runbooks/remediation/cognito_user_password_reset.py +163 -0
- runbooks/remediation/commons.py +455 -0
- runbooks/remediation/dynamodb_optimize.py +155 -0
- runbooks/remediation/dynamodb_remediation.py +744 -0
- runbooks/remediation/dynamodb_server_side_encryption.py +108 -0
- runbooks/remediation/ec2_public_ips.py +134 -0
- runbooks/remediation/ec2_remediation.py +892 -0
- runbooks/remediation/ec2_subnet_disable_auto_ip_assignment.py +72 -0
- runbooks/remediation/ec2_unattached_ebs_volumes.py +448 -0
- runbooks/remediation/ec2_unused_security_groups.py +202 -0
- runbooks/remediation/kms_enable_key_rotation.py +651 -0
- runbooks/remediation/kms_remediation.py +717 -0
- runbooks/remediation/lambda_list.py +243 -0
- runbooks/remediation/lambda_remediation.py +971 -0
- runbooks/remediation/multi_account.py +569 -0
- runbooks/remediation/rds_instance_list.py +199 -0
- runbooks/remediation/rds_remediation.py +873 -0
- runbooks/remediation/rds_snapshot_list.py +192 -0
- runbooks/remediation/requirements.txt +118 -0
- runbooks/remediation/s3_block_public_access.py +159 -0
- runbooks/remediation/s3_bucket_public_access.py +143 -0
- runbooks/remediation/s3_disable_static_website_hosting.py +74 -0
- runbooks/remediation/s3_downloader.py +215 -0
- runbooks/remediation/s3_enable_access_logging.py +562 -0
- runbooks/remediation/s3_encryption.py +526 -0
- runbooks/remediation/s3_force_ssl_secure_policy.py +143 -0
- runbooks/remediation/s3_list.py +141 -0
- runbooks/remediation/s3_object_search.py +201 -0
- runbooks/remediation/s3_remediation.py +816 -0
- runbooks/remediation/scan_for_phrase.py +425 -0
- runbooks/remediation/workspaces_list.py +220 -0
- runbooks/security/__init__.py +9 -10
- runbooks/security/security_baseline_tester.py +4 -2
- runbooks-0.7.6.dist-info/METADATA +608 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/RECORD +84 -76
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/entry_points.txt +0 -1
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/top_level.txt +0 -1
- jupyter-agent/.env +0 -2
- jupyter-agent/.env.template +0 -2
- jupyter-agent/.gitattributes +0 -35
- jupyter-agent/.gradio/certificate.pem +0 -31
- jupyter-agent/README.md +0 -16
- jupyter-agent/__main__.log +0 -8
- jupyter-agent/app.py +0 -256
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +0 -154
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +0 -123
- jupyter-agent/requirements.txt +0 -9
- jupyter-agent/tmp/4ojbs8a02ir/jupyter-agent.ipynb +0 -68
- jupyter-agent/tmp/cm5iasgpm3p/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/crqbsseag5/jupyter-agent.ipynb +0 -91
- jupyter-agent/tmp/hohanq1u097/jupyter-agent.ipynb +0 -57
- jupyter-agent/tmp/jns1sam29wm/jupyter-agent.ipynb +0 -53
- jupyter-agent/tmp/jupyter-agent.ipynb +0 -27
- jupyter-agent/utils.py +0 -409
- runbooks/aws/__init__.py +0 -58
- runbooks/aws/dynamodb_operations.py +0 -231
- runbooks/aws/ec2_copy_image_cross-region.py +0 -195
- runbooks/aws/ec2_describe_instances.py +0 -202
- runbooks/aws/ec2_ebs_snapshots_delete.py +0 -186
- runbooks/aws/ec2_run_instances.py +0 -213
- runbooks/aws/ec2_start_stop_instances.py +0 -212
- runbooks/aws/ec2_terminate_instances.py +0 -143
- runbooks/aws/ec2_unused_eips.py +0 -196
- runbooks/aws/ec2_unused_volumes.py +0 -188
- runbooks/aws/s3_create_bucket.py +0 -142
- runbooks/aws/s3_list_buckets.py +0 -152
- runbooks/aws/s3_list_objects.py +0 -156
- runbooks/aws/s3_object_operations.py +0 -183
- runbooks/aws/tagging_lambda_handler.py +0 -183
- runbooks/inventory/FAILED_SCRIPTS_TROUBLESHOOTING.md +0 -619
- runbooks/inventory/PASSED_SCRIPTS_GUIDE.md +0 -738
- runbooks/inventory/aws_organization.png +0 -0
- runbooks/inventory/cfn_move_stack_instances.py +0 -1526
- runbooks/inventory/delete_s3_buckets_objects.py +0 -169
- runbooks/inventory/lockdown_cfn_stackset_role.py +0 -224
- runbooks/inventory/update_aws_actions.py +0 -173
- runbooks/inventory/update_cfn_stacksets.py +0 -1215
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +0 -294
- runbooks/inventory/update_iam_roles_cross_accounts.py +0 -478
- runbooks/inventory/update_s3_public_access_block.py +0 -539
- runbooks/organizations/__init__.py +0 -12
- runbooks/organizations/manager.py +0 -374
- runbooks-0.7.0.dist-info/METADATA +0 -375
- /runbooks/inventory/{tests → Tests}/common_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/common_test_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/script_test_data.py +0 -0
- /runbooks/inventory/{tests → Tests}/setup.py +0 -0
- /runbooks/inventory/{tests → Tests}/src.py +0 -0
- /runbooks/inventory/{tests/test_inventory_modules.py → Tests/test_Inventory_Modules.py} +0 -0
- /runbooks/inventory/{tests → Tests}/test_cfn_describe_stacks.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_ec2_describe_instances.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_lambda_list_functions.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_moto_integration_example.py +0 -0
- /runbooks/inventory/{tests → Tests}/test_org_list_accounts.py +0 -0
- /runbooks/inventory/{Inventory_Modules.py → inventory_modules.py} +0 -0
- /runbooks/{aws → operate}/tags.json +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/WHEEL +0 -0
- {runbooks-0.7.0.dist-info → runbooks-0.7.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,496 @@
|
|
1
|
+
"""
|
2
|
+
CloudWatch Operations Module.
|
3
|
+
|
4
|
+
Provides comprehensive CloudWatch resource management capabilities including
|
5
|
+
log group management, retention policies, and monitoring configuration.
|
6
|
+
|
7
|
+
Migrated and enhanced from:
|
8
|
+
- inventory/update_cloudwatch_logs_retention_policy.py
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
from datetime import datetime
|
13
|
+
from typing import Any, Dict, List, Optional, Union
|
14
|
+
|
15
|
+
import boto3
|
16
|
+
from botocore.exceptions import ClientError
|
17
|
+
from loguru import logger
|
18
|
+
|
19
|
+
from runbooks.operate.base import BaseOperation, OperationContext, OperationResult, OperationStatus
|
20
|
+
|
21
|
+
|
22
|
+
class CloudWatchOperations(BaseOperation):
|
23
|
+
"""
|
24
|
+
CloudWatch resource operations and lifecycle management.
|
25
|
+
|
26
|
+
Handles all CloudWatch-related operational tasks including log group management,
|
27
|
+
retention policy updates, and monitoring configuration.
|
28
|
+
"""
|
29
|
+
|
30
|
+
service_name = "cloudwatch"
|
31
|
+
supported_operations = {
|
32
|
+
"create_log_group",
|
33
|
+
"delete_log_group",
|
34
|
+
"update_log_retention_policy",
|
35
|
+
"create_metric_alarm",
|
36
|
+
"delete_metric_alarm",
|
37
|
+
"put_metric_data",
|
38
|
+
"create_dashboard",
|
39
|
+
"delete_dashboard",
|
40
|
+
"tag_log_group",
|
41
|
+
"untag_log_group",
|
42
|
+
}
|
43
|
+
requires_confirmation = True
|
44
|
+
|
45
|
+
def __init__(self, profile: Optional[str] = None, region: Optional[str] = None, dry_run: bool = False):
|
46
|
+
"""Initialize CloudWatch operations."""
|
47
|
+
super().__init__(profile, region, dry_run)
|
48
|
+
|
49
|
+
def execute_operation(self, context: OperationContext, operation_type: str, **kwargs) -> List[OperationResult]:
|
50
|
+
"""
|
51
|
+
Execute CloudWatch operation.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
context: Operation context
|
55
|
+
operation_type: Type of operation to execute
|
56
|
+
**kwargs: Operation-specific arguments
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
List of operation results
|
60
|
+
"""
|
61
|
+
self.validate_context(context)
|
62
|
+
|
63
|
+
if operation_type == "create_log_group":
|
64
|
+
return self.create_log_group(context, **kwargs)
|
65
|
+
elif operation_type == "delete_log_group":
|
66
|
+
return self.delete_log_group(context, kwargs.get("log_group_name"))
|
67
|
+
elif operation_type == "update_log_retention_policy":
|
68
|
+
return self.update_log_retention_policy(context, **kwargs)
|
69
|
+
elif operation_type == "create_metric_alarm":
|
70
|
+
return self.create_metric_alarm(context, **kwargs)
|
71
|
+
elif operation_type == "delete_metric_alarm":
|
72
|
+
return self.delete_metric_alarm(context, kwargs.get("alarm_name"))
|
73
|
+
elif operation_type == "put_metric_data":
|
74
|
+
return self.put_metric_data(context, **kwargs)
|
75
|
+
elif operation_type == "create_dashboard":
|
76
|
+
return self.create_dashboard(context, **kwargs)
|
77
|
+
elif operation_type == "delete_dashboard":
|
78
|
+
return self.delete_dashboard(context, kwargs.get("dashboard_name"))
|
79
|
+
elif operation_type == "tag_log_group":
|
80
|
+
return self.tag_log_group(context, **kwargs)
|
81
|
+
elif operation_type == "untag_log_group":
|
82
|
+
return self.untag_log_group(context, **kwargs)
|
83
|
+
else:
|
84
|
+
raise ValueError(f"Unsupported operation: {operation_type}")
|
85
|
+
|
86
|
+
def create_log_group(
|
87
|
+
self,
|
88
|
+
context: OperationContext,
|
89
|
+
log_group_name: str,
|
90
|
+
kms_key_id: Optional[str] = None,
|
91
|
+
tags: Optional[Dict[str, str]] = None,
|
92
|
+
retention_in_days: Optional[int] = None,
|
93
|
+
) -> List[OperationResult]:
|
94
|
+
"""
|
95
|
+
Create CloudWatch log group.
|
96
|
+
|
97
|
+
Args:
|
98
|
+
context: Operation context
|
99
|
+
log_group_name: Name of log group to create
|
100
|
+
kms_key_id: KMS key ID for encryption
|
101
|
+
tags: Log group tags
|
102
|
+
retention_in_days: Log retention period in days
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
List of operation results
|
106
|
+
"""
|
107
|
+
logs_client = self.get_client("logs", context.region)
|
108
|
+
|
109
|
+
result = self.create_operation_result(context, "create_log_group", "logs:log_group", log_group_name)
|
110
|
+
|
111
|
+
try:
|
112
|
+
if context.dry_run:
|
113
|
+
logger.info(f"[DRY-RUN] Would create log group {log_group_name}")
|
114
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
115
|
+
return [result]
|
116
|
+
|
117
|
+
create_params = {"logGroupName": log_group_name}
|
118
|
+
|
119
|
+
if kms_key_id:
|
120
|
+
create_params["kmsKeyId"] = kms_key_id
|
121
|
+
if tags:
|
122
|
+
create_params["tags"] = tags
|
123
|
+
|
124
|
+
response = self.execute_aws_call(logs_client, "create_log_group", **create_params)
|
125
|
+
|
126
|
+
# Set retention policy if specified
|
127
|
+
if retention_in_days:
|
128
|
+
self.execute_aws_call(
|
129
|
+
logs_client, "put_retention_policy", logGroupName=log_group_name, retentionInDays=retention_in_days
|
130
|
+
)
|
131
|
+
logger.info(f"Set retention policy to {retention_in_days} days for {log_group_name}")
|
132
|
+
|
133
|
+
result.response_data = response
|
134
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
135
|
+
logger.info(f"Successfully created log group {log_group_name}")
|
136
|
+
|
137
|
+
except ClientError as e:
|
138
|
+
error_msg = f"Failed to create log group {log_group_name}: {e}"
|
139
|
+
logger.error(error_msg)
|
140
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
141
|
+
|
142
|
+
return [result]
|
143
|
+
|
144
|
+
def delete_log_group(self, context: OperationContext, log_group_name: str) -> List[OperationResult]:
|
145
|
+
"""
|
146
|
+
Delete CloudWatch log group.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
context: Operation context
|
150
|
+
log_group_name: Name of log group to delete
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
List of operation results
|
154
|
+
"""
|
155
|
+
logs_client = self.get_client("logs", context.region)
|
156
|
+
|
157
|
+
result = self.create_operation_result(context, "delete_log_group", "logs:log_group", log_group_name)
|
158
|
+
|
159
|
+
try:
|
160
|
+
if not self.confirm_operation(context, log_group_name, "delete log group"):
|
161
|
+
result.mark_completed(OperationStatus.CANCELLED, "Operation cancelled by user")
|
162
|
+
return [result]
|
163
|
+
|
164
|
+
if context.dry_run:
|
165
|
+
logger.info(f"[DRY-RUN] Would delete log group {log_group_name}")
|
166
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
167
|
+
else:
|
168
|
+
response = self.execute_aws_call(logs_client, "delete_log_group", logGroupName=log_group_name)
|
169
|
+
|
170
|
+
result.response_data = response
|
171
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
172
|
+
logger.info(f"Successfully deleted log group {log_group_name}")
|
173
|
+
|
174
|
+
except ClientError as e:
|
175
|
+
error_msg = f"Failed to delete log group {log_group_name}: {e}"
|
176
|
+
logger.error(error_msg)
|
177
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
178
|
+
|
179
|
+
return [result]
|
180
|
+
|
181
|
+
def update_log_retention_policy(
|
182
|
+
self,
|
183
|
+
context: OperationContext,
|
184
|
+
log_group_name: Optional[str] = None,
|
185
|
+
retention_in_days: int = 30,
|
186
|
+
update_all_log_groups: bool = False,
|
187
|
+
) -> List[OperationResult]:
|
188
|
+
"""
|
189
|
+
Update CloudWatch log retention policy.
|
190
|
+
|
191
|
+
Migrated from inventory/update_cloudwatch_logs_retention_policy.py
|
192
|
+
|
193
|
+
Args:
|
194
|
+
context: Operation context
|
195
|
+
log_group_name: Specific log group name (if None, updates all)
|
196
|
+
retention_in_days: Retention period in days
|
197
|
+
update_all_log_groups: Whether to update all log groups
|
198
|
+
|
199
|
+
Returns:
|
200
|
+
List of operation results
|
201
|
+
"""
|
202
|
+
logs_client = self.get_client("logs", context.region)
|
203
|
+
results = []
|
204
|
+
|
205
|
+
try:
|
206
|
+
if log_group_name:
|
207
|
+
# Update specific log group
|
208
|
+
log_groups = [log_group_name]
|
209
|
+
elif update_all_log_groups:
|
210
|
+
# Get all log groups
|
211
|
+
paginator = logs_client.get_paginator("describe_log_groups")
|
212
|
+
log_groups = []
|
213
|
+
|
214
|
+
for page in paginator.paginate():
|
215
|
+
for log_group in page["logGroups"]:
|
216
|
+
log_groups.append(log_group["logGroupName"])
|
217
|
+
else:
|
218
|
+
raise ValueError("Either log_group_name must be specified or update_all_log_groups must be True")
|
219
|
+
|
220
|
+
for lg_name in log_groups:
|
221
|
+
result = self.create_operation_result(context, "update_log_retention_policy", "logs:log_group", lg_name)
|
222
|
+
|
223
|
+
try:
|
224
|
+
if context.dry_run:
|
225
|
+
logger.info(f"[DRY-RUN] Would set retention to {retention_in_days} days for {lg_name}")
|
226
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
227
|
+
else:
|
228
|
+
response = self.execute_aws_call(
|
229
|
+
logs_client, "put_retention_policy", logGroupName=lg_name, retentionInDays=retention_in_days
|
230
|
+
)
|
231
|
+
|
232
|
+
result.response_data = response
|
233
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
234
|
+
logger.info(f"Successfully updated retention policy for {lg_name}")
|
235
|
+
|
236
|
+
except ClientError as e:
|
237
|
+
error_msg = f"Failed to update retention policy for {lg_name}: {e}"
|
238
|
+
logger.error(error_msg)
|
239
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
240
|
+
|
241
|
+
results.append(result)
|
242
|
+
|
243
|
+
except Exception as e:
|
244
|
+
error_msg = f"Failed to update log retention policies: {e}"
|
245
|
+
logger.error(error_msg)
|
246
|
+
result = self.create_operation_result(
|
247
|
+
context, "update_log_retention_policy", "logs:operation", "batch_update"
|
248
|
+
)
|
249
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
250
|
+
results.append(result)
|
251
|
+
|
252
|
+
return results
|
253
|
+
|
254
|
+
def create_metric_alarm(
|
255
|
+
self,
|
256
|
+
context: OperationContext,
|
257
|
+
alarm_name: str,
|
258
|
+
comparison_operator: str,
|
259
|
+
evaluation_periods: int,
|
260
|
+
metric_name: str,
|
261
|
+
namespace: str,
|
262
|
+
period: int,
|
263
|
+
statistic: str,
|
264
|
+
threshold: float,
|
265
|
+
actions_enabled: bool = True,
|
266
|
+
alarm_actions: Optional[List[str]] = None,
|
267
|
+
alarm_description: Optional[str] = None,
|
268
|
+
dimensions: Optional[List[Dict[str, str]]] = None,
|
269
|
+
insufficient_data_actions: Optional[List[str]] = None,
|
270
|
+
ok_actions: Optional[List[str]] = None,
|
271
|
+
tags: Optional[List[Dict[str, str]]] = None,
|
272
|
+
unit: Optional[str] = None,
|
273
|
+
) -> List[OperationResult]:
|
274
|
+
"""
|
275
|
+
Create CloudWatch metric alarm.
|
276
|
+
|
277
|
+
Args:
|
278
|
+
context: Operation context
|
279
|
+
alarm_name: Name of alarm to create
|
280
|
+
comparison_operator: Comparison operator
|
281
|
+
evaluation_periods: Number of evaluation periods
|
282
|
+
metric_name: Metric name
|
283
|
+
namespace: Metric namespace
|
284
|
+
period: Period in seconds
|
285
|
+
statistic: Statistic type
|
286
|
+
threshold: Alarm threshold
|
287
|
+
actions_enabled: Whether actions are enabled
|
288
|
+
alarm_actions: Alarm actions
|
289
|
+
alarm_description: Alarm description
|
290
|
+
dimensions: Metric dimensions
|
291
|
+
insufficient_data_actions: Insufficient data actions
|
292
|
+
ok_actions: OK actions
|
293
|
+
tags: Alarm tags
|
294
|
+
unit: Metric unit
|
295
|
+
|
296
|
+
Returns:
|
297
|
+
List of operation results
|
298
|
+
"""
|
299
|
+
cloudwatch_client = self.get_client("cloudwatch", context.region)
|
300
|
+
|
301
|
+
result = self.create_operation_result(context, "create_metric_alarm", "cloudwatch:alarm", alarm_name)
|
302
|
+
|
303
|
+
try:
|
304
|
+
if context.dry_run:
|
305
|
+
logger.info(f"[DRY-RUN] Would create CloudWatch alarm {alarm_name}")
|
306
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
307
|
+
return [result]
|
308
|
+
|
309
|
+
alarm_params = {
|
310
|
+
"AlarmName": alarm_name,
|
311
|
+
"ComparisonOperator": comparison_operator,
|
312
|
+
"EvaluationPeriods": evaluation_periods,
|
313
|
+
"MetricName": metric_name,
|
314
|
+
"Namespace": namespace,
|
315
|
+
"Period": period,
|
316
|
+
"Statistic": statistic,
|
317
|
+
"Threshold": threshold,
|
318
|
+
"ActionsEnabled": actions_enabled,
|
319
|
+
}
|
320
|
+
|
321
|
+
if alarm_actions:
|
322
|
+
alarm_params["AlarmActions"] = alarm_actions
|
323
|
+
if alarm_description:
|
324
|
+
alarm_params["AlarmDescription"] = alarm_description
|
325
|
+
if dimensions:
|
326
|
+
alarm_params["Dimensions"] = dimensions
|
327
|
+
if insufficient_data_actions:
|
328
|
+
alarm_params["InsufficientDataActions"] = insufficient_data_actions
|
329
|
+
if ok_actions:
|
330
|
+
alarm_params["OKActions"] = ok_actions
|
331
|
+
if tags:
|
332
|
+
alarm_params["Tags"] = tags
|
333
|
+
if unit:
|
334
|
+
alarm_params["Unit"] = unit
|
335
|
+
|
336
|
+
response = self.execute_aws_call(cloudwatch_client, "put_metric_alarm", **alarm_params)
|
337
|
+
|
338
|
+
result.response_data = response
|
339
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
340
|
+
logger.info(f"Successfully created CloudWatch alarm {alarm_name}")
|
341
|
+
|
342
|
+
except ClientError as e:
|
343
|
+
error_msg = f"Failed to create CloudWatch alarm {alarm_name}: {e}"
|
344
|
+
logger.error(error_msg)
|
345
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
346
|
+
|
347
|
+
return [result]
|
348
|
+
|
349
|
+
def delete_metric_alarm(self, context: OperationContext, alarm_name: str) -> List[OperationResult]:
|
350
|
+
"""
|
351
|
+
Delete CloudWatch metric alarm.
|
352
|
+
|
353
|
+
Args:
|
354
|
+
context: Operation context
|
355
|
+
alarm_name: Name of alarm to delete
|
356
|
+
|
357
|
+
Returns:
|
358
|
+
List of operation results
|
359
|
+
"""
|
360
|
+
cloudwatch_client = self.get_client("cloudwatch", context.region)
|
361
|
+
|
362
|
+
result = self.create_operation_result(context, "delete_metric_alarm", "cloudwatch:alarm", alarm_name)
|
363
|
+
|
364
|
+
try:
|
365
|
+
if not self.confirm_operation(context, alarm_name, "delete CloudWatch alarm"):
|
366
|
+
result.mark_completed(OperationStatus.CANCELLED, "Operation cancelled by user")
|
367
|
+
return [result]
|
368
|
+
|
369
|
+
if context.dry_run:
|
370
|
+
logger.info(f"[DRY-RUN] Would delete CloudWatch alarm {alarm_name}")
|
371
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
372
|
+
else:
|
373
|
+
response = self.execute_aws_call(cloudwatch_client, "delete_alarms", AlarmNames=[alarm_name])
|
374
|
+
|
375
|
+
result.response_data = response
|
376
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
377
|
+
logger.info(f"Successfully deleted CloudWatch alarm {alarm_name}")
|
378
|
+
|
379
|
+
except ClientError as e:
|
380
|
+
error_msg = f"Failed to delete CloudWatch alarm {alarm_name}: {e}"
|
381
|
+
logger.error(error_msg)
|
382
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
383
|
+
|
384
|
+
return [result]
|
385
|
+
|
386
|
+
def put_metric_data(
|
387
|
+
self, context: OperationContext, namespace: str, metric_data: List[Dict[str, Any]]
|
388
|
+
) -> List[OperationResult]:
|
389
|
+
"""
|
390
|
+
Put custom metric data to CloudWatch.
|
391
|
+
|
392
|
+
Args:
|
393
|
+
context: Operation context
|
394
|
+
namespace: Metric namespace
|
395
|
+
metric_data: List of metric data points
|
396
|
+
|
397
|
+
Returns:
|
398
|
+
List of operation results
|
399
|
+
"""
|
400
|
+
cloudwatch_client = self.get_client("cloudwatch", context.region)
|
401
|
+
|
402
|
+
result = self.create_operation_result(context, "put_metric_data", "cloudwatch:metric", namespace)
|
403
|
+
|
404
|
+
try:
|
405
|
+
if context.dry_run:
|
406
|
+
logger.info(f"[DRY-RUN] Would put {len(metric_data)} metric data points to {namespace}")
|
407
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
408
|
+
else:
|
409
|
+
response = self.execute_aws_call(
|
410
|
+
cloudwatch_client, "put_metric_data", Namespace=namespace, MetricData=metric_data
|
411
|
+
)
|
412
|
+
|
413
|
+
result.response_data = response
|
414
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
415
|
+
logger.info(f"Successfully put {len(metric_data)} metric data points to {namespace}")
|
416
|
+
|
417
|
+
except ClientError as e:
|
418
|
+
error_msg = f"Failed to put metric data to {namespace}: {e}"
|
419
|
+
logger.error(error_msg)
|
420
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
421
|
+
|
422
|
+
return [result]
|
423
|
+
|
424
|
+
def tag_log_group(
|
425
|
+
self, context: OperationContext, log_group_name: str, tags: Dict[str, str]
|
426
|
+
) -> List[OperationResult]:
|
427
|
+
"""
|
428
|
+
Add tags to CloudWatch log group.
|
429
|
+
|
430
|
+
Args:
|
431
|
+
context: Operation context
|
432
|
+
log_group_name: Name of log group to tag
|
433
|
+
tags: Tags to add
|
434
|
+
|
435
|
+
Returns:
|
436
|
+
List of operation results
|
437
|
+
"""
|
438
|
+
logs_client = self.get_client("logs", context.region)
|
439
|
+
|
440
|
+
result = self.create_operation_result(context, "tag_log_group", "logs:log_group", log_group_name)
|
441
|
+
|
442
|
+
try:
|
443
|
+
if context.dry_run:
|
444
|
+
logger.info(f"[DRY-RUN] Would add {len(tags)} tags to log group {log_group_name}")
|
445
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
446
|
+
else:
|
447
|
+
response = self.execute_aws_call(logs_client, "tag_log_group", logGroupName=log_group_name, tags=tags)
|
448
|
+
|
449
|
+
result.response_data = response
|
450
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
451
|
+
logger.info(f"Successfully added {len(tags)} tags to log group {log_group_name}")
|
452
|
+
|
453
|
+
except ClientError as e:
|
454
|
+
error_msg = f"Failed to tag log group {log_group_name}: {e}"
|
455
|
+
logger.error(error_msg)
|
456
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
457
|
+
|
458
|
+
return [result]
|
459
|
+
|
460
|
+
def untag_log_group(
|
461
|
+
self, context: OperationContext, log_group_name: str, tag_keys: List[str]
|
462
|
+
) -> List[OperationResult]:
|
463
|
+
"""
|
464
|
+
Remove tags from CloudWatch log group.
|
465
|
+
|
466
|
+
Args:
|
467
|
+
context: Operation context
|
468
|
+
log_group_name: Name of log group to untag
|
469
|
+
tag_keys: Tag keys to remove
|
470
|
+
|
471
|
+
Returns:
|
472
|
+
List of operation results
|
473
|
+
"""
|
474
|
+
logs_client = self.get_client("logs", context.region)
|
475
|
+
|
476
|
+
result = self.create_operation_result(context, "untag_log_group", "logs:log_group", log_group_name)
|
477
|
+
|
478
|
+
try:
|
479
|
+
if context.dry_run:
|
480
|
+
logger.info(f"[DRY-RUN] Would remove {len(tag_keys)} tags from log group {log_group_name}")
|
481
|
+
result.mark_completed(OperationStatus.DRY_RUN)
|
482
|
+
else:
|
483
|
+
response = self.execute_aws_call(
|
484
|
+
logs_client, "untag_log_group", logGroupName=log_group_name, tags=tag_keys
|
485
|
+
)
|
486
|
+
|
487
|
+
result.response_data = response
|
488
|
+
result.mark_completed(OperationStatus.SUCCESS)
|
489
|
+
logger.info(f"Successfully removed {len(tag_keys)} tags from log group {log_group_name}")
|
490
|
+
|
491
|
+
except ClientError as e:
|
492
|
+
error_msg = f"Failed to untag log group {log_group_name}: {e}"
|
493
|
+
logger.error(error_msg)
|
494
|
+
result.mark_completed(OperationStatus.FAILED, error_msg)
|
495
|
+
|
496
|
+
return [result]
|