aws-cdk-constructs 2.40.1__tar.gz → 2.41.0__tar.gz

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.
Files changed (87) hide show
  1. {aws_cdk_constructs-2.40.1/aws_cdk_constructs.egg-info → aws_cdk_constructs-2.41.0}/PKG-INFO +1 -1
  2. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/__init__.py +1 -1
  3. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/ecs/cluster.py +162 -2
  4. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/ecs/microservice.py +168 -71
  5. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0/aws_cdk_constructs.egg-info}/PKG-INFO +1 -1
  6. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_ecs_cluster.py +165 -0
  7. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_ecs_microservice.py +59 -0
  8. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/LICENSE.txt +0 -0
  9. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/MANIFEST.in +0 -0
  10. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/README.md +0 -0
  11. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/api/__init__.py +0 -0
  12. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/api/api.py +0 -0
  13. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/__init__.py +0 -0
  14. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/auth_flow.py +0 -0
  15. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/base_edge_constructs.py +0 -0
  16. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/bucket.py +0 -0
  17. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/configuration.py +0 -0
  18. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/edge_function.py +0 -0
  19. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/edge_role.py +0 -0
  20. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/log_level.py +0 -0
  21. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/secret_generator.py +0 -0
  22. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/bucket/with_configuration.py +0 -0
  23. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/__init__.py +0 -0
  24. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/_types/__init__.py +0 -0
  25. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/_types/document.py +0 -0
  26. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/_types/graph.py +0 -0
  27. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/_types/relational.py +0 -0
  28. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/database.py +0 -0
  29. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/database/types.py +0 -0
  30. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/ecs/__init__.py +0 -0
  31. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/ecs/fargate_validator.py +0 -0
  32. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/efs/__init__.py +0 -0
  33. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/efs/volume.py +0 -0
  34. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/add-index/index.js +0 -0
  35. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/add-index/index.js.map +0 -0
  36. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/check-auth/index.js +0 -0
  37. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/check-auth/index.js.LICENSE.txt +0 -0
  38. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/check-auth/index.js.map +0 -0
  39. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/parse-auth/index.js +0 -0
  40. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/parse-auth/index.js.LICENSE.txt +0 -0
  41. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/parse-auth/index.js.map +0 -0
  42. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/refresh-auth/index.js +0 -0
  43. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/refresh-auth/index.js.LICENSE.txt +0 -0
  44. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/refresh-auth/index.js.map +0 -0
  45. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/secret-generator/index.js +0 -0
  46. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/secret-generator/index.js.map +0 -0
  47. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/sign-out/index.js +0 -0
  48. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/sign-out/index.js.LICENSE.txt +0 -0
  49. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/sign-out/index.js.map +0 -0
  50. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/with_configuration/index.js +0 -0
  51. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/with_configuration/index.js.LICENSE.txt +0 -0
  52. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/lambdas/with_configuration/index.js.map +0 -0
  53. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/load_balancer/__init__.py +0 -0
  54. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/load_balancer/alb.py +0 -0
  55. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/load_balancer/nlb.py +0 -0
  56. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/microservice/__init__.py +0 -0
  57. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/microservice/base_user_data.sh +0 -0
  58. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/microservice/manage_connection_to_nlb_instance_security_group_ingress/lambda_function.js +0 -0
  59. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/microservice/microservice.py +0 -0
  60. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/security_group/__init__.py +0 -0
  61. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/security_group/security_group.py +0 -0
  62. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/service_user_for_iac/__init__.py +0 -0
  63. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/service_user_for_iac/service_user_for_iac.py +0 -0
  64. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/service_user_for_static_assets/__init__.py +0 -0
  65. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/service_user_for_static_assets/service_user_for_static_assets.py +0 -0
  66. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/utils.py +0 -0
  67. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/windows_server/__init__.py +0 -0
  68. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/windows_server/user_data.ps1 +0 -0
  69. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs/windows_server/windows_server.py +0 -0
  70. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs.egg-info/SOURCES.txt +0 -0
  71. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs.egg-info/dependency_links.txt +0 -0
  72. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs.egg-info/requires.txt +0 -0
  73. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/aws_cdk_constructs.egg-info/top_level.txt +0 -0
  74. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/setup.cfg +0 -0
  75. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/setup.py +0 -0
  76. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_alb.py +0 -0
  77. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_bucket.py +0 -0
  78. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_database.py +0 -0
  79. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_efs_volume.py +0 -0
  80. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_fargate_validator.py +0 -0
  81. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_microservice.py +0 -0
  82. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_nlb.py +0 -0
  83. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_security_group.py +0 -0
  84. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_service_user_for_iac.py +0 -0
  85. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_service_user_for_static_assets.py +0 -0
  86. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_utils.py +0 -0
  87. {aws_cdk_constructs-2.40.1 → aws_cdk_constructs-2.41.0}/tests/test_windows_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws_cdk_constructs
