cdk-factory 0.9.12__py3-none-any.whl → 0.10.0__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.
Potentially problematic release.
This version of cdk-factory might be problematic. Click here for more details.
- cdk_factory/configurations/resources/auto_scaling.py +27 -0
- cdk_factory/configurations/resources/cloudfront.py +101 -11
- cdk_factory/configurations/resources/ecs_service.py +12 -0
- cdk_factory/configurations/resources/lambda_edge.py +92 -0
- cdk_factory/configurations/resources/monitoring.py +74 -0
- cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py +51 -1
- cdk_factory/lambdas/edge/ip_gate/handler.py +104 -0
- cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py +99 -0
- cdk_factory/stack_library/cloudfront/__init__.py +6 -0
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +627 -0
- cdk_factory/stack_library/ecs/ecs_service_stack.py +90 -0
- cdk_factory/stack_library/lambda_edge/__init__.py +6 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +217 -0
- cdk_factory/stack_library/monitoring/__init__.py +6 -0
- cdk_factory/stack_library/monitoring/monitoring_stack.py +492 -0
- cdk_factory/version.py +1 -1
- cdk_factory/workload/workload_factory.py +2 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/METADATA +1 -1
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/RECORD +22 -13
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/WHEEL +0 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.9.12.dist-info → cdk_factory-0.10.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Monitoring Stack - CloudWatch Alarms and Dashboards
|
|
3
|
+
Geek Cafe, LLC
|
|
4
|
+
Maintainers: Eric Wilson
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, List, Any, Optional
|
|
10
|
+
|
|
11
|
+
from aws_cdk import (
|
|
12
|
+
Duration,
|
|
13
|
+
aws_cloudwatch as cloudwatch,
|
|
14
|
+
aws_cloudwatch_actions as cw_actions,
|
|
15
|
+
aws_sns as sns,
|
|
16
|
+
aws_sns_subscriptions as subscriptions,
|
|
17
|
+
aws_ssm as ssm,
|
|
18
|
+
aws_logs as logs,
|
|
19
|
+
CfnOutput,
|
|
20
|
+
)
|
|
21
|
+
from constructs import Construct
|
|
22
|
+
|
|
23
|
+
from cdk_factory.interfaces.istack import IStack
|
|
24
|
+
from cdk_factory.stack.stack_module_registry import register_stack
|
|
25
|
+
from cdk_factory.configurations.stack import StackConfig
|
|
26
|
+
from cdk_factory.configurations.resources.monitoring import MonitoringConfig
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@register_stack("monitoring_library_module")
|
|
32
|
+
class MonitoringStack(IStack):
|
|
33
|
+
"""
|
|
34
|
+
Monitoring Stack with CloudWatch Alarms and Dashboards
|
|
35
|
+
|
|
36
|
+
Supports:
|
|
37
|
+
- SNS topics for notifications
|
|
38
|
+
- CloudWatch alarms (metric, composite, anomaly detection)
|
|
39
|
+
- CloudWatch dashboards
|
|
40
|
+
- Log metric filters
|
|
41
|
+
- Email/Slack/PagerDuty subscriptions
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
scope: Construct,
|
|
47
|
+
id: str,
|
|
48
|
+
stack_config: StackConfig,
|
|
49
|
+
deployment,
|
|
50
|
+
**kwargs,
|
|
51
|
+
) -> None:
|
|
52
|
+
super().__init__(scope, id, **kwargs)
|
|
53
|
+
|
|
54
|
+
self.stack_config = stack_config
|
|
55
|
+
self.deployment = deployment
|
|
56
|
+
|
|
57
|
+
# Monitoring config
|
|
58
|
+
monitoring_dict = stack_config.dictionary.get("monitoring", {})
|
|
59
|
+
self.monitoring_config = MonitoringConfig(monitoring_dict, deployment)
|
|
60
|
+
|
|
61
|
+
# Resources
|
|
62
|
+
self.sns_topics: Dict[str, sns.Topic] = {}
|
|
63
|
+
self.alarms: Dict[str, cloudwatch.Alarm] = {}
|
|
64
|
+
self.dashboards: Dict[str, cloudwatch.Dashboard] = {}
|
|
65
|
+
|
|
66
|
+
def build(
|
|
67
|
+
self,
|
|
68
|
+
vpc=None,
|
|
69
|
+
target_groups=None,
|
|
70
|
+
security_groups=None,
|
|
71
|
+
shared=None,
|
|
72
|
+
):
|
|
73
|
+
"""Build monitoring resources"""
|
|
74
|
+
|
|
75
|
+
logger.info(f"Building monitoring stack: {self.monitoring_config.name}")
|
|
76
|
+
|
|
77
|
+
# Create SNS topics first
|
|
78
|
+
self._create_sns_topics()
|
|
79
|
+
|
|
80
|
+
# Create log metric filters
|
|
81
|
+
self._create_log_metric_filters()
|
|
82
|
+
|
|
83
|
+
# Create alarms
|
|
84
|
+
self._create_alarms()
|
|
85
|
+
|
|
86
|
+
# Create composite alarms
|
|
87
|
+
self._create_composite_alarms()
|
|
88
|
+
|
|
89
|
+
# Create dashboards
|
|
90
|
+
self._create_dashboards()
|
|
91
|
+
|
|
92
|
+
# Export SSM parameters
|
|
93
|
+
self._export_ssm_parameters()
|
|
94
|
+
|
|
95
|
+
# Create outputs
|
|
96
|
+
self._create_outputs()
|
|
97
|
+
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def _create_sns_topics(self) -> None:
|
|
101
|
+
"""Create SNS topics for alarm notifications"""
|
|
102
|
+
topics_config = self.monitoring_config.sns_topics
|
|
103
|
+
|
|
104
|
+
for topic_config in topics_config:
|
|
105
|
+
topic_name = topic_config.get("name")
|
|
106
|
+
if not topic_name:
|
|
107
|
+
logger.warning("SNS topic name is required, skipping")
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# Create topic
|
|
111
|
+
topic = sns.Topic(
|
|
112
|
+
self,
|
|
113
|
+
f"Topic-{topic_name}",
|
|
114
|
+
topic_name=topic_name,
|
|
115
|
+
display_name=topic_config.get("display_name", topic_name),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Add subscriptions
|
|
119
|
+
subscriptions_config = topic_config.get("subscriptions", [])
|
|
120
|
+
for sub_config in subscriptions_config:
|
|
121
|
+
protocol = sub_config.get("protocol", "email")
|
|
122
|
+
endpoint = sub_config.get("endpoint")
|
|
123
|
+
|
|
124
|
+
if not endpoint:
|
|
125
|
+
logger.warning(f"Subscription endpoint required for {topic_name}, skipping")
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
if protocol == "email":
|
|
129
|
+
topic.add_subscription(subscriptions.EmailSubscription(endpoint))
|
|
130
|
+
elif protocol == "sms":
|
|
131
|
+
topic.add_subscription(subscriptions.SmsSubscription(endpoint))
|
|
132
|
+
elif protocol == "https":
|
|
133
|
+
topic.add_subscription(subscriptions.UrlSubscription(endpoint))
|
|
134
|
+
elif protocol == "lambda":
|
|
135
|
+
# Lambda ARN as endpoint
|
|
136
|
+
logger.warning(f"Lambda subscriptions not yet implemented for {topic_name}")
|
|
137
|
+
else:
|
|
138
|
+
logger.warning(f"Unsupported protocol {protocol} for {topic_name}")
|
|
139
|
+
|
|
140
|
+
self.sns_topics[topic_name] = topic
|
|
141
|
+
logger.info(f"Created SNS topic: {topic_name}")
|
|
142
|
+
|
|
143
|
+
def _create_log_metric_filters(self) -> None:
|
|
144
|
+
"""Create CloudWatch Logs metric filters"""
|
|
145
|
+
filters_config = self.monitoring_config.log_metric_filters
|
|
146
|
+
|
|
147
|
+
for filter_config in filters_config:
|
|
148
|
+
filter_name = filter_config.get("name")
|
|
149
|
+
log_group_name = filter_config.get("log_group_name")
|
|
150
|
+
filter_pattern = filter_config.get("filter_pattern")
|
|
151
|
+
metric_namespace = filter_config.get("metric_namespace", "CustomMetrics")
|
|
152
|
+
metric_name = filter_config.get("metric_name")
|
|
153
|
+
|
|
154
|
+
if not all([filter_name, log_group_name, filter_pattern, metric_name]):
|
|
155
|
+
logger.warning(f"Missing required fields for metric filter {filter_name}, skipping")
|
|
156
|
+
continue
|
|
157
|
+
|
|
158
|
+
# Import log group
|
|
159
|
+
log_group = logs.LogGroup.from_log_group_name(
|
|
160
|
+
self,
|
|
161
|
+
f"LogGroup-{filter_name}",
|
|
162
|
+
log_group_name=log_group_name
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Create metric filter
|
|
166
|
+
logs.MetricFilter(
|
|
167
|
+
self,
|
|
168
|
+
f"MetricFilter-{filter_name}",
|
|
169
|
+
log_group=log_group,
|
|
170
|
+
filter_pattern=logs.FilterPattern.literal(filter_pattern),
|
|
171
|
+
metric_namespace=metric_namespace,
|
|
172
|
+
metric_name=metric_name,
|
|
173
|
+
metric_value=filter_config.get("metric_value", "1"),
|
|
174
|
+
default_value=filter_config.get("default_value", 0),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
logger.info(f"Created metric filter: {filter_name}")
|
|
178
|
+
|
|
179
|
+
def _create_alarms(self) -> None:
|
|
180
|
+
"""Create CloudWatch alarms"""
|
|
181
|
+
alarms_config = self.monitoring_config.alarms
|
|
182
|
+
|
|
183
|
+
for alarm_config in alarms_config:
|
|
184
|
+
alarm_name = alarm_config.get("name")
|
|
185
|
+
if not alarm_name:
|
|
186
|
+
logger.warning("Alarm name is required, skipping")
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
# Determine alarm type
|
|
190
|
+
alarm_type = alarm_config.get("type", "metric")
|
|
191
|
+
|
|
192
|
+
if alarm_type == "metric":
|
|
193
|
+
alarm = self._create_metric_alarm(alarm_config)
|
|
194
|
+
elif alarm_type == "anomaly":
|
|
195
|
+
alarm = self._create_anomaly_alarm(alarm_config)
|
|
196
|
+
else:
|
|
197
|
+
logger.warning(f"Unsupported alarm type: {alarm_type}")
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
if alarm:
|
|
201
|
+
self.alarms[alarm_name] = alarm
|
|
202
|
+
logger.info(f"Created alarm: {alarm_name}")
|
|
203
|
+
|
|
204
|
+
def _create_metric_alarm(self, config: Dict[str, Any]) -> Optional[cloudwatch.Alarm]:
|
|
205
|
+
"""Create a metric-based alarm"""
|
|
206
|
+
alarm_name = config.get("name")
|
|
207
|
+
|
|
208
|
+
# Get metric configuration
|
|
209
|
+
metric_config = config.get("metric", {})
|
|
210
|
+
|
|
211
|
+
# Check if using SSM import for resource
|
|
212
|
+
namespace = metric_config.get("namespace")
|
|
213
|
+
metric_name = metric_config.get("metric_name")
|
|
214
|
+
dimensions = metric_config.get("dimensions", {})
|
|
215
|
+
|
|
216
|
+
# Resolve SSM parameters in dimensions
|
|
217
|
+
resolved_dimensions = {}
|
|
218
|
+
for dim_name, dim_value in dimensions.items():
|
|
219
|
+
if isinstance(dim_value, str) and dim_value.startswith("{{ssm:") and dim_value.endswith("}}"):
|
|
220
|
+
ssm_param = dim_value[6:-2]
|
|
221
|
+
dim_value = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
222
|
+
resolved_dimensions[dim_name] = dim_value
|
|
223
|
+
|
|
224
|
+
# Create metric
|
|
225
|
+
metric = cloudwatch.Metric(
|
|
226
|
+
namespace=namespace,
|
|
227
|
+
metric_name=metric_name,
|
|
228
|
+
dimensions_map=resolved_dimensions if resolved_dimensions else None,
|
|
229
|
+
statistic=metric_config.get("statistic", "Average"),
|
|
230
|
+
period=Duration.seconds(metric_config.get("period", 300)),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Comparison operator
|
|
234
|
+
comparison_op_map = {
|
|
235
|
+
"GreaterThanThreshold": cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
|
|
236
|
+
"GreaterThanOrEqualToThreshold": cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
237
|
+
"LessThanThreshold": cloudwatch.ComparisonOperator.LESS_THAN_THRESHOLD,
|
|
238
|
+
"LessThanOrEqualToThreshold": cloudwatch.ComparisonOperator.LESS_THAN_OR_EQUAL_TO_THRESHOLD,
|
|
239
|
+
}
|
|
240
|
+
comparison_op = comparison_op_map.get(
|
|
241
|
+
config.get("comparison_operator", "GreaterThanThreshold"),
|
|
242
|
+
cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Treat missing data
|
|
246
|
+
treat_missing_data_map = {
|
|
247
|
+
"breaching": cloudwatch.TreatMissingData.BREACHING,
|
|
248
|
+
"notBreaching": cloudwatch.TreatMissingData.NOT_BREACHING,
|
|
249
|
+
"ignore": cloudwatch.TreatMissingData.IGNORE,
|
|
250
|
+
"missing": cloudwatch.TreatMissingData.MISSING,
|
|
251
|
+
}
|
|
252
|
+
treat_missing_data = treat_missing_data_map.get(
|
|
253
|
+
config.get("treat_missing_data", "notBreaching"),
|
|
254
|
+
cloudwatch.TreatMissingData.NOT_BREACHING
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Create alarm
|
|
258
|
+
alarm = cloudwatch.Alarm(
|
|
259
|
+
self,
|
|
260
|
+
f"Alarm-{alarm_name}",
|
|
261
|
+
alarm_name=alarm_name,
|
|
262
|
+
alarm_description=config.get("description", ""),
|
|
263
|
+
metric=metric,
|
|
264
|
+
threshold=config.get("threshold", 0),
|
|
265
|
+
evaluation_periods=config.get("evaluation_periods", 1),
|
|
266
|
+
datapoints_to_alarm=config.get("datapoints_to_alarm"),
|
|
267
|
+
comparison_operator=comparison_op,
|
|
268
|
+
treat_missing_data=treat_missing_data,
|
|
269
|
+
actions_enabled=config.get("actions_enabled", True),
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Add alarm actions (SNS topics)
|
|
273
|
+
actions = config.get("actions", [])
|
|
274
|
+
for action in actions:
|
|
275
|
+
if action in self.sns_topics:
|
|
276
|
+
alarm.add_alarm_action(cw_actions.SnsAction(self.sns_topics[action]))
|
|
277
|
+
|
|
278
|
+
# Add OK actions
|
|
279
|
+
ok_actions = config.get("ok_actions", [])
|
|
280
|
+
for action in ok_actions:
|
|
281
|
+
if action in self.sns_topics:
|
|
282
|
+
alarm.add_ok_action(cw_actions.SnsAction(self.sns_topics[action]))
|
|
283
|
+
|
|
284
|
+
# Add insufficient data actions
|
|
285
|
+
insufficient_data_actions = config.get("insufficient_data_actions", [])
|
|
286
|
+
for action in insufficient_data_actions:
|
|
287
|
+
if action in self.sns_topics:
|
|
288
|
+
alarm.add_insufficient_data_action(cw_actions.SnsAction(self.sns_topics[action]))
|
|
289
|
+
|
|
290
|
+
return alarm
|
|
291
|
+
|
|
292
|
+
def _create_anomaly_alarm(self, config: Dict[str, Any]) -> Optional[cloudwatch.Alarm]:
|
|
293
|
+
"""Create an anomaly detection alarm"""
|
|
294
|
+
# Anomaly detection alarms use a different approach
|
|
295
|
+
# For now, log and skip - can be implemented later
|
|
296
|
+
logger.info(f"Anomaly detection alarm {config.get('name')} - implementation pending")
|
|
297
|
+
return None
|
|
298
|
+
|
|
299
|
+
def _create_composite_alarms(self) -> None:
|
|
300
|
+
"""Create composite alarms (combine multiple alarms)"""
|
|
301
|
+
composite_config = self.monitoring_config.composite_alarms
|
|
302
|
+
|
|
303
|
+
for comp_config in composite_config:
|
|
304
|
+
comp_name = comp_config.get("name")
|
|
305
|
+
if not comp_name:
|
|
306
|
+
logger.warning("Composite alarm name is required, skipping")
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
# Build alarm rule
|
|
310
|
+
alarm_rule = comp_config.get("alarm_rule")
|
|
311
|
+
if not alarm_rule:
|
|
312
|
+
logger.warning(f"Alarm rule required for {comp_name}, skipping")
|
|
313
|
+
continue
|
|
314
|
+
|
|
315
|
+
# Replace alarm names with ARNs in the rule
|
|
316
|
+
# This is a simplified version - full implementation would parse the rule
|
|
317
|
+
# For now, just pass through
|
|
318
|
+
|
|
319
|
+
composite_alarm = cloudwatch.CompositeAlarm(
|
|
320
|
+
self,
|
|
321
|
+
f"CompositeAlarm-{comp_name}",
|
|
322
|
+
composite_alarm_name=comp_name,
|
|
323
|
+
alarm_description=comp_config.get("description", ""),
|
|
324
|
+
alarm_rule=cloudwatch.AlarmRule.from_string(alarm_rule),
|
|
325
|
+
actions_enabled=comp_config.get("actions_enabled", True),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Add actions
|
|
329
|
+
actions = comp_config.get("actions", [])
|
|
330
|
+
for action in actions:
|
|
331
|
+
if action in self.sns_topics:
|
|
332
|
+
composite_alarm.add_alarm_action(cw_actions.SnsAction(self.sns_topics[action]))
|
|
333
|
+
|
|
334
|
+
logger.info(f"Created composite alarm: {comp_name}")
|
|
335
|
+
|
|
336
|
+
def _create_dashboards(self) -> None:
|
|
337
|
+
"""Create CloudWatch dashboards"""
|
|
338
|
+
dashboards_config = self.monitoring_config.dashboards
|
|
339
|
+
|
|
340
|
+
for dashboard_config in dashboards_config:
|
|
341
|
+
dashboard_name = dashboard_config.get("name")
|
|
342
|
+
if not dashboard_name:
|
|
343
|
+
logger.warning("Dashboard name is required, skipping")
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
# Create dashboard
|
|
347
|
+
dashboard = cloudwatch.Dashboard(
|
|
348
|
+
self,
|
|
349
|
+
f"Dashboard-{dashboard_name}",
|
|
350
|
+
dashboard_name=dashboard_name,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Add widgets
|
|
354
|
+
widgets_config = dashboard_config.get("widgets", [])
|
|
355
|
+
for widget_config in widgets_config:
|
|
356
|
+
widget = self._create_widget(widget_config)
|
|
357
|
+
if widget:
|
|
358
|
+
# Add to dashboard (position will be auto-calculated)
|
|
359
|
+
dashboard.add_widgets(widget)
|
|
360
|
+
|
|
361
|
+
self.dashboards[dashboard_name] = dashboard
|
|
362
|
+
logger.info(f"Created dashboard: {dashboard_name}")
|
|
363
|
+
|
|
364
|
+
def _create_widget(self, config: Dict[str, Any]) -> Optional[cloudwatch.IWidget]:
|
|
365
|
+
"""Create a dashboard widget"""
|
|
366
|
+
widget_type = config.get("type", "graph")
|
|
367
|
+
|
|
368
|
+
if widget_type == "graph":
|
|
369
|
+
return self._create_graph_widget(config)
|
|
370
|
+
elif widget_type == "number":
|
|
371
|
+
return self._create_single_value_widget(config)
|
|
372
|
+
elif widget_type == "log":
|
|
373
|
+
return self._create_log_widget(config)
|
|
374
|
+
elif widget_type == "alarm":
|
|
375
|
+
return self._create_alarm_widget(config)
|
|
376
|
+
else:
|
|
377
|
+
logger.warning(f"Unsupported widget type: {widget_type}")
|
|
378
|
+
return None
|
|
379
|
+
|
|
380
|
+
def _create_graph_widget(self, config: Dict[str, Any]) -> cloudwatch.GraphWidget:
|
|
381
|
+
"""Create a graph widget"""
|
|
382
|
+
metrics_config = config.get("metrics", [])
|
|
383
|
+
metrics = []
|
|
384
|
+
|
|
385
|
+
for metric_config in metrics_config:
|
|
386
|
+
metric = cloudwatch.Metric(
|
|
387
|
+
namespace=metric_config.get("namespace"),
|
|
388
|
+
metric_name=metric_config.get("metric_name"),
|
|
389
|
+
dimensions_map=metric_config.get("dimensions", {}),
|
|
390
|
+
statistic=metric_config.get("statistic", "Average"),
|
|
391
|
+
period=Duration.seconds(metric_config.get("period", 300)),
|
|
392
|
+
label=metric_config.get("label"),
|
|
393
|
+
)
|
|
394
|
+
metrics.append(metric)
|
|
395
|
+
|
|
396
|
+
return cloudwatch.GraphWidget(
|
|
397
|
+
title=config.get("title", ""),
|
|
398
|
+
left=metrics,
|
|
399
|
+
width=config.get("width", 12),
|
|
400
|
+
height=config.get("height", 6),
|
|
401
|
+
legend_position=cloudwatch.LegendPosition.BOTTOM,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
def _create_single_value_widget(self, config: Dict[str, Any]) -> cloudwatch.SingleValueWidget:
|
|
405
|
+
"""Create a single value widget"""
|
|
406
|
+
metrics_config = config.get("metrics", [])
|
|
407
|
+
metrics = []
|
|
408
|
+
|
|
409
|
+
for metric_config in metrics_config:
|
|
410
|
+
metric = cloudwatch.Metric(
|
|
411
|
+
namespace=metric_config.get("namespace"),
|
|
412
|
+
metric_name=metric_config.get("metric_name"),
|
|
413
|
+
dimensions_map=metric_config.get("dimensions", {}),
|
|
414
|
+
statistic=metric_config.get("statistic", "Average"),
|
|
415
|
+
period=Duration.seconds(metric_config.get("period", 300)),
|
|
416
|
+
)
|
|
417
|
+
metrics.append(metric)
|
|
418
|
+
|
|
419
|
+
return cloudwatch.SingleValueWidget(
|
|
420
|
+
title=config.get("title", ""),
|
|
421
|
+
metrics=metrics,
|
|
422
|
+
width=config.get("width", 6),
|
|
423
|
+
height=config.get("height", 3),
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
def _create_log_widget(self, config: Dict[str, Any]) -> cloudwatch.LogQueryWidget:
|
|
427
|
+
"""Create a log query widget"""
|
|
428
|
+
log_group_names = config.get("log_group_names", [])
|
|
429
|
+
query_string = config.get("query_string", "")
|
|
430
|
+
|
|
431
|
+
return cloudwatch.LogQueryWidget(
|
|
432
|
+
title=config.get("title", ""),
|
|
433
|
+
log_group_names=log_group_names,
|
|
434
|
+
query_string=query_string,
|
|
435
|
+
width=config.get("width", 24),
|
|
436
|
+
height=config.get("height", 6),
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
def _create_alarm_widget(self, config: Dict[str, Any]) -> cloudwatch.AlarmWidget:
|
|
440
|
+
"""Create an alarm status widget"""
|
|
441
|
+
alarm_names = config.get("alarm_names", [])
|
|
442
|
+
alarms = [self.alarms[name] for name in alarm_names if name in self.alarms]
|
|
443
|
+
|
|
444
|
+
if not alarms:
|
|
445
|
+
logger.warning(f"No alarms found for alarm widget")
|
|
446
|
+
return None
|
|
447
|
+
|
|
448
|
+
return cloudwatch.AlarmWidget(
|
|
449
|
+
title=config.get("title", "Alarms"),
|
|
450
|
+
alarms=alarms,
|
|
451
|
+
width=config.get("width", 12),
|
|
452
|
+
height=config.get("height", 6),
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
def _export_ssm_parameters(self) -> None:
|
|
456
|
+
"""Export monitoring resources to SSM Parameter Store"""
|
|
457
|
+
ssm_exports = self.monitoring_config.ssm_exports
|
|
458
|
+
|
|
459
|
+
if not ssm_exports:
|
|
460
|
+
return
|
|
461
|
+
|
|
462
|
+
# Export SNS topic ARNs
|
|
463
|
+
for topic_name, topic in self.sns_topics.items():
|
|
464
|
+
param_name = ssm_exports.get(f"sns_topic_{topic_name}")
|
|
465
|
+
if param_name:
|
|
466
|
+
ssm.StringParameter(
|
|
467
|
+
self,
|
|
468
|
+
f"SnsTopicParam-{topic_name}",
|
|
469
|
+
parameter_name=param_name,
|
|
470
|
+
string_value=topic.topic_arn,
|
|
471
|
+
description=f"SNS Topic ARN for {topic_name}",
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
def _create_outputs(self) -> None:
|
|
475
|
+
"""Create CloudFormation outputs"""
|
|
476
|
+
# Output SNS topic ARNs
|
|
477
|
+
for topic_name, topic in self.sns_topics.items():
|
|
478
|
+
CfnOutput(
|
|
479
|
+
self,
|
|
480
|
+
f"SnsTopicArn-{topic_name}",
|
|
481
|
+
value=topic.topic_arn,
|
|
482
|
+
description=f"SNS Topic ARN: {topic_name}",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# Output dashboard URLs
|
|
486
|
+
for dashboard_name, dashboard in self.dashboards.items():
|
|
487
|
+
CfnOutput(
|
|
488
|
+
self,
|
|
489
|
+
f"DashboardUrl-{dashboard_name}",
|
|
490
|
+
value=f"https://console.aws.amazon.com/cloudwatch/home?region={self.region}#dashboards:name={dashboard_name}",
|
|
491
|
+
description=f"Dashboard URL: {dashboard_name}",
|
|
492
|
+
)
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.
|
|
1
|
+
__version__ = "0.10.0"
|
|
@@ -42,6 +42,8 @@ class WorkloadFactory:
|
|
|
42
42
|
runtime_directory=runtime_directory,
|
|
43
43
|
paths=paths,
|
|
44
44
|
)
|
|
45
|
+
# TODO: do we need to get other settings like configs, environment vars, etc?
|
|
46
|
+
outdir = outdir or "./cdk.out"
|
|
45
47
|
self.workload: WorkloadConfig = WorkloadConfig(config=self.cdk_config.config)
|
|
46
48
|
self.workload.paths = paths or []
|
|
47
49
|
self.workload.cdk_app_file = cdk_app_file or __file__
|
|
@@ -2,7 +2,7 @@ cdk_factory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
cdk_factory/app.py,sha256=RnX0-pwdTAPAdKJK_j13Zl8anf9zYKBwboR0KA8K8xM,10346
|
|
3
3
|
cdk_factory/cdk.json,sha256=SKZKhJ2PBpFH78j-F8S3VDYW-lf76--Q2I3ON-ZIQfw,3106
|
|
4
4
|
cdk_factory/cli.py,sha256=FGbCTS5dYCNsfp-etshzvFlGDCjC28r6rtzYbe7KoHI,6407
|
|
5
|
-
cdk_factory/version.py,sha256=
|
|
5
|
+
cdk_factory/version.py,sha256=v4zmKjsKOPZbp6BrWoz7iK4ST0sdZdUh9bQSJmluZ5o,23
|
|
6
6
|
cdk_factory/builds/README.md,sha256=9BBWd7bXpyKdMU_g2UljhQwrC9i5O_Tvkb6oPvndoZk,90
|
|
7
7
|
cdk_factory/commands/command_loader.py,sha256=QbLquuP_AdxtlxlDy-2IWCQ6D-7qa58aphnDPtp_uTs,3744
|
|
8
8
|
cdk_factory/configurations/base_config.py,sha256=JKjhNsy0RCUZy1s8n5D_aXXI-upR9izaLtCTfKYiV9k,9624
|
|
@@ -20,8 +20,8 @@ cdk_factory/configurations/workload.py,sha256=sM-B6UKOdOn5_H-eWmW03J9oa8YZZmO0bv
|
|
|
20
20
|
cdk_factory/configurations/resources/_resources.py,sha256=tnXGn4kEC0JPQaTWB3QpAZG-2hIGBtugHTzuKn1OTvE,2548
|
|
21
21
|
cdk_factory/configurations/resources/api_gateway.py,sha256=-k4hMGszIdQLb5DGmWBIPy49YGutp8zczafRh-Vob0I,4904
|
|
22
22
|
cdk_factory/configurations/resources/apigateway_route_config.py,sha256=6ytn_nwKwlfpBtHL5sV6gxMpgAJ3p6QFGumMoW4CTHM,2351
|
|
23
|
-
cdk_factory/configurations/resources/auto_scaling.py,sha256=
|
|
24
|
-
cdk_factory/configurations/resources/cloudfront.py,sha256=
|
|
23
|
+
cdk_factory/configurations/resources/auto_scaling.py,sha256=rY-lZBZIS3bq0Lzf4IGbmMxhgiD_tknjjTZhh4Z6yFI,5922
|
|
24
|
+
cdk_factory/configurations/resources/cloudfront.py,sha256=oCWa9I8hbXVu114t-IO5frW6hgvPoovFgDbipDqFoGs,3848
|
|
25
25
|
cdk_factory/configurations/resources/cloudwatch_widget.py,sha256=EdEQSXUkDtoY_Mg_cJBWo1Hp84jSiK7U9tsd3k1VhKI,1271
|
|
26
26
|
cdk_factory/configurations/resources/code_artifact.py,sha256=P2X2i6NEcePitEf2wkN6lFTjIbXasn0uzrlPOT0tEac,3253
|
|
27
27
|
cdk_factory/configurations/resources/code_artifact_login.py,sha256=mKd8Bx0WypmSspfibwXbgubbIzW-6gGdQqIHvOnDAYQ,6066
|
|
@@ -30,12 +30,14 @@ cdk_factory/configurations/resources/cognito.py,sha256=udX2AJ1ITLhy4f1XiJQwrva6F
|
|
|
30
30
|
cdk_factory/configurations/resources/docker.py,sha256=hUbuxkuhcQu9LnLX7I8_57eTmHefEAGVnOHO37MkqC4,2166
|
|
31
31
|
cdk_factory/configurations/resources/dynamodb.py,sha256=HsZMOaRwfuNPwKIzokeeE3f5zAQLTB5hRb_GzYq2ibg,2903
|
|
32
32
|
cdk_factory/configurations/resources/ecr.py,sha256=o9hHzEBVPoxUvWZGXGbRJ-98FmP6fMLY5a1-qg42jL0,8253
|
|
33
|
-
cdk_factory/configurations/resources/ecs_service.py,sha256=
|
|
33
|
+
cdk_factory/configurations/resources/ecs_service.py,sha256=NlFkSgWhO58QtCGZZRFQECA3km7O5D_Ee0BIfAcfDxE,4729
|
|
34
34
|
cdk_factory/configurations/resources/exisiting.py,sha256=EVOLnkB-DGfTlmDgyQ5DD5k2zYfpFxqI3gugDR7mifI,478
|
|
35
|
+
cdk_factory/configurations/resources/lambda_edge.py,sha256=2rcwk2MlvW9mW2qHbNg7CsSNilZrJlmDXDjitKr5xyM,3256
|
|
35
36
|
cdk_factory/configurations/resources/lambda_function.py,sha256=VENZ9-ABJ5mjcN8J8wdLH4KHDYr1kWO0iFDH0B2mJXA,14659
|
|
36
37
|
cdk_factory/configurations/resources/lambda_layers.py,sha256=gVeP_-LC3Eq0lkPaG_JfFUwboM5evRPr99SfKj53m7A,633
|
|
37
38
|
cdk_factory/configurations/resources/lambda_triggers.py,sha256=MD7cdMNKEulNBhtMLIFnWJuJ5R-yyIqa0LHUgbSQerA,834
|
|
38
39
|
cdk_factory/configurations/resources/load_balancer.py,sha256=DHVKuEDaTfbB0UKYBt7UQQCPCM4FY-ThT1T52lcwg_E,4897
|
|
40
|
+
cdk_factory/configurations/resources/monitoring.py,sha256=zsfDMa7yph33Ql8iP7lIqqLAyixh-Mesi0imtZJFdcE,2310
|
|
39
41
|
cdk_factory/configurations/resources/rds.py,sha256=vHmR1zpDmV8HqgYhLgWi26w0S3lJN9hpgFboomiQgY8,4315
|
|
40
42
|
cdk_factory/configurations/resources/resource_mapping.py,sha256=cwv3n63RJ6E59ErsmSTdkW4i-g8huhHtKI0ExbRhJxA,2182
|
|
41
43
|
cdk_factory/configurations/resources/resource_naming.py,sha256=VE9S2cpzp11qqPL2z1sX79wXH0o1SntO2OG74nEmWC8,5508
|
|
@@ -48,7 +50,7 @@ cdk_factory/configurations/resources/security_group.py,sha256=8kQtaaRVEn2aDm8XoC
|
|
|
48
50
|
cdk_factory/configurations/resources/security_group_full_stack.py,sha256=x5MIMCa_olO7prFBKx9zVOfvsVdKo-2mWyhrCy27dFw,2031
|
|
49
51
|
cdk_factory/configurations/resources/sqs.py,sha256=fAh2dqttJ6PX46enFRULuiLEu3TEj0Vb2xntAOgUpYE,4346
|
|
50
52
|
cdk_factory/configurations/resources/vpc.py,sha256=sNn6w76bHFwmt6N76gZZhqpsuNB9860C1SZu6tebaXY,3835
|
|
51
|
-
cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py,sha256=
|
|
53
|
+
cdk_factory/constructs/cloudfront/cloudfront_distribution_construct.py,sha256=Eu30OITGGZaSoMq9Nj7tFGohgDO0-Ls7yKjpwYI2HrQ,16956
|
|
52
54
|
cdk_factory/constructs/ecr/ecr_construct.py,sha256=JLz3gWrsjlM0XghvbgxuoGlF-VIo_7IYxtgX7mTkidE,10660
|
|
53
55
|
cdk_factory/constructs/lambdas/lambda_function_construct.py,sha256=SQ5SEXn4kezVAzXuv_A_JB3o_svyBXOMi-htvfB9HQs,4516
|
|
54
56
|
cdk_factory/constructs/lambdas/lambda_function_docker_construct.py,sha256=aSyn3eh1YnuIahZ7CbZ5WswwPL8u70ZibMoS24QQifc,9907
|
|
@@ -64,6 +66,7 @@ cdk_factory/interfaces/istack.py,sha256=bhTBs-o9FgKwvJMSuwxjUV6D3nUlvZHVzfm27jP9
|
|
|
64
66
|
cdk_factory/interfaces/live_ssm_resolver.py,sha256=3FIr9a02SXqZmbFs3RT0WxczWEQR_CF7QSt7kWbDrVE,8163
|
|
65
67
|
cdk_factory/interfaces/ssm_parameter_mixin.py,sha256=uA2j8HmAOpuEA9ynRj51s0WjUHMVLsbLQN-QS9NKyHA,12089
|
|
66
68
|
cdk_factory/lambdas/health_handler.py,sha256=dd40ykKMxWCFEIyp2ZdQvAGNjw_ylI9CSm1N24Hp2ME,196
|
|
69
|
+
cdk_factory/lambdas/edge/ip_gate/handler.py,sha256=MPSAOQGYVMSALdH6f9QTXqMSKKJ1tva-yidtaDTbXsg,3241
|
|
67
70
|
cdk_factory/pipeline/path_utils.py,sha256=fvWdrcb4onmpIu1APkHLhXg8zWfK74HcW3Ra2ynxfXM,2586
|
|
68
71
|
cdk_factory/pipeline/pipeline_factory.py,sha256=rvtkdlTPJG477nTVRN8S2ksWt4bwpd9eVLFd9WO02pM,17248
|
|
69
72
|
cdk_factory/pipeline/stage.py,sha256=Be7ExMB9A-linRM18IQDOzQ-cP_I2_ThRNzlT4FIrUg,437
|
|
@@ -78,19 +81,25 @@ cdk_factory/stack_library/__init__.py,sha256=5Y9TpIe8ZK1688G60PGcuP-hM0RvYEY_3Hl
|
|
|
78
81
|
cdk_factory/stack_library/stack_base.py,sha256=tTleSFmlf26DuKVF_ytftf8P7IVWb5iex8cYfYupfvQ,4940
|
|
79
82
|
cdk_factory/stack_library/api_gateway/api_gateway_stack.py,sha256=l6J5uurBQqbhj9JuvQitV9PiIHS5kqa-dFWifCeA3GM,39347
|
|
80
83
|
cdk_factory/stack_library/auto_scaling/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
|
-
cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py,sha256=
|
|
84
|
+
cdk_factory/stack_library/auto_scaling/auto_scaling_stack.py,sha256=XPTPGF-W8ARvzW8UYd4yPJ7xFUw-2BrdxQOYk9I-v3g,23490
|
|
82
85
|
cdk_factory/stack_library/aws_lambdas/lambda_stack.py,sha256=SFbBPvvCopbyiuYtq-O5sQkFCf94Wzua6aDUXiFDSB4,26161
|
|
83
86
|
cdk_factory/stack_library/buckets/README.md,sha256=XkK3UNVtRLE7NtUvbhCOBBYUYi8hlrrSaI1s3GJVrqI,78
|
|
84
87
|
cdk_factory/stack_library/buckets/bucket_stack.py,sha256=SLoZqSffAqmeBBEVUQg54D_8Ad5UKdkjEAmKAVgAqQo,1778
|
|
88
|
+
cdk_factory/stack_library/cloudfront/__init__.py,sha256=Zfx50q4xIJ4ZEoVIzUBDTKbRE9DKDM6iyVIFhtQXvww,153
|
|
89
|
+
cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=-mw24FbvwLUzAa1NNKCKGFILZpNXtsuSdxNSNtEz6D8,26804
|
|
85
90
|
cdk_factory/stack_library/code_artifact/code_artifact_stack.py,sha256=vySYIjWGTdVfMcUOyJdW6gTL1maHWq9ThzfrN_rVL5A,6290
|
|
86
91
|
cdk_factory/stack_library/cognito/cognito_stack.py,sha256=zEHkKVCIeyZywPs_GIMXCXyCux9RAKdl5kba3wy8wtQ,24608
|
|
87
92
|
cdk_factory/stack_library/dynamodb/dynamodb_stack.py,sha256=TVyOrUhgaSuN8uymkpaQcpOaSA0lkYJ8QUMgakTCKus,6771
|
|
88
93
|
cdk_factory/stack_library/ecr/README.md,sha256=xw2wPx9WN03Y4BBwqvbi9lAFGNyaD1FUNpqxVJX14Oo,179
|
|
89
94
|
cdk_factory/stack_library/ecr/ecr_stack.py,sha256=1xA68sxFVyqreYjXrP_7U9I8RF9RtFeR6KeEfSWuC2U,2118
|
|
90
95
|
cdk_factory/stack_library/ecs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
-
cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=
|
|
96
|
+
cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=zuGdZEP5KmeVDTJb-H47LYhvs-85-Fi4Xb78nsA-lF4,24685
|
|
97
|
+
cdk_factory/stack_library/lambda_edge/__init__.py,sha256=ByBJ_CWdc4UtTmFBZH-6pzBMNkjkdtE65AmnB0Fs6lM,156
|
|
98
|
+
cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=m87aaeGNEG-LN2mr6Kj6O3WmeXcmh-ACp8oXdlqOI1E,8070
|
|
92
99
|
cdk_factory/stack_library/load_balancer/__init__.py,sha256=wZpKw2OecLJGdF5mPayCYAEhu2H3c2gJFFIxwXftGDU,52
|
|
93
100
|
cdk_factory/stack_library/load_balancer/load_balancer_stack.py,sha256=t5JUe5lMUbQCRFZR08k8nO-g-53yWY8gKB9v8ZnedBs,24391
|
|
101
|
+
cdk_factory/stack_library/monitoring/__init__.py,sha256=k1G_KDx47Aw0UugaL99PN_TKlyLK4nkJVApCaAK7GJg,153
|
|
102
|
+
cdk_factory/stack_library/monitoring/monitoring_stack.py,sha256=N_1YvEXE7fboH_S3kv_dSKZsufxMuPdFMjGzlNFpuSo,19283
|
|
94
103
|
cdk_factory/stack_library/rds/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
104
|
cdk_factory/stack_library/rds/rds_stack.py,sha256=-IQFmacO_UgedAN72Uo0ZGlcmqGcDinr3tquBdhQZXw,8559
|
|
96
105
|
cdk_factory/stack_library/route53/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -119,9 +128,9 @@ cdk_factory/utilities/json_loading_utility.py,sha256=YRgzA1I-B_HwZm1eWJTeQ1JLkeb
|
|
|
119
128
|
cdk_factory/utilities/lambda_function_utilities.py,sha256=S1GvBsY_q2cyUiaud3HORJMnLhI5cRi31fbeaktY-_Q,15826
|
|
120
129
|
cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITEP7EuSU,1019
|
|
121
130
|
cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
|
|
122
|
-
cdk_factory/workload/workload_factory.py,sha256=
|
|
123
|
-
cdk_factory-0.
|
|
124
|
-
cdk_factory-0.
|
|
125
|
-
cdk_factory-0.
|
|
126
|
-
cdk_factory-0.
|
|
127
|
-
cdk_factory-0.
|
|
131
|
+
cdk_factory/workload/workload_factory.py,sha256=mM8GU_5mKq_0OyK060T3JrUSUiGAcKf0eqNlT9mfaws,6028
|
|
132
|
+
cdk_factory-0.10.0.dist-info/METADATA,sha256=HZeUq_9_oU9e86iAoXouf2uIJNXlJMSk9P36L1vEBns,2451
|
|
133
|
+
cdk_factory-0.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
134
|
+
cdk_factory-0.10.0.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
|
|
135
|
+
cdk_factory-0.10.0.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
|
|
136
|
+
cdk_factory-0.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|