3
- Version: 2.40.1
3
+ Version: 2.41.0
4
4
  Summary: AWS CDK constructs
5
5
  Home-page: https://bitbucket.org/cioapps/aws-cdk-constructs
6
6
  Author: author
@@ -15,4 +15,4 @@ from .load_balancer import Alb, Nlb
15
15
  from .security_group import SecurityGroup
16
16
  from .windows_server import WindowsServer
17
17
 
18
- __version__ = '2.40.1'
18
+ __version__ = '2.41.0'
@@ -1,16 +1,20 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import List, Optional, TYPE_CHECKING, Dict
4
-
5
3
  from aws_cdk import (
6
4
  aws_ecs as _ecs,
7
5
  aws_ec2 as _ec2,
6
+ aws_iam as _iam,
8
7
  aws_route53 as _route53,
9
8
  aws_cloudwatch as _cloudwatch,
10
9
  aws_elasticloadbalancingv2 as _elb,
10
+ aws_autoscaling as _autoscaling,
11
+ aws_secretsmanager as _secretsmanager,
11
12
  Duration,
13
+ Tags,
12
14
  )
13
15
  from constructs import Construct
16
+ from typing import List, Optional, TYPE_CHECKING, Dict
17
+
14
18
  from aws_cdk_constructs.load_balancer import Alb
15
19
 
16
20
  if TYPE_CHECKING:
@@ -61,6 +65,20 @@ class ECSCluster(Construct):
61
65
  use_cdn (Optional - bool): Add cloudflare IPs to the ALB security group
62
66
 
63
67
  load_balancer_idle_timeout (Optional int): Sets the default timeout for the ALB in seconds. Defaults to 50s in ALB construct
68
+
69
+ ec2_enable (Optional - bool): Enables a EC2 capacity provider in the cluster.
70
+
71
+ ec2_instance_type (Optional - str): The instance type to be used by the EC2 capacity provider.
72
+
73
+ ec2_desired_capacity (Optional - int): The desired capacity of the EC2 capacity provider.
74
+
75
+ ec2_max_capacity (Optional - int): The max capacity of the EC2 capacity provider.
76
+
77
+ ec2_tag_scheduler_uptime (Optional - str): The tag to be used to filter the EC2 instances to be used by the scheduler.
78
+
79
+ ec2_tag_scheduler_uptime_days (Optional - str): The tag to be used to filter the EC2 instances to be used by the scheduler.
80
+
81
+ ec2_tag_scheduler_skip (Optional - str): The tag to be used to filter the EC2 instances to be used by the scheduler.
64
82
  """
65
83
 
66
84
  def __init__(
@@ -80,6 +98,13 @@ class ECSCluster(Construct):
80
98
  use_cdn: Optional[bool] = False,
81
99
  ssl_certificate_arn: Optional[str] = None,
82
100
  load_balancer_idle_timeout: Optional[int] = None,
101
+ ec2_enable: Optional[bool] = False,
102
+ ec2_instance_type: Optional[str] = None,
103
+ ec2_desired_capacity: Optional[int] = None,
104
+ ec2_max_capacity: Optional[int] = None,
105
+ ec2_tag_scheduler_uptime: Optional[str] = '08:00-18:00',
106
+ ec2_tag_scheduler_uptime_days: Optional[str] = '1-2-3-4-5',
107
+ ec2_tag_scheduler_skip: Optional[str] = 'False',
83
108
  ) -> None:
84
109
  self.id = id
85
110
  self.scope = scope
@@ -99,6 +124,18 @@ class ECSCluster(Construct):
99
124
  )
100
125
  self.create_alb = create_alb
101
126
 
127
+ self.ec2_enabled = all(
128
+ [
129
+ ec2_enable,
130
+ ec2_max_capacity,
131
+ ec2_desired_capacity,
132
+ ec2_instance_type,
133
+ ]
134
+ )
135
+ self.ec2_instance_type = (ec2_instance_type,)
136
+ self.ec2_max_capacity = (ec2_max_capacity,)
137
+ self.ec2_desired_capacity = ec2_desired_capacity
138
+
102
139
  super().__init__(scope, id)
103
140
 
104
141
  create_alb_dns = 'True' if create_alb_dns else 'False'
@@ -129,6 +166,129 @@ class ECSCluster(Construct):
129
166
  else:
130
167
  self.alb = None
131
168
 
169
+ if self.ec2_enabled:
170
+ self.ecs_sg = _ec2.SecurityGroup(
171
+ self, f'{app_name}-sg', vpc=self.vpc
172
+ )
173
+ self.ecs_sg.add_ingress_rule(
174
+ peer=self.alb.security_group,
175
+ connection=_ec2.Port.tcp_range(
176
+ start_port=32768, end_port=65535
177
+ ),
178
+ )
179
+ self._ec2_role = _iam.Role(
180
+ self,
181
+ 'asg_role',
182
+ description=app_name + '_' + '_ec2_role',
183
+ assumed_by=_iam.ServicePrincipal('ec2.amazonaws.com'),
184
+ managed_policies=[
185
+ # AWS managed policy to allow sending logs and custom metrics to CloudWatch
186
+ _iam.ManagedPolicy.from_aws_managed_policy_name(
187
+ 'CloudWatchAgentServerPolicy'
188
+ ),
189
+ # AWS managed policy to allow Session Manager console connections to the EC2 instance
190
+ _iam.ManagedPolicy.from_aws_managed_policy_name(
191
+ 'AmazonSSMManagedInstanceCore'
192
+ ),
193
+ ],
194
+ role_name=app_name + '_' + '_ec2_role',
195
+ )
196
+
197
+ user_data = _ec2.UserData.for_linux()
198
+ user_data.add_commands(
199
+ 'yum -y install docker',
200
+ 'service docker start',
201
+ 'curl -O https://s3.eu-west-1.amazonaws.com/amazon-ecs-agent-eu-west-1/amazon-ecs-init-latest.x86_64.rpm',
202
+ 'sudo yum localinstall -y amazon-ecs-init-latest.x86_64.rpm',
203
+ f"echo 'ECS_CLUSTER={self.cluster.cluster_name}' >> /etc/ecs/ecs.config",
204
+ "echo 'ECS_ENABLE_TASK_IAM_ROLE=true' >> /etc/ecs/ecs.config",
205
+ 'echo \'ECS_AVAILABLE_LOGGING_DRIVERS=["awslogs"]\' >> /etc/ecs/ecs.config',
206
+ 'cat /etc/ecs/ecs.config',
207
+ 'service ecs start',
208
+ 'yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm',
209
+ 'sudo systemctl status amazon-ssm-agent',
210
+ 'ssm-cli get-diagnostics --output table',
211
+ )
212
+ machine_image = _ec2.GenericLinuxImage(
213
+ {'eu-west-1': self._get_ami_id(f'{self.id}-ami-id')}
214
+ )
215
+
216
+ if not machine_image:
217
+ raise TypeError(
218
+ f'Impossible to determine the AMI ID for environment: {self.environment}'
219
+ )
220
+ launch_template = _ec2.LaunchTemplate(
221
+ self,
222
+ f'{app_name}-lt',
223
+ detailed_monitoring=True,
224
+ machine_image=machine_image,
225
+ role=self._ec2_role,
226
+ user_data=user_data,
227
+ security_group=self.ecs_sg,
228
+ instance_type=_ec2.InstanceType(ec2_instance_type),
229
+ )
230
+ auto_scaling_group = _autoscaling.AutoScalingGroup(
231
+ self,
232
+ f'{app_name}-asg',
233
+ vpc=self.vpc,
234
+ allow_all_outbound=True,
235
+ desired_capacity=ec2_desired_capacity,
236
+ max_capacity=ec2_max_capacity,
237
+ launch_template=launch_template,
238
+ )
239
+
240
+ capacity_provider = _ecs.AsgCapacityProvider(
241
+ self,
242
+ f'{app_name}-AsgCapacityProvider',
243
+ auto_scaling_group=auto_scaling_group,
244
+ )
245
+ self.cluster.add_asg_capacity_provider(capacity_provider)
246
+ Tags.of(auto_scaling_group).add(
247
+ 'SchedulerSkip',
248
+ ec2_tag_scheduler_skip,
249
+ apply_to_launched_instances=True,
250
+ )
251
+ Tags.of(auto_scaling_group).add(
252
+ 'SchedulerUptime',
253
+ ec2_tag_scheduler_uptime,
254
+ apply_to_launched_instances=True,
255
+ )
256
+ Tags.of(auto_scaling_group).add(
257
+ 'SchedulerUptimeDays',
258
+ ec2_tag_scheduler_uptime_days,
259
+ apply_to_launched_instances=True,
260
+ )
261
+
262
+ def _get_ami_id(self, id):
263
+ ami_secret_arn = self.environments_parameters.get('common').get(
264
+ 'al2023_x86_64_ami_secret_arn'
265
+ )
266
+ # Determine the Image Architecture to retrieve the correspondent secret ID
267
+ # Get the AMI secret as json
268
+ secret_obj = _secretsmanager.Secret.from_secret_complete_arn(
269
+ scope=self,
270
+ id=self.id + '_ami_secret' + id,
271
+ secret_complete_arn=ami_secret_arn,
272
+ )
273
+ # Normalize the environment name
274
+ # We only has three development stages (development, qa, production)
275
+ # Any other environment will use the production version of the AMI (like Shared Services, which is a production environment)
276
+ development_stages = ['development', 'qa', 'production']
277
+ ami_secret_environment = ('production', self.environment)[
278
+ self.environment.lower in development_stages
279
+ ]
280
+
281
+ ami_secret_environment_lower = ami_secret_environment.lower()
282
+ # Get the AMI id correspondent to the selected environment, as CDK token
283
+ ec2_ami_id = secret_obj.secret_value_from_json(
284
+ f'{ami_secret_environment_lower}/ami_id'
285
+ ).unsafe_unwrap() # Unsafe unwrap as we accept that the value is part of the cloudformation template
286
+ if not ec2_ami_id:
287
+ raise TypeError(
288
+ f'Impossible to retrieve the AMI ID from secrets manager for environment: {self.environment}'
289
+ )
290
+ return ec2_ami_id
291
+
132
292
  def _get_vpc(self) -> _ec2.IVpc:
133
293
  return _ec2.Vpc.from_lookup(
134
294
  self.scope, self.id + 'VPC', vpc_id=self.aws_account.get('vpc')
@@ -18,8 +18,8 @@ from aws_cdk import (
18
18
  )
19
19
  from constructs import Construct
20
20
 
21
- from aws_cdk_constructs.efs.volume import EFSVolume
22
21
  from aws_cdk_constructs.ecs.fargate_validator import FargateValidator
22
+ from aws_cdk_constructs.efs.volume import EFSVolume
23
23
 
24
24
  if TYPE_CHECKING:
25
25
  from .cluster import ECSCluster
@@ -105,6 +105,8 @@ class ECSMicroservice(Construct):
105
105
 
106
106
  max_buffer_size_mib (int): The size (mebibytes) of the buffer when log_driver_blocking_mode is set to false. Default: 4
107
107
 
108
+ deploy_to_ec2 (bool): When set to true the service will be deployed to EC2 instead of Fargate. Default: false
109
+
108
110
  """
109
111
 
110
112
  def __init__(
@@ -136,9 +138,11 @@ class ECSMicroservice(Construct):
136
138
  health_check_timeout: int = 5,
137
139
  internal: bool = False,
138
140
  alternative_fqdns: list[str] = [],
141
+ ephemeral_storage_gib: int = 21,
139
142
  omit_cloudformation_template_outputs: bool = False,
140
143
  log_driver_blocking_mode: bool = False,
141
144
  max_buffer_size_mib: int = 4,
145
+ deploy_to_ec2: [bool] = False,
142
146
  ) -> None:
143
147
  self.scope = scope
144
148
  self.id = name
@@ -165,6 +169,8 @@ class ECSMicroservice(Construct):
165
169
  self.environment = self.cluster.environment
166
170
  self.log_driver_blocking_mode = log_driver_blocking_mode
167
171
  self.max_buffer_size_mib = max_buffer_size_mib
172
+ self.ephemeral_storage_gib = ephemeral_storage_gib
173
+ self.deploy_to_ec2 = deploy_to_ec2
168
174
 
169
175
  if not log_driver_blocking_mode:
170
176
  # Configure ECS logging in non-blocking mode and the buffer size when the log_driver_blocking_mode is set to False (default value)
@@ -209,28 +215,51 @@ class ECSMicroservice(Construct):
209
215
  if self.cluster.alb is not None and internal is False:
210
216
  lb_target_priority = self.cluster.alb.get_safe_rule_priority()
211
217
  self.cluster.target_priority = lb_target_priority
212
- self.target_group = self.cluster.alb.listener.add_targets(
213
- self.id,
214
- port=self.port,
215
- targets=[self.service],
216
- priority=lb_target_priority,
217
- protocol=_elb.ApplicationProtocol.HTTP,
218
- health_check=_elb.HealthCheck(
219
- port=str(self.port),
220
- path=self.health_check_path,
221
- healthy_threshold_count=healthy_threshold_count,
222
- unhealthy_threshold_count=unhealthy_threshold_count,
223
- interval=Duration.seconds(health_check_interval),
224
- timeout=Duration.seconds(health_check_timeout),
225
- ),
226
- conditions=[
227
- _elb.ListenerCondition.host_headers(
228
- [
229
- f'{self.id}-{self.cluster.app_name}.{self.cluster.domain_name}'
230
- ]
231
- )
232
- ],
233
- )
218
+
219
+ if self.deploy_to_ec2:
220
+ self.target_group = self.cluster.alb.listener.add_targets(
221
+ self.id,
222
+ targets=[self.service],
223
+ priority=lb_target_priority,
224
+ protocol=_elb.ApplicationProtocol.HTTP,
225
+ health_check=_elb.HealthCheck(
226
+ path=self.health_check_path,
227
+ healthy_threshold_count=healthy_threshold_count,
228
+ unhealthy_threshold_count=unhealthy_threshold_count,
229
+ interval=Duration.seconds(health_check_interval),
230
+ timeout=Duration.seconds(health_check_timeout),
231
+ ),
232
+ conditions=[
233
+ _elb.ListenerCondition.host_headers(
234
+ [
235
+ f'{self.id}-{self.cluster.app_name}.{self.cluster.domain_name}'
236
+ ]
237
+ )
238
+ ],
239
+ )
240
+ else:
241
+ self.target_group = self.cluster.alb.listener.add_targets(
242
+ self.id,
243
+ port=self.port,
244
+ targets=[self.service],
245
+ priority=lb_target_priority,
246
+ protocol=_elb.ApplicationProtocol.HTTP,
247
+ health_check=_elb.HealthCheck(
248
+ port=str(self.port),
249
+ path=self.health_check_path,
250
+ healthy_threshold_count=healthy_threshold_count,
251
+ unhealthy_threshold_count=unhealthy_threshold_count,
252
+ interval=Duration.seconds(health_check_interval),
253
+ timeout=Duration.seconds(health_check_timeout),
254
+ ),
255
+ conditions=[
256
+ _elb.ListenerCondition.host_headers(
257
+ [
258
+ f'{self.id}-{self.cluster.app_name}.{self.cluster.domain_name}'
259
+ ]
260
+ )
261
+ ],
262
+ )
234
263
  if alternative_fqdns:
235
264
  for fqdn in alternative_fqdns:
236
265
  lb_target_priority = (
@@ -248,11 +277,19 @@ class ECSMicroservice(Construct):
248
277
  ],
249
278
  )
250
279
 
251
- self.cluster.alb.security_group.add_ingress_rule(
252
- peer=self.security_group,
253
- connection=_ec2.Port.all_traffic(),
254
- description='Allow ALB to access the service',
255
- )
280
+ if deploy_to_ec2:
281
+ self.cluster.alb.security_group.add_ingress_rule(
282
+ peer=self.cluster.ecs_sg,
283
+ connection=_ec2.Port.all_traffic(),
284
+ description='Allow ALB to access the service',
285
+ )
286
+ else:
287
+
288
+ self.cluster.alb.security_group.add_ingress_rule(
289
+ peer=self.security_group,
290
+ connection=_ec2.Port.all_traffic(),
291
+ description='Allow ALB to access the service',
292
+ )
256
293
 
257
294
  if self.cluster.hosted_zone:
258
295
  self.create_cname(self.cluster.hosted_zone)
@@ -289,12 +326,22 @@ class ECSMicroservice(Construct):
289
326
 
290
327
  :returns: aws_ecs.FargateTaskDefinition object
291
328
  """
292
- return _ecs.FargateTaskDefinition(
293
- scope=self.scope,
294
- id=self.id + '-task',
295
- cpu=self.cpu,
296
- memory_limit_mib=self.memory_limit_mib,
297
- )
329
+ if self.deploy_to_ec2:
330
+ task = _ecs.TaskDefinition(
331
+ compatibility=_ecs.Compatibility.EC2,
332
+ scope=self.scope,
333
+ id=self.id + '-task',
334
+ )
335
+ return task
336
+ else:
337
+
338
+ return _ecs.FargateTaskDefinition(
339
+ scope=self.scope,
340
+ id=self.id + '-task',
341
+ cpu=self.cpu,
342
+ memory_limit_mib=self.memory_limit_mib,
343
+ ephemeral_storage_gib=self.ephemeral_storage_gib,
344
+ )
298
345
 
299
346
  def _create_main_container(self) -> _ecs.ContainerDefinition:
300
347
  """Create the main container for the service, based on the ECR image and attaches it to the task definition
@@ -319,19 +366,36 @@ class ECSMicroservice(Construct):
319
366
  )
320
367
  )
321
368
 
322
- return self.task.add_container(
323
- id=self.id + '-main-container',
324
- image=image,
325
- port_mappings=[_ecs.PortMapping(container_port=self.port)],
326
- logging=_ecs.LogDriver.aws_logs(
327
- stream_prefix=self.id + '-main-container',
328
- log_retention=_logs.RetentionDays.ONE_WEEK,
329
- mode=self.log_driver_mode,
330
- max_buffer_size=self.max_buffer_size,
331
- ),
332
- environment=self.container_env,
333
- entry_point=self.entry_point,
334
- )
369
+ if self.deploy_to_ec2:
370
+ return self.task.add_container(
371
+ id=self.id + '-main-container',
372
+ image=image,
373
+ cpu=self.cpu,
374
+ memory_reservation_mib=self.memory_limit_mib,
375
+ port_mappings=[_ecs.PortMapping(container_port=self.port)],
376
+ logging=_ecs.LogDriver.aws_logs(
377
+ stream_prefix=self.id + '-main-container',
378
+ log_retention=_logs.RetentionDays.ONE_WEEK,
379
+ mode=self.log_driver_mode,
380
+ max_buffer_size=self.max_buffer_size,
381
+ ),
382
+ environment=self.container_env,
383
+ entry_point=self.entry_point,
384
+ )
385
+ else:
386
+ return self.task.add_container(
387
+ id=self.id + '-main-container',
388
+ image=image,
389
+ port_mappings=[_ecs.PortMapping(container_port=self.port)],
390
+ logging=_ecs.LogDriver.aws_logs(
391
+ stream_prefix=self.id + '-main-container',
392
+ log_retention=_logs.RetentionDays.ONE_WEEK,
393
+ mode=self.log_driver_mode,
394
+ max_buffer_size=self.max_buffer_size,
395
+ ),
396
+ environment=self.container_env,
397
+ entry_point=self.entry_point,
398
+ )
335
399
 
336
400
  def _create_autoscaling(self) -> Optional[_ecs.ScalableTaskCount]:
337
401
  """Create the autoscaling settings for the service, if there's a max_number of replicas"""
@@ -344,16 +408,27 @@ class ECSMicroservice(Construct):
344
408
 
345
409
  def _create_service(self) -> _ecs.FargateService:
346
410
  """Creates the Fargate Service"""
347
- return _ecs.FargateService(
348
- scope=self.scope,
349
- id=self.id + '-srv',
350
- task_definition=self.task,
351
- security_groups=self.security_groups,
352
- cluster=self.cluster.cluster,
353
- enable_execute_command=True,
354
- desired_count=self.desired_count,
355
- propagate_tags=_ecs.PropagatedTagSource.SERVICE,
356
- )
411
+ if self.deploy_to_ec2:
412
+ return _ecs.Ec2Service(
413
+ scope=self.scope,
414
+ id=self.id + '-srv',
415
+ task_definition=self.task,
416
+ cluster=self.cluster.cluster,
417
+ enable_execute_command=False,
418
+ desired_count=self.desired_count,
419
+ propagate_tags=_ecs.PropagatedTagSource.SERVICE,
420
+ )
421
+ else:
422
+ return _ecs.FargateService(
423
+ scope=self.scope,
424
+ id=self.id + '-srv',
425
+ task_definition=self.task,
426
+ security_groups=self.security_groups,
427
+ cluster=self.cluster.cluster,
428
+ enable_execute_command=True,
429
+ desired_count=self.desired_count,
430
+ propagate_tags=_ecs.PropagatedTagSource.SERVICE,
431
+ )
357
432
 
358
433
  def attach_volume(self, name: str, mount_point: str) -> EFSVolume:
359
434
  """Public method to create and attach an EFS volume to the containers
@@ -371,7 +446,11 @@ class ECSMicroservice(Construct):
371
446
  environments_parameters=self.environments_parameters,
372
447
  app_name=self.cluster.app_name,
373
448
  )
374
- volume.grant_access(self.security_group)
449
+ if self.deploy_to_ec2:
450
+ volume.grant_access(self.cluster.ecs_sg)
451
+ else:
452
+ volume.grant_access(self.security_group)
453
+
375
454
  self.task.node.add_dependency(volume)
376
455
  self.task.add_volume(
377
456
  name=volume.id,
@@ -398,19 +477,37 @@ class ECSMicroservice(Construct):
398
477
  directory=os.getcwd() + dockerfile_path,
399
478
  )
400
479
 
401
- init_container = self.task.add_container(
402
- id=f'{self.id}-{name}-init-container',
403
- image=_ecs.ContainerImage.from_docker_image_asset(
404
- init_container_image
405
- ),
406
- essential=False,
407
- logging=_ecs.LogDriver.aws_logs(
408
- stream_prefix=f'{self.id}-{name}-init-container',
409
- log_retention=_logs.RetentionDays.ONE_WEEK,
410
- mode=self.log_driver_mode,
411
- max_buffer_size=self.max_buffer_size,
412
- ),
413
- )
480
+ if self.deploy_to_ec2:
481
+ init_container = self.task.add_container(
482
+ id=f'{self.id}-{name}-init-container',
483
+ image=_ecs.ContainerImage.from_docker_image_asset(
484
+ init_container_image
485
+ ),
486
+ cpu=self.cpu,
487
+ memory_reservation_mib=self.memory_limit_mib,
488
+ essential=False,
489
+ logging=_ecs.LogDriver.aws_logs(
490
+ stream_prefix=f'{self.id}-{name}-init-container',
491
+ log_retention=_logs.RetentionDays.ONE_WEEK,
492
+ mode=self.log_driver_mode,
493
+ max_buffer_size=self.max_buffer_size,
494
+ ),
495
+ )
496
+ else:
497
+
498
+ init_container = self.task.add_container(
499
+ id=f'{self.id}-{name}-init-container',
500
+ image=_ecs.ContainerImage.from_docker_image_asset(
501
+ init_container_image
502
+ ),
503
+ essential=False,
504
+ logging=_ecs.LogDriver.aws_logs(
505
+ stream_prefix=f'{self.id}-{name}-init-container',
506
+ log_retention=_logs.RetentionDays.ONE_WEEK,
507
+ mode=self.log_driver_mode,
508
+ max_buffer_size=self.max_buffer_size,
509
+ ),
510
+ )
414
511
 
415
512
  init_container.node.add_dependency(volume)
416
513
  self.main_container.add_container_dependencies(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aws_cdk_constructs
3
- Version: 2.40.1
3
+ Version: 2.41.0
4
4
  Summary: AWS CDK constructs
5
5
  Home-page: https://bitbucket.org/cioapps/aws-cdk-constructs
6
6
  Author: author
@@ -472,3 +472,168 @@ def test_cluster_render_widgets_from_microservices_internal_are_ignored(
472
472
  .get('Fn::Join')[1]
473
473
  )
474
474
  assert len(widgets) == 97
475
+
476
+
477
+ def test_ecs_requires_all_ec2_params_to_be_enables(
478
+ stack, vpc, hosted_zone, environment_parameters, mocker
479
+ ):
480
+ mocker.patch('aws_cdk.aws_ec2.Vpc.from_lookup', return_value=vpc)
481
+ cluster = ECSCluster(
482
+ scope=stack,
483
+ id='x',
484
+ environment='development',
485
+ environments_parameters=environment_parameters,
486
+ app_name='testapp',
487
+ hosted_zone=hosted_zone,
488
+ target_priority=100,
489
+ ec2_enable=True,
490
+ ec2_instance_type='t2.micro',
491
+ ec2_max_capacity=1,
492
+ )
493
+
494
+ assert cluster.ec2_enabled == False
495
+
496
+
497
+ def test_ecs_with_ec2_creates_sg_with_port_range(
498
+ stack, vpc, hosted_zone, environment_parameters, mocker
499
+ ):
500
+ mocker.patch('aws_cdk.aws_ec2.Vpc.from_lookup', return_value=vpc)
501
+ cluster = ECSCluster(
502
+ scope=stack,
503
+ id='x',
504
+ environment='development',
505
+ environments_parameters=environment_parameters,
506
+ app_name='testapp',
507
+ hosted_zone=hosted_zone,
508
+ target_priority=100,
509
+ ec2_enable=True,
510
+ ec2_instance_type='t2.micro',
511
+ ec2_max_capacity=1,
512
+ ec2_desired_capacity=1,
513
+ )
514
+ template = Template.from_stack(stack)
515
+ alb_sg = template.find_resources(
516
+ 'AWS::EC2::SecurityGroup',
517
+ {
518
+ 'Properties': {
519
+ 'GroupName': f'testapp_{stack.stack_name}_alb_sg',
520
+ }
521
+ },
522
+ )
523
+ alb_sg_id = list(alb_sg.keys())[0]
524
+ template.has_resource_properties(
525
+ 'AWS::EC2::SecurityGroupIngress',
526
+ {
527
+ 'FromPort': 32768,
528
+ 'IpProtocol': 'tcp',
529
+ 'SourceSecurityGroupId': {'Fn::GetAtt': [alb_sg_id, 'GroupId']},
530
+ 'ToPort': 65535,
531
+ },
532
+ )
533
+
534
+
535
+ def test_ecs_with_ec2_creates_right_launch_template(
536
+ stack, vpc, hosted_zone, environment_parameters, mocker
537
+ ):
538
+ mocker.patch('aws_cdk.aws_ec2.Vpc.from_lookup', return_value=vpc)
539
+ secret = cdk.aws_secretsmanager.Secret(
540
+ stack,
541
+ 'Secret',
542
+ secret_object_value={
543
+ 'production/ami_id': cdk.SecretValue.unsafe_plain_text(
544
+ 'ami_id_response'
545
+ )
546
+ },
547
+ )
548
+ mocker.patch(
549
+ 'aws_cdk.aws_secretsmanager.Secret.from_secret_complete_arn',
550
+ return_value=secret,
551
+ )
552
+ cluster = ECSCluster(
553
+ scope=stack,
554
+ id='x',
555
+ environment='development',
556
+ environments_parameters=environment_parameters,
557
+ app_name='testapp',
558
+ hosted_zone=hosted_zone,
559
+ target_priority=100,
560
+ ec2_enable=True,
561
+ ec2_instance_type='t2.micro',
562
+ ec2_max_capacity=1,
563
+ ec2_desired_capacity=1,
564
+ )
565
+
566
+ template = Template.from_stack(stack)
567
+ secret = template.find_resources(
568
+ 'AWS::SecretsManager::Secret',
569
+ {
570
+ 'Properties': {
571
+ 'SecretString': '{"production/ami_id":"ami_id_response"}'
572
+ }
573
+ },
574
+ )
575
+ secret_id = list(secret.keys())[0]
576
+ template.has_resource_properties(
577
+ 'AWS::EC2::LaunchTemplate',
578
+ {
579
+ 'LaunchTemplateData': {
580
+ 'ImageId': {
581
+ 'Fn::Join': [
582
+ '',
583
+ [
584
+ '{{resolve:secretsmanager:',
585
+ {'Ref': secret_id},
586
+ ':SecretString:production/ami_id::}}',
587
+ ],
588
+ ]
589
+ },
590
+ 'InstanceType': 't2.micro',
591
+ }
592
+ },
593
+ )
594
+
595
+
596
+ def test_ecs_with_ec2_creates_asg(
597
+ stack, vpc, hosted_zone, environment_parameters, mocker
598
+ ):
599
+ mocker.patch('aws_cdk.aws_ec2.Vpc.from_lookup', return_value=vpc)
600
+
601
+ cluster = ECSCluster(
602
+ scope=stack,
603
+ id='x',
604
+ environment='development',
605
+ environments_parameters=environment_parameters,
606
+ app_name='testapp',
607
+ hosted_zone=hosted_zone,
608
+ target_priority=100,
609
+ ec2_enable=True,
610
+ ec2_instance_type='t2.micro',
611
+ ec2_max_capacity=1,
612
+ ec2_desired_capacity=1,
613
+ )
614
+ template = Template.from_stack(stack)
615
+ template.has_resource_properties(
616
+ 'AWS::AutoScaling::AutoScalingGroup',
617
+ {
618
+ 'DesiredCapacity': '1',
619
+ 'MaxSize': '1',
620
+ 'MinSize': '1',
621
+ 'Tags': [
622
+ {
623
+ 'Key': 'SchedulerSkip',
624
+ 'PropagateAtLaunch': True,
625
+ 'Value': 'False',
626
+ },
627
+ {
628
+ 'Key': 'SchedulerUptime',
629
+ 'PropagateAtLaunch': True,
630
+ 'Value': '08:00-18:00',
631
+ },
632
+ {
633
+ 'Key': 'SchedulerUptimeDays',
634
+ 'PropagateAtLaunch': True,
635
+ 'Value': '1-2-3-4-5',
636
+ },
637
+ ],
638
+ },
639
+ )
@@ -36,6 +36,23 @@ def cluster(stack, vpc, mocker, environment_parameters):
36
36
  )
37
37
 
38
38
 
39
+ @pytest.fixture
40
+ def cluster_ec2(stack, vpc, mocker, environment_parameters):
41
+ mocker.patch('aws_cdk.aws_ec2.Vpc.from_lookup', return_value=vpc)
42
+
43
+ return ECSCluster(
44
+ scope=stack,
45
+ id='x',
46
+ environments_parameters=environment_parameters,
47
+ environment='development',
48
+ app_name='test-app',
49
+ ec2_enable=True,
50
+ ec2_instance_type='t2.micro',
51
+ ec2_max_capacity=1,
52
+ ec2_desired_capacity=1,
53
+ )
54
+
55
+
39
56
  @pytest.fixture
40
57
  def template(stack, ms):
41
58
  return Template.from_stack(stack)
@@ -48,6 +65,17 @@ def ms(stack, cluster):
48
65
  )
49
66
 
50
67
 
68
+ @pytest.fixture
69
+ def ms_ec2(stack, cluster_ec2):
70
+ return ECSMicroservice(
71
+ scope=stack,
72
+ name='test-ms',
73
+ image='repository/image',
74
+ cluster=cluster_ec2,
75
+ deploy_to_ec2=True,
76
+ )
77
+
78
+
51
79
  def test_construct_default_values(stack, cluster, ms):
52
80
  assert ms.id == 'test-ms'
53
81
  assert ms.image == 'repository/image'
@@ -650,3 +678,34 @@ def test_ms_log_blocking_mode(stack, cluster):
650
678
  ],
651
679
  },
652
680
  )
681
+
682
+
683
+ def test_ms_on_ec2_has_autoassigend_port_on_targetgroup(
684
+ stack, cluster_ec2, ms_ec2
685
+ ):
686
+ template = Template.from_stack(stack)
687
+ template.has_resource_properties(
688
+ 'AWS::ElasticLoadBalancingV2::TargetGroup',
689
+ {'Port': 80, 'TargetType': 'instance'},
690
+ )
691
+
692
+
693
+ def test_ms_on_ec2_adds_sg_from_ec2_to_alb(stack, cluster_ec2, ms_ec2):
694
+ template = Template.from_stack(stack)
695
+ srv_sg = template.find_resources(
696
+ 'AWS::EC2::SecurityGroup',
697
+ {'Properties': {'GroupDescription': 'test-stack/x/test-app-sg'}},
698
+ )
699
+
700
+ srv_sg_id = list(srv_sg.keys())[0]
701
+ template.has_resource_properties(
702
+ 'AWS::EC2::SecurityGroupIngress',
703
+ {'SourceSecurityGroupId': {'Fn::GetAtt': [srv_sg_id, 'GroupId']}},
704
+ )
705
+
706
+
707
+ def test_ms_has_launch_type_ec2(stack, cluster_ec2, ms_ec2):
708
+ template = Template.from_stack(stack)
709
+ template.has_resource_properties(
710
+ 'AWS::ECS::Service', {'LaunchType': 'EC2'}
711
+ )