diff --git a/AWS/ecs-fargate/README.md b/AWS/ecs-fargate/README.md index 7c371df..a8691ab 100644 --- a/AWS/ecs-fargate/README.md +++ b/AWS/ecs-fargate/README.md @@ -75,3 +75,19 @@ git clone https://github.com/dbeaver/team-edition-deploy.git 2. Specify the desired version in `variables.tf` in the `dbeaver_te_version` variable. 3. Run `terraform apply` to upgrade the ECS cluster and complete the deployment. + + +## Modules description + +This deployment is built on self-contained parameterized Terraform modules. All modules are located in the local [`./modules/`](./modules/) folder, so every piece of infrastructure can be reviewed, customized and maintained. Root `*.tf` files only wire modules together and pass variables, resource definitions are kept inside the modules. + +- **alb** — Application Load Balancer with HTTP to HTTPS redirect and HTTPS listener. +- **alb-route** — Target group and listener rule for a single backend service. +- **ecs-cluster** — ECS cluster with Fargate capacity providers. +- **ecs-service** — Fargate task definition and ECS service, with optional EFS volumes and ALB target group. +- **efs-volume** — EFS file system, mount targets and optional access point. +- **iam** — ECS task and execution IAM roles with CloudWatch Logs and EFS access policies. +- **rds** — RDS instance and DB subnet group. +- **vpc** — VPC with public/private subnets, Internet Gateway, NAT Gateway and route tables. + +For customers who used the previous flat Terraform deployment, the [`migration.tf`](./migration.tf) file remaps the existing Terraform state to the new module layout, so `terraform apply` preserves the environment without losing data. \ No newline at end of file diff --git a/AWS/ecs-fargate/alb.tf b/AWS/ecs-fargate/alb.tf index e6e0de0..c6ecfbf 100644 --- a/AWS/ecs-fargate/alb.tf +++ b/AWS/ecs-fargate/alb.tf @@ -1,194 +1,19 @@ ################################################################################ -# ALB +# AWS ALB ################################################################################ -resource "aws_lb" "dbeaver_te_lb" { - name = "DBeaverTE-${var.deployment_id}-ALB" - internal = false - load_balancer_type = "application" - security_groups = [aws_security_group.dbeaver_alb.id] - subnets = aws_subnet.public_subnets[*].id - tags = { - env = var.deployment_id - } -} - - -# This resources must be edited if HTTPS not used -resource "aws_lb_listener" "dbeaver-te-listener" { - - load_balancer_arn = aws_lb.dbeaver_te_lb.arn - port = "80" - protocol = "HTTP" - - default_action { - type = "redirect" - - redirect { - port = "443" - protocol = "HTTPS" - status_code = "HTTP_301" - } - } -} - -# This resources must be edited if HTTPS not used -resource "aws_lb_listener" "dbeaver-te-listener-https" { - - load_balancer_arn = aws_lb.dbeaver_te_lb.arn - port = "443" - protocol = "HTTPS" - ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01" - certificate_arn = "arn:aws:acm:${var.aws_region}:${var.aws_account_id}:certificate/${var.alb_certificate_Identifier}" - - default_action { - type = "forward" - target_group_arn = aws_lb_target_group.dbeaver_te.arn - } -} +module "alb" { + source = "./modules/alb" -resource "aws_lb_listener_rule" "forward_to_service_uri_dc" { - listener_arn = aws_lb_listener.dbeaver-te-listener-https.arn - priority = 99 + name = "${local.name_prefix}-${var.deployment_id}-ALB" + deployment_id = var.deployment_id + vpc_id = local.vpc_id + public_subnets = local.public_subnets + security_group_ids = [aws_security_group.dbeaver_alb.id] + certificate_arn = "arn:aws:acm:${var.aws_region}:${var.aws_account_id}:certificate/${var.alb_certificate_Identifier}" + ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06" - condition { - path_pattern { - values = ["/dc*"] - } - } - - action { - type = "forward" - target_group_arn = aws_lb_target_group.dbeaver_dc.arn - } -} - -resource "aws_lb_listener_rule" "forward_to_service_uri_qm" { - listener_arn = aws_lb_listener.dbeaver-te-listener-https.arn - priority = 98 - - condition { - path_pattern { - values = ["/qm*"] - } - } - - action { - type = "forward" - target_group_arn = aws_lb_target_group.dbeaver_qm.arn - } -} - -resource "aws_lb_listener_rule" "forward_to_service_uri_rm" { - listener_arn = aws_lb_listener.dbeaver-te-listener-https.arn - priority = 97 - - condition { - path_pattern { - values = ["/rm*"] - } - } - - action { - type = "forward" - target_group_arn = aws_lb_target_group.dbeaver_rm.arn - } -} - - -resource "aws_lb_listener_rule" "forward_to_service_uri_tm" { - listener_arn = aws_lb_listener.dbeaver-te-listener-https.arn - priority = 94 - - condition { - path_pattern { - values = ["/tm*"] - } - } - - action { - type = "forward" - target_group_arn = aws_lb_target_group.dbeaver_tm.arn - } -} - - -resource "aws_lb_target_group" "dbeaver_dc" { - name = "DBeaverTE-${var.deployment_id}-dc" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = aws_vpc.dbeaver_net.id - - health_check { - matcher = "200,302" - unhealthy_threshold = 7 - enabled = true - path = "/dc/health" - } -} - -resource "aws_lb_target_group" "dbeaver_te" { - name = "DBeaverTE-${var.deployment_id}" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = aws_vpc.dbeaver_net.id - - health_check { - matcher = "200,302" - unhealthy_threshold = 10 - enabled = true - path = "/" - } - stickiness { - enabled = true - type = "lb_cookie" - cookie_duration = 86400 - } -} - -resource "aws_lb_target_group" "dbeaver_qm" { - name = "DBeaverTE-${var.deployment_id}-qm" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = aws_vpc.dbeaver_net.id - - health_check { - matcher = "200,302" - unhealthy_threshold = 7 - enabled = true - path = "/qm/health" - } -} - -resource "aws_lb_target_group" "dbeaver_rm" { - name = "DBeaverTE-${var.deployment_id}-rm" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = aws_vpc.dbeaver_net.id - - health_check { - matcher = "200,302" - unhealthy_threshold = 7 - enabled = true - path = "/rm/health" - } -} - -resource "aws_lb_target_group" "dbeaver_tm" { - name = "DBeaverTE-${var.deployment_id}-tm" - port = 80 - protocol = "HTTP" - target_type = "ip" - vpc_id = aws_vpc.dbeaver_net.id - - health_check { - matcher = "200,302" - unhealthy_threshold = 7 - enabled = true - path = "/tm/health" + tags = { + Env = var.deployment_id } } diff --git a/AWS/ecs-fargate/aws-iam.tf b/AWS/ecs-fargate/aws-iam.tf deleted file mode 100644 index 6d321ad..0000000 --- a/AWS/ecs-fargate/aws-iam.tf +++ /dev/null @@ -1,118 +0,0 @@ -data "aws_iam_policy_document" "assume_role_policy" { - statement { - effect = "Allow" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["ecs-tasks.amazonaws.com"] - } - } -} - -resource "aws_iam_policy" "CloudbeaverTeamEditionEFSAccessPolicy" { - name = "DBeaverTE-${var.deployment_id}-CloudbeaverTeamEditionEFSAccessPolicy" - description = "Policy to allow access only to specific EFS resources for ${var.deployment_id} environment" - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Effect = "Allow" - Action = [ - "elasticfilesystem:DescribeFileSystems", - "elasticfilesystem:DescribeMountTargets", - "elasticfilesystem:DescribeMountTargetSecurityGroups", - "elasticfilesystem:DescribeTags", - "elasticfilesystem:CreateMountTarget", - "elasticfilesystem:DeleteMountTarget", - "elasticfilesystem:ModifyMountTargetSecurityGroups", - "elasticfilesystem:ListTagsForResource", - "elasticfilesystem:TagResource", - "elasticfilesystem:UntagResource" - ] - Resource = [ - aws_efs_file_system.cloudbeaver_db_data.arn, - aws_efs_file_system.cloudbeaver_rm_data.arn, - aws_efs_file_system.cloudbeaver_tm_data.arn, - aws_efs_file_system.cloudbeaver_dc_data.arn - ] - }, - { - Effect = "Allow" - Action = [ - "elasticfilesystem:CreateTags", - "elasticfilesystem:DeleteTags", - "elasticfilesystem:DescribeFileSystemPolicy", - "elasticfilesystem:PutFileSystemPolicy" - ] - Resource = [ - aws_efs_file_system.cloudbeaver_db_data.arn, - aws_efs_file_system.cloudbeaver_rm_data.arn, - aws_efs_file_system.cloudbeaver_tm_data.arn, - aws_efs_file_system.cloudbeaver_dc_data.arn, - aws_efs_file_system.cloudbeaver_certificates.arn - ] - }, - { - "Effect": "Allow", - "Action": [ - "elasticfilesystem:ClientMount", - "elasticfilesystem:ClientWrite", - "elasticfilesystem:ClientRootAccess" - ], - "Resource": [ - "${aws_efs_file_system.cloudbeaver_certificates.arn}", - "${aws_efs_access_point.certs_public.arn}" - ] - }, - { - "Effect": "Allow", - "Action": "elasticfilesystem:DescribeAccessPoints", - "Resource": "*" - } - ] - }) - - depends_on = [ - aws_efs_file_system.cloudbeaver_db_data, - aws_efs_file_system.cloudbeaver_rm_data, - aws_efs_file_system.cloudbeaver_tm_data, - aws_efs_file_system.cloudbeaver_dc_data - ] -} - -resource "aws_iam_role" "ecsTaskExecutionRole" { - name = "DBeaverTE-${var.deployment_id}-ecsTaskExecutionRole" - assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy.json}" -} - - - -resource "aws_iam_role_policy_attachment" "ecsTaskExecutionRole_policy" { - role = "${aws_iam_role.ecsTaskExecutionRole.name}" - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" -} - -resource "aws_iam_role_policy_attachment" "TeamEditionEFSAccessPolicy_attachment" { - role = "${aws_iam_role.ecsTaskExecutionRole.name}" - policy_arn = "${aws_iam_policy.CloudbeaverTeamEditionEFSAccessPolicy.arn}" -} -resource "aws_iam_role_policy_attachment" "logs_policy_attachment" { - role = "${aws_iam_role.ecsTaskExecutionRole.name}" - policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" -} - -resource "aws_iam_role" "ecs_task_role_exec" { - name = "DBeaverTE-${var.deployment_id}-ecsTaskRoleExec" - assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json -} - -resource "aws_iam_role_policy_attachment" "ecs_task_role_exec_ssm" { - role = aws_iam_role.ecs_task_role_exec.name - policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" -} - -resource "aws_iam_role_policy_attachment" "ecs_task_role_exec_logs" { - role = aws_iam_role.ecs_task_role_exec.name - policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" -} \ No newline at end of file diff --git a/AWS/ecs-fargate/ecs-cloudbeaver-dc.tf b/AWS/ecs-fargate/ecs-cloudbeaver-dc.tf new file mode 100644 index 0000000..cd62357 --- /dev/null +++ b/AWS/ecs-fargate/ecs-cloudbeaver-dc.tf @@ -0,0 +1,73 @@ +################################################################################ +# DBeaver TE DC +################################################################################ + +module "cloudbeaver_dc_route" { + source = "./modules/alb-route" + + name = "${local.name_prefix}-${var.deployment_id}-dc" + vpc_id = local.vpc_id + listener_arn = module.alb.https_listener_arn + path_pattern = "/dc*" + priority = 99 + health_check_path = "/dc/health" + + tags = { Env = var.deployment_id } +} + +module "cloudbeaver_dc" { + source = "./modules/ecs-service" + + name = "cloudbeaver-dc" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "dc" + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-dc:${var.dbeaver_te_version}" + cpu = 1024 + memory = 2048 + container_port = 8970 + + execution_role_arn = module.iam.execution_role_arn + task_role_arn = module.iam.task_role_arn + environment = local.updated_cloudbeaver_dc_env + + efs_volumes = [ + { + name = "cloudbeaver_dc_data" + file_system_id = module.efs["dc_data"].file_system_id + root_directory = "/" + mount_path = "/opt/domain-controller/workspace" + }, + { + name = "cloudbeaver_certificates" + file_system_id = module.efs["certificates"].file_system_id + root_directory = "/" + transit_encryption = true + mount_path = "/opt/domain-controller/conf/certificates" + }, + { + name = "api_tokens" + file_system_id = module.efs["api_tokens"].file_system_id + root_directory = "/" + transit_encryption = true + mount_path = "/opt/domain-controller/conf/keys" + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = var.desired_count["dc"] + enable_execute_command = true + + target_group_arn = module.cloudbeaver_dc_route.target_group_arn + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } + + depends_on = [module.kafka, module.rds, module.postgres] +} diff --git a/AWS/ecs-fargate/ecs-cloudbeaver-qm.tf b/AWS/ecs-fargate/ecs-cloudbeaver-qm.tf new file mode 100644 index 0000000..c1a567a --- /dev/null +++ b/AWS/ecs-fargate/ecs-cloudbeaver-qm.tf @@ -0,0 +1,60 @@ +################################################################################ +# DBeaver TE QM +################################################################################ + +module "cloudbeaver_qm_route" { + source = "./modules/alb-route" + + name = "${local.name_prefix}-${var.deployment_id}-qm" + vpc_id = local.vpc_id + listener_arn = module.alb.https_listener_arn + path_pattern = "/qm*" + priority = 98 + health_check_path = "/qm/health" + + tags = { Env = var.deployment_id } +} + +module "cloudbeaver_qm" { + source = "./modules/ecs-service" + + name = "cloudbeaver-qm" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "qm" + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-qm:${var.dbeaver_te_version}" + cpu = 1024 + memory = 2048 + container_port = 8972 + + execution_role_arn = module.iam.execution_role_arn + task_role_arn = module.iam.task_role_arn + environment = local.cloudbeaver_shared_env_modified + + efs_volumes = [ + { + name = "cloudbeaver_certificates_public" + file_system_id = module.efs["certificates"].file_system_id + access_point_id = module.efs["certificates"].access_point_id + mount_path = "/opt/query-manager/conf/certificates" + transit_encryption = true + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = var.desired_count["qm"] + enable_execute_command = true + + target_group_arn = module.cloudbeaver_qm_route.target_group_arn + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } + + depends_on = [module.cloudbeaver_dc] +} diff --git a/AWS/ecs-fargate/ecs-cloudbeaver-rm.tf b/AWS/ecs-fargate/ecs-cloudbeaver-rm.tf new file mode 100644 index 0000000..b743af0 --- /dev/null +++ b/AWS/ecs-fargate/ecs-cloudbeaver-rm.tf @@ -0,0 +1,66 @@ +################################################################################ +# DBeaver TE RM +################################################################################ + +module "cloudbeaver_rm_route" { + source = "./modules/alb-route" + + name = "${local.name_prefix}-${var.deployment_id}-rm" + vpc_id = local.vpc_id + listener_arn = module.alb.https_listener_arn + path_pattern = "/rm*" + priority = 97 + health_check_path = "/rm/health" + + tags = { Env = var.deployment_id } +} + +module "cloudbeaver_rm" { + source = "./modules/ecs-service" + + name = "cloudbeaver-rm" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "rm" + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-rm:${var.dbeaver_te_version}" + cpu = 1024 + memory = 2048 + container_port = 8971 + + execution_role_arn = module.iam.execution_role_arn + task_role_arn = module.iam.task_role_arn + environment = local.cloudbeaver_shared_env_modified + + efs_volumes = [ + { + name = "cloudbeaver_rm_data" + file_system_id = module.efs["rm_data"].file_system_id + root_directory = "/" + mount_path = "/opt/resource-manager/workspace" + }, + { + name = "cloudbeaver_certificates_public" + file_system_id = module.efs["certificates"].file_system_id + access_point_id = module.efs["certificates"].access_point_id + mount_path = "/opt/resource-manager/conf/certificates" + transit_encryption = true + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = var.desired_count["rm"] + enable_execute_command = true + + target_group_arn = module.cloudbeaver_rm_route.target_group_arn + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } + + depends_on = [module.cloudbeaver_dc] +} diff --git a/AWS/ecs-fargate/ecs-cloudbeaver-te.tf b/AWS/ecs-fargate/ecs-cloudbeaver-te.tf new file mode 100644 index 0000000..a102208 --- /dev/null +++ b/AWS/ecs-fargate/ecs-cloudbeaver-te.tf @@ -0,0 +1,62 @@ +################################################################################ +# DBeaver TE CloudBeaver +################################################################################ + +module "cloudbeaver_te_route" { + source = "./modules/alb-route" + + name = "${local.name_prefix}-${var.deployment_id}" + vpc_id = local.vpc_id + listener_arn = module.alb.https_listener_arn + path_pattern = "/*" + priority = 200 + health_check_path = "/" + health_check_unhealthy_threshold = 10 + stickiness_enabled = true + + tags = { Env = var.deployment_id } +} + +module "cloudbeaver_te" { + source = "./modules/ecs-service" + + name = "cloudbeaver-te" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "te" + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-te:${var.dbeaver_te_version}" + cpu = 4096 + memory = 8192 + container_port = 8978 + + execution_role_arn = module.iam.execution_role_arn + task_role_arn = module.iam.task_role_arn + environment = local.cloudbeaver_shared_env_modified + + efs_volumes = [ + { + name = "cloudbeaver_certificates_public" + file_system_id = module.efs["certificates"].file_system_id + access_point_id = module.efs["certificates"].access_point_id + mount_path = "/opt/cloudbeaver/conf/certificates" + transit_encryption = true + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = var.desired_count["te"] + enable_execute_command = true + + target_group_arn = module.cloudbeaver_te_route.target_group_arn + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } + + depends_on = [module.cloudbeaver_dc, module.cloudbeaver_qm, module.cloudbeaver_rm] +} diff --git a/AWS/ecs-fargate/ecs-cloudbeaver-tm.tf b/AWS/ecs-fargate/ecs-cloudbeaver-tm.tf new file mode 100644 index 0000000..d6b9728 --- /dev/null +++ b/AWS/ecs-fargate/ecs-cloudbeaver-tm.tf @@ -0,0 +1,66 @@ +################################################################################ +# DBeaver TE TM +################################################################################ + +module "cloudbeaver_tm_route" { + source = "./modules/alb-route" + + name = "${local.name_prefix}-${var.deployment_id}-tm" + vpc_id = local.vpc_id + listener_arn = module.alb.https_listener_arn + path_pattern = "/tm*" + priority = 94 + health_check_path = "/tm/health" + + tags = { Env = var.deployment_id } +} + +module "cloudbeaver_tm" { + source = "./modules/ecs-service" + + name = "cloudbeaver-tm" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "tm" + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-tm:${var.dbeaver_te_version}" + cpu = 2048 + memory = 4096 + container_port = 8973 + + execution_role_arn = module.iam.execution_role_arn + task_role_arn = module.iam.task_role_arn + environment = local.cloudbeaver_shared_env_modified + + efs_volumes = [ + { + name = "cloudbeaver_tm_data" + file_system_id = module.efs["tm_data"].file_system_id + root_directory = "/" + mount_path = "/opt/task-manager/workspace" + }, + { + name = "cloudbeaver_certificates_public" + file_system_id = module.efs["certificates"].file_system_id + access_point_id = module.efs["certificates"].access_point_id + mount_path = "/opt/task-manager/conf/certificates" + transit_encryption = true + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = var.desired_count["tm"] + enable_execute_command = true + + target_group_arn = module.cloudbeaver_tm_route.target_group_arn + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } + + depends_on = [module.cloudbeaver_dc] +} diff --git a/AWS/ecs-fargate/ecs-kafka.tf b/AWS/ecs-fargate/ecs-kafka.tf new file mode 100644 index 0000000..8f8a421 --- /dev/null +++ b/AWS/ecs-fargate/ecs-kafka.tf @@ -0,0 +1,41 @@ +################################################################################ +# Kafka +################################################################################ + +module "kafka" { + source = "./modules/ecs-service" + + name = "kafka" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + deployment_id = var.deployment_id + image = "${var.image_source}/cloudbeaver-kafka:3.9" + cpu = 2048 + memory = 4096 + container_port = 9092 + + execution_role_arn = module.iam.execution_role_arn + + environment = concat(var.cloudbeaver-kafka-env, [ + { + name = "KAFKA_CFG_CONTROLLER_QUORUM_VOTERS" + value = "0@localhost:9093" + }, + { + name = "KAFKA_CFG_ADVERTISED_LISTENERS" + value = "PLAINTEXT://${var.deployment_id}-kafka:9092" + } + ]) + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te_private.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = 1 + enable_execute_command = false + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } +} diff --git a/AWS/ecs-fargate/ecs-postgres.tf b/AWS/ecs-fargate/ecs-postgres.tf new file mode 100644 index 0000000..eb2e0c7 --- /dev/null +++ b/AWS/ecs-fargate/ecs-postgres.tf @@ -0,0 +1,45 @@ +################################################################################ +# Postgres (container-based, disabled when var.rds_db = true) +################################################################################ + +module "postgres" { + source = "./modules/ecs-service" + count = var.rds_db ? 0 : 1 + + name = "postgres" + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + family_suffix = "db" + log_prefix = "db" + deployment_id = var.deployment_id + container_name_override = "${var.deployment_id}-postgres" + image = var.db_image + cpu = 256 + memory = 512 + container_port = 5432 + + execution_role_arn = module.iam.execution_role_arn + + environment = var.cloudbeaver-db-env + + efs_volumes = [ + { + name = "cloudbeaver_db_data" + file_system_id = module.efs["db_data"].file_system_id + root_directory = "/" + mount_path = "/var/lib/postgresql/data" + } + ] + + cluster_id = module.ecs_cluster.id + security_group_ids = [aws_security_group.dbeaver_te_private.id] + subnet_ids = local.private_subnets + service_connect_namespace_arn = aws_service_discovery_private_dns_namespace.dbeaver.arn + desired_count = 1 + enable_execute_command = false + + aws_region = var.aws_region + log_group_name = local.log_group_name + + tags = { Env = var.deployment_id } +} diff --git a/AWS/ecs-fargate/ecs.tf b/AWS/ecs-fargate/ecs.tf new file mode 100644 index 0000000..43ed915 --- /dev/null +++ b/AWS/ecs-fargate/ecs.tf @@ -0,0 +1,24 @@ +################################################################################ +# ECS Cluster +################################################################################ + +module "ecs_cluster" { + source = "./modules/ecs-cluster" + + name = "${local.name_prefix_full}-${var.deployment_id}" + + capacity_providers = ["FARGATE"] + default_capacity_provider = "FARGATE" + default_capacity_provider_base = 1 + default_capacity_provider_weight = 1 + + tags = { + Env = var.deployment_id + } +} + +resource "aws_service_discovery_private_dns_namespace" "dbeaver" { + name = "${var.deployment_id}-${var.dbeaver_te_default_ns}" + description = "DBeaver SD Namespace" + vpc = local.vpc_id +} diff --git a/AWS/ecs-fargate/efs.tf b/AWS/ecs-fargate/efs.tf new file mode 100644 index 0000000..c75f2b3 --- /dev/null +++ b/AWS/ecs-fargate/efs.tf @@ -0,0 +1,21 @@ +################################################################################ +# EFS Volumes +################################################################################ + +module "efs" { + source = "./modules/efs-volume" + for_each = local.efs_volumes + + name = each.value.name + deployment_id = var.deployment_id + name_prefix = local.name_prefix + subnet_ids = local.private_subnets + security_group_ids = [aws_security_group.dbeaver_efs.id] + encrypted = var.efs_encrypted + + access_point = lookup(each.value, "access_point", null) + + tags = { + Env = var.deployment_id + } +} diff --git a/AWS/ecs-fargate/iam.tf b/AWS/ecs-fargate/iam.tf new file mode 100644 index 0000000..7223d57 --- /dev/null +++ b/AWS/ecs-fargate/iam.tf @@ -0,0 +1,17 @@ +################################################################################ +# IAM Roles +################################################################################ + +module "iam" { + source = "./modules/iam" + + deployment_id = var.deployment_id + name_prefix = local.name_prefix + name_prefix_full = local.name_prefix_full + efs_arns = [for k, v in module.efs : v.file_system_arn] + efs_ap_arns = compact([for k, v in module.efs : v.access_point_arn != null ? v.access_point_arn : ""]) + + tags = { + Env = var.deployment_id + } +} diff --git a/AWS/ecs-fargate/locals.tf b/AWS/ecs-fargate/locals.tf new file mode 100644 index 0000000..8d65b69 --- /dev/null +++ b/AWS/ecs-fargate/locals.tf @@ -0,0 +1,72 @@ +locals { + name_prefix = "DBeaverTE" + name_prefix_full = "DBeaverTeamEdition" + + vpc_id = var.create_vpc ? module.vpc[0].vpc_id : var.vpc_id + public_subnets = var.create_vpc ? module.vpc[0].public_subnets : var.public_subnet_ids + private_subnets = var.create_vpc ? module.vpc[0].private_subnets : var.private_subnet_ids + + log_group_name = "${local.name_prefix_full}-${var.deployment_id}" + + rds_db_url = var.rds_db ? "jdbc:postgresql://${module.rds[0].db_instance_address}:5432/cloudbeaver" : "" + + efs_volumes = { + db_data = { name = "cloudbeaver_db_data" } + rm_data = { name = "cloudbeaver_rm_data" } + tm_data = { name = "cloudbeaver_tm_data" } + dc_data = { name = "cloudbeaver_dc_data" } + certificates = { + name = "cloudbeaver_certificates" + access_point = { + path = "/public" + owner_uid = 8978 + owner_gid = 8978 + permissions = "0755" + } + } + api_tokens = { name = "api_tokens" } + } + + cloudbeaver_dc_env_modified = [ + for item in var.cloudbeaver-dc-env : { + name = item.name + value = ( + item.name == "CLOUDBEAVER_DC_BACKEND_DB_URL" ? (var.rds_db ? local.rds_db_url : format("jdbc:postgresql://%s-postgres:5432/cloudbeaver", var.deployment_id)) : + item.name == "CLOUDBEAVER_QM_BACKEND_DB_URL" ? (var.rds_db ? local.rds_db_url : format("jdbc:postgresql://%s-postgres:5432/cloudbeaver", var.deployment_id)) : + item.name == "CLOUDBEAVER_TM_BACKEND_DB_URL" ? (var.rds_db ? local.rds_db_url : format("jdbc:postgresql://%s-postgres:5432/cloudbeaver", var.deployment_id)) : + item.name == "CLOUDBEAVER_DC_SERVER_URL" ? format("http://%s-cloudbeaver-dc:8970/dc", var.deployment_id) : + item.name == "CLOUDBEAVER_QM_SERVER_URL" ? format("http://%s-cloudbeaver-qm:8972/qm", var.deployment_id) : + item.name == "CLOUDBEAVER_RM_SERVER_URL" ? format("http://%s-cloudbeaver-rm:8971/rm", var.deployment_id) : + item.name == "CLOUDBEAVER_TM_SERVER_URL" ? format("http://%s-cloudbeaver-tm:8973/tm", var.deployment_id) : + item.name == "CLOUDBEAVER_KAFKA_BROKERS" ? format("%s-kafka:9092", var.deployment_id) : + item.value + ) + } + ] + + cloudbeaver_shared_env_modified = [ + for item in var.cloudbeaver-shared-env : { + name = item.name + value = ( + item.name == "CLOUDBEAVER_DC_SERVER_URL" ? format("http://%s-cloudbeaver-dc:8970/dc", var.deployment_id) : + item.value + ) + } + ] + + postgres_password = { for item in var.cloudbeaver-db-env : item.name => item.value }["POSTGRES_PASSWORD"] + postgres_user = { for item in var.cloudbeaver-db-env : item.name => item.value }["POSTGRES_USER"] + + updated_cloudbeaver_dc_env = [for item in local.cloudbeaver_dc_env_modified : { + name = item.name + value = ( + item.name == "CLOUDBEAVER_DC_BACKEND_DB_PASSWORD" ? local.postgres_password : + item.name == "CLOUDBEAVER_QM_BACKEND_DB_PASSWORD" ? local.postgres_password : + item.name == "CLOUDBEAVER_TM_BACKEND_DB_PASSWORD" ? local.postgres_password : + item.name == "CLOUDBEAVER_DC_BACKEND_DB_USER" ? local.postgres_user : + item.name == "CLOUDBEAVER_QM_BACKEND_DB_USER" ? local.postgres_user : + item.name == "CLOUDBEAVER_TM_BACKEND_DB_USER" ? local.postgres_user : + item.value + ) + }] +} diff --git a/AWS/ecs-fargate/migration.tf b/AWS/ecs-fargate/migration.tf new file mode 100644 index 0000000..be634f9 --- /dev/null +++ b/AWS/ecs-fargate/migration.tf @@ -0,0 +1,363 @@ +################################################################################ +# VPC +################################################################################ + +moved { + from = aws_vpc.dbeaver_net + to = module.vpc[0].aws_vpc.this +} + +moved { + from = aws_subnet.public_subnets[0] + to = module.vpc[0].aws_subnet.public[0] +} + +moved { + from = aws_subnet.public_subnets[1] + to = module.vpc[0].aws_subnet.public[1] +} + +moved { + from = aws_subnet.private_subnets[0] + to = module.vpc[0].aws_subnet.private[0] +} + +moved { + from = aws_subnet.private_subnets[1] + to = module.vpc[0].aws_subnet.private[1] +} + +moved { + from = aws_internet_gateway.dbeaver_gw + to = module.vpc[0].aws_internet_gateway.this +} + +moved { + from = aws_eip.dbeaver_nat_gateway + to = module.vpc[0].aws_eip.nat +} + +moved { + from = aws_nat_gateway.nat_gateway + to = module.vpc[0].aws_nat_gateway.this +} + +removed { + from = aws_route.dbeaver_vpc_main_gw + lifecycle { destroy = true } +} + +removed { + from = aws_route_table.dbeaver_private_rt_nat + lifecycle { destroy = true } +} + +removed { + from = aws_route_table_association.private_subnets_rt + lifecycle { destroy = true } +} + + +################################################################################ +# IAM +################################################################################ + +moved { + from = aws_iam_role.ecsTaskExecutionRole + to = module.iam.aws_iam_role.execution +} + +moved { + from = aws_iam_role.ecs_task_role_exec + to = module.iam.aws_iam_role.task +} + +moved { + from = aws_iam_policy.CloudbeaverTeamEditionEFSAccessPolicy + to = module.iam.aws_iam_policy.efs_access +} + +moved { + from = aws_iam_role_policy_attachment.ecsTaskExecutionRole_policy + to = module.iam.aws_iam_role_policy_attachment.execution_ecs +} + +moved { + from = aws_iam_role_policy_attachment.TeamEditionEFSAccessPolicy_attachment + to = module.iam.aws_iam_role_policy_attachment.execution_efs +} + +moved { + from = aws_iam_role_policy_attachment.ecs_task_role_exec_ssm + to = module.iam.aws_iam_role_policy_attachment.task_ssm +} + +removed { + from = aws_iam_role_policy_attachment.logs_policy_attachment + lifecycle { destroy = true } +} + +removed { + from = aws_iam_role_policy_attachment.ecs_task_role_exec_logs + lifecycle { destroy = true } +} + + +################################################################################ +# RDS +################################################################################ + +moved { + from = aws_db_subnet_group.rds_dbeaver_db_subnet[0] + to = module.rds[0].aws_db_subnet_group.this +} + +moved { + from = aws_db_instance.rds_dbeaver_db[0] + to = module.rds[0].aws_db_instance.this +} + + +################################################################################ +# EFS +################################################################################ + +moved { + from = aws_efs_file_system.cloudbeaver_db_data + to = module.efs["db_data"].aws_efs_file_system.this +} + +moved { + from = aws_efs_mount_target.cloudbeaver_db_data_mt[0] + to = module.efs["db_data"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_db_data_mt[1] + to = module.efs["db_data"].aws_efs_mount_target.this[1] +} + +moved { + from = aws_efs_file_system.cloudbeaver_dc_data + to = module.efs["dc_data"].aws_efs_file_system.this +} + +moved { + from = aws_efs_mount_target.cloudbeaver_dc_data_mt[0] + to = module.efs["dc_data"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_dc_data_mt[1] + to = module.efs["dc_data"].aws_efs_mount_target.this[1] +} + +moved { + from = aws_efs_file_system.cloudbeaver_rm_data + to = module.efs["rm_data"].aws_efs_file_system.this +} + +moved { + from = aws_efs_mount_target.cloudbeaver_rm_data_mt[0] + to = module.efs["rm_data"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_rm_data_mt[1] + to = module.efs["rm_data"].aws_efs_mount_target.this[1] +} + +moved { + from = aws_efs_file_system.cloudbeaver_tm_data + to = module.efs["tm_data"].aws_efs_file_system.this +} + +moved { + from = aws_efs_mount_target.cloudbeaver_tm_data_mt[0] + to = module.efs["tm_data"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_tm_data_mt[1] + to = module.efs["tm_data"].aws_efs_mount_target.this[1] +} + +moved { + from = aws_efs_file_system.cloudbeaver_certificates + to = module.efs["certificates"].aws_efs_file_system.this +} + +moved { + from = aws_efs_access_point.certs_public + to = module.efs["certificates"].aws_efs_access_point.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_certificates_mt[0] + to = module.efs["certificates"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.cloudbeaver_certificates_mt[1] + to = module.efs["certificates"].aws_efs_mount_target.this[1] +} + +moved { + from = aws_efs_file_system.api_tokens + to = module.efs["api_tokens"].aws_efs_file_system.this +} + +moved { + from = aws_efs_mount_target.api_tokens_mt[0] + to = module.efs["api_tokens"].aws_efs_mount_target.this[0] +} + +moved { + from = aws_efs_mount_target.api_tokens_mt[1] + to = module.efs["api_tokens"].aws_efs_mount_target.this[1] +} + + +################################################################################ +# ECS +################################################################################ + +moved { + from = aws_ecs_cluster.dbeaver_te + to = module.ecs_cluster.aws_ecs_cluster.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_te + to = module.cloudbeaver_te.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_dc + to = module.cloudbeaver_dc.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_qm + to = module.cloudbeaver_qm.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_rm + to = module.cloudbeaver_rm.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_tm + to = module.cloudbeaver_tm.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.kafka + to = module.kafka.aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_task_definition.dbeaver_db[0] + to = module.postgres[0].aws_ecs_task_definition.this +} + +moved { + from = aws_ecs_service.te + to = module.cloudbeaver_te.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.dc + to = module.cloudbeaver_dc.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.qm + to = module.cloudbeaver_qm.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.rm + to = module.cloudbeaver_rm.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.tm + to = module.cloudbeaver_tm.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.kafka + to = module.kafka.aws_ecs_service.this +} + +moved { + from = aws_ecs_service.postgres[0] + to = module.postgres[0].aws_ecs_service.this +} + + +################################################################################ +# ALB +################################################################################ + +moved { + from = aws_lb.dbeaver_te_lb + to = module.alb.aws_lb.this +} + +moved { + from = aws_lb_listener.dbeaver-te-listener + to = module.alb.aws_lb_listener.http +} + +moved { + from = aws_lb_listener.dbeaver-te-listener-https + to = module.alb.aws_lb_listener.https +} + +moved { + from = aws_lb_target_group.dbeaver_te + to = module.cloudbeaver_te_route.aws_lb_target_group.this +} + +moved { + from = aws_lb_target_group.dbeaver_dc + to = module.cloudbeaver_dc_route.aws_lb_target_group.this +} + +moved { + from = aws_lb_target_group.dbeaver_qm + to = module.cloudbeaver_qm_route.aws_lb_target_group.this +} + +moved { + from = aws_lb_target_group.dbeaver_rm + to = module.cloudbeaver_rm_route.aws_lb_target_group.this +} + +moved { + from = aws_lb_target_group.dbeaver_tm + to = module.cloudbeaver_tm_route.aws_lb_target_group.this +} + +moved { + from = aws_lb_listener_rule.forward_to_service_uri_dc + to = module.cloudbeaver_dc_route.aws_lb_listener_rule.this +} + +moved { + from = aws_lb_listener_rule.forward_to_service_uri_qm + to = module.cloudbeaver_qm_route.aws_lb_listener_rule.this +} + +moved { + from = aws_lb_listener_rule.forward_to_service_uri_rm + to = module.cloudbeaver_rm_route.aws_lb_listener_rule.this +} + +moved { + from = aws_lb_listener_rule.forward_to_service_uri_tm + to = module.cloudbeaver_tm_route.aws_lb_listener_rule.this +} diff --git a/AWS/ecs-fargate/modules/alb-route/main.tf b/AWS/ecs-fargate/modules/alb-route/main.tf new file mode 100644 index 0000000..1c37873 --- /dev/null +++ b/AWS/ecs-fargate/modules/alb-route/main.tf @@ -0,0 +1,43 @@ +resource "aws_lb_target_group" "this" { + name = var.name + port = 80 + protocol = "HTTP" + target_type = "ip" + vpc_id = var.vpc_id + + health_check { + matcher = var.health_check_matcher + unhealthy_threshold = var.health_check_unhealthy_threshold + enabled = true + path = var.health_check_path + } + + dynamic "stickiness" { + for_each = var.stickiness_enabled ? [1] : [] + content { + enabled = true + type = "lb_cookie" + cookie_duration = var.stickiness_duration + } + } + + tags = var.tags +} + +resource "aws_lb_listener_rule" "this" { + listener_arn = var.listener_arn + priority = var.priority + + condition { + path_pattern { + values = [var.path_pattern] + } + } + + action { + type = "forward" + target_group_arn = aws_lb_target_group.this.arn + } + + tags = var.tags +} diff --git a/AWS/ecs-fargate/modules/alb-route/outputs.tf b/AWS/ecs-fargate/modules/alb-route/outputs.tf new file mode 100644 index 0000000..4198db6 --- /dev/null +++ b/AWS/ecs-fargate/modules/alb-route/outputs.tf @@ -0,0 +1,4 @@ +output "target_group_arn" { + description = "ARN of the ALB target group" + value = aws_lb_target_group.this.arn +} diff --git a/AWS/ecs-fargate/modules/alb-route/variables.tf b/AWS/ecs-fargate/modules/alb-route/variables.tf new file mode 100644 index 0000000..445581f --- /dev/null +++ b/AWS/ecs-fargate/modules/alb-route/variables.tf @@ -0,0 +1,61 @@ +variable "name" { + description = "Name for the target group" + type = string +} + +variable "vpc_id" { + description = "VPC ID for the target group" + type = string +} + +variable "listener_arn" { + description = "ARN of the ALB HTTPS listener" + type = string +} + +variable "path_pattern" { + description = "ALB path pattern for the listener rule" + type = string +} + +variable "priority" { + description = "Priority for the ALB listener rule" + type = number + default = 100 +} + +variable "health_check_path" { + description = "Health check path for the target group" + type = string + default = "/" +} + +variable "health_check_matcher" { + description = "HTTP status codes for health check success" + type = string + default = "200,302" +} + +variable "health_check_unhealthy_threshold" { + description = "Number of consecutive failures before marking unhealthy" + type = number + default = 7 +} + +variable "stickiness_enabled" { + description = "Whether to enable session stickiness on the target group" + type = bool + default = false +} + +variable "stickiness_duration" { + description = "Cookie duration in seconds for session stickiness" + type = number + default = 86400 +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/alb/main.tf b/AWS/ecs-fargate/modules/alb/main.tf new file mode 100644 index 0000000..c9753aa --- /dev/null +++ b/AWS/ecs-fargate/modules/alb/main.tf @@ -0,0 +1,49 @@ +resource "aws_lb" "this" { + name = var.name + internal = var.internal + load_balancer_type = "application" + security_groups = var.security_group_ids + subnets = var.public_subnets + + tags = merge(var.tags, { + Name = var.name + }) +} + +resource "aws_lb_listener" "http" { + load_balancer_arn = aws_lb.this.arn + port = 80 + protocol = "HTTP" + + default_action { + type = "redirect" + + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } + + tags = var.tags +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.this.arn + port = 443 + protocol = "HTTPS" + ssl_policy = var.ssl_policy + certificate_arn = var.certificate_arn + + default_action { + type = "fixed-response" + + fixed_response { + content_type = "text/plain" + message_body = "Not Found" + status_code = "404" + } + } + + tags = var.tags +} diff --git a/AWS/ecs-fargate/modules/alb/outputs.tf b/AWS/ecs-fargate/modules/alb/outputs.tf new file mode 100644 index 0000000..3d98b51 --- /dev/null +++ b/AWS/ecs-fargate/modules/alb/outputs.tf @@ -0,0 +1,24 @@ +output "alb_arn" { + description = "ARN of the Application Load Balancer" + value = aws_lb.this.arn +} + +output "alb_dns_name" { + description = "DNS name of the Application Load Balancer" + value = aws_lb.this.dns_name +} + +output "alb_zone_id" { + description = "Hosted zone ID of the ALB" + value = aws_lb.this.zone_id +} + +output "https_listener_arn" { + description = "ARN of the HTTPS listener" + value = aws_lb_listener.https.arn +} + +output "http_listener_arn" { + description = "ARN of the HTTP listener" + value = aws_lb_listener.http.arn +} diff --git a/AWS/ecs-fargate/modules/alb/variables.tf b/AWS/ecs-fargate/modules/alb/variables.tf new file mode 100644 index 0000000..7ed3251 --- /dev/null +++ b/AWS/ecs-fargate/modules/alb/variables.tf @@ -0,0 +1,47 @@ +variable "name" { + description = "Name for the ALB" + type = string +} + +variable "deployment_id" { + description = "Deployment identifier" + type = string +} + +variable "vpc_id" { + description = "VPC ID for the ALB" + type = string +} + +variable "public_subnets" { + description = "List of public subnet IDs for ALB placement" + type = list(string) +} + +variable "security_group_ids" { + description = "Security group IDs to attach to the ALB" + type = list(string) +} + +variable "certificate_arn" { + description = "ACM certificate ARN for HTTPS listener" + type = string +} + +variable "ssl_policy" { + description = "SSL policy for the HTTPS listener" + type = string + default = "ELBSecurityPolicy-TLS13-1-2-2021-06" +} + +variable "internal" { + description = "Whether the ALB is internal" + type = bool + default = false +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/ecs-cluster/main.tf b/AWS/ecs-fargate/modules/ecs-cluster/main.tf new file mode 100644 index 0000000..b1ea3cb --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-cluster/main.tf @@ -0,0 +1,24 @@ +resource "aws_ecs_cluster" "this" { + name = var.name + + dynamic "setting" { + for_each = var.container_insights_enabled ? [1] : [] + content { + name = "containerInsights" + value = "enabled" + } + } + + tags = var.tags +} + +resource "aws_ecs_cluster_capacity_providers" "this" { + cluster_name = aws_ecs_cluster.this.name + capacity_providers = var.capacity_providers + + default_capacity_provider_strategy { + base = var.default_capacity_provider_base + weight = var.default_capacity_provider_weight + capacity_provider = var.default_capacity_provider + } +} diff --git a/AWS/ecs-fargate/modules/ecs-cluster/outputs.tf b/AWS/ecs-fargate/modules/ecs-cluster/outputs.tf new file mode 100644 index 0000000..b974cb5 --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-cluster/outputs.tf @@ -0,0 +1,14 @@ +output "id" { + description = "ID of the ECS cluster" + value = aws_ecs_cluster.this.id +} + +output "arn" { + description = "ARN of the ECS cluster" + value = aws_ecs_cluster.this.arn +} + +output "name" { + description = "Name of the ECS cluster" + value = aws_ecs_cluster.this.name +} diff --git a/AWS/ecs-fargate/modules/ecs-cluster/variables.tf b/AWS/ecs-fargate/modules/ecs-cluster/variables.tf new file mode 100644 index 0000000..2944804 --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-cluster/variables.tf @@ -0,0 +1,40 @@ +variable "name" { + description = "Name of the ECS cluster" + type = string +} + +variable "capacity_providers" { + description = "List of capacity providers associated with the cluster" + type = list(string) + default = ["FARGATE", "FARGATE_SPOT"] +} + +variable "default_capacity_provider" { + description = "Name of the default capacity provider" + type = string + default = "FARGATE" +} + +variable "default_capacity_provider_base" { + description = "Minimum number of tasks to run with the default provider" + type = number + default = 1 +} + +variable "default_capacity_provider_weight" { + description = "Relative percentage of tasks to run with the default provider" + type = number + default = 1 +} + +variable "container_insights_enabled" { + description = "Whether to enable CloudWatch Container Insights" + type = bool + default = false +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/ecs-service/main.tf b/AWS/ecs-fargate/modules/ecs-service/main.tf new file mode 100644 index 0000000..29d18b4 --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-service/main.tf @@ -0,0 +1,109 @@ +locals { + family_suffix = coalesce(var.family_suffix, var.name) + log_prefix = coalesce(var.log_prefix, local.family_suffix) + container_name = coalesce(var.container_name_override, "${var.deployment_id}-${var.name}") + service_name = "${var.deployment_id}-${var.name}" + family_name = "${var.name_prefix_full}-${var.deployment_id}-${local.family_suffix}" +} + +resource "aws_ecs_task_definition" "this" { + family = local.family_name + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.cpu + memory = var.memory + execution_role_arn = var.execution_role_arn + task_role_arn = var.task_role_arn + + dynamic "volume" { + for_each = var.efs_volumes + content { + name = "${var.deployment_id}-${volume.value.name}" + efs_volume_configuration { + file_system_id = volume.value.file_system_id + root_directory = volume.value.access_point_id != null ? null : coalesce(volume.value.root_directory, "/") + transit_encryption = volume.value.transit_encryption || volume.value.access_point_id != null ? "ENABLED" : null + + dynamic "authorization_config" { + for_each = volume.value.access_point_id != null ? [1] : [] + content { + access_point_id = volume.value.access_point_id + iam = "DISABLED" + } + } + } + } + } + + container_definitions = jsonencode([{ + name = local.container_name + image = var.image + essential = true + + environment = var.environment + + mountPoints = [ + for vol in var.efs_volumes : { + containerPath = vol.mount_path + sourceVolume = "${var.deployment_id}-${vol.name}" + } + ] + + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = var.log_group_name + awslogs-region = var.aws_region + awslogs-create-group = "true" + awslogs-stream-prefix = local.log_prefix + } + } + + portMappings = [{ + name = local.service_name + protocol = "tcp" + containerPort = var.container_port + hostPort = var.container_port + }] + }]) + + tags = var.tags +} + +resource "aws_ecs_service" "this" { + name = local.service_name + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.this.arn + launch_type = "FARGATE" + desired_count = var.desired_count + enable_execute_command = var.enable_execute_command + + network_configuration { + security_groups = var.security_group_ids + subnets = var.subnet_ids + assign_public_ip = var.assign_public_ip + } + + service_connect_configuration { + enabled = true + namespace = var.service_connect_namespace_arn + service { + port_name = local.service_name + client_alias { + dns_name = local.service_name + port = var.container_port + } + } + } + + dynamic "load_balancer" { + for_each = var.target_group_arn != null ? [1] : [] + content { + target_group_arn = var.target_group_arn + container_name = local.container_name + container_port = var.container_port + } + } + + tags = var.tags +} diff --git a/AWS/ecs-fargate/modules/ecs-service/outputs.tf b/AWS/ecs-fargate/modules/ecs-service/outputs.tf new file mode 100644 index 0000000..3a044ce --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-service/outputs.tf @@ -0,0 +1,19 @@ +output "service_arn" { + description = "ARN of the ECS service" + value = aws_ecs_service.this.id +} + +output "service_name" { + description = "Name of the ECS service" + value = aws_ecs_service.this.name +} + +output "task_definition_arn" { + description = "ARN of the task definition" + value = aws_ecs_task_definition.this.arn +} + +output "container_name" { + description = "Name of the container in the task definition" + value = local.container_name +} diff --git a/AWS/ecs-fargate/modules/ecs-service/variables.tf b/AWS/ecs-fargate/modules/ecs-service/variables.tf new file mode 100644 index 0000000..3c0331b --- /dev/null +++ b/AWS/ecs-fargate/modules/ecs-service/variables.tf @@ -0,0 +1,150 @@ +variable "name" { + description = "Service name" + type = string +} + +variable "deployment_id" { + description = "Deployment identifier" + type = string +} + +variable "name_prefix" { + description = "Short prefix for resource names" + type = string +} + +variable "name_prefix_full" { + description = "Full prefix for resource names" + type = string +} + +variable "family_suffix" { + description = "Task definition family suffix" + type = string + default = null +} + +variable "log_prefix" { + description = "CloudWatch Logs stream prefix" + type = string + default = null +} + +variable "image" { + description = "Container image URI" + type = string +} + +variable "cpu" { + description = "CPU units for the task" + type = number +} + +variable "memory" { + description = "Memory in MiB for the task" + type = number +} + +variable "container_port" { + description = "Port the container listens on" + type = number +} + +variable "container_name_override" { + description = "Override container name" + type = string + default = null +} + +variable "execution_role_arn" { + description = "ARN of the ECS task execution role" + type = string +} + +variable "task_role_arn" { + description = "ARN of the ECS task role" + type = string + default = null +} + +variable "environment" { + description = "Environment variables for the container" + type = list(object({ + name = string + value = string + })) + default = [] +} + +variable "efs_volumes" { + description = "EFS volumes to attach to the task" + type = list(object({ + name = string + file_system_id = string + root_directory = optional(string, null) + transit_encryption = optional(bool, false) + access_point_id = optional(string, null) + mount_path = string + })) + default = [] +} + +variable "cluster_id" { + description = "ECS cluster ID" + type = string +} + +variable "security_group_ids" { + description = "Security group IDs for the ECS service" + type = list(string) +} + +variable "subnet_ids" { + description = "Subnet IDs for the ECS service" + type = list(string) +} + +variable "service_connect_namespace_arn" { + description = "ARN of the Service Connect namespace" + type = string +} + +variable "desired_count" { + description = "Number of task instances" + type = number + default = 1 +} + +variable "enable_execute_command" { + description = "Enable ECS Exec" + type = bool + default = true +} + +variable "assign_public_ip" { + description = "Assign a public IP to the task ENI" + type = bool + default = false +} + +variable "target_group_arn" { + description = "ARN of the ALB target group" + type = string + default = null +} + +variable "aws_region" { + description = "AWS region" + type = string +} + +variable "log_group_name" { + description = "CloudWatch log group name" + type = string +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/efs-volume/main.tf b/AWS/ecs-fargate/modules/efs-volume/main.tf new file mode 100644 index 0000000..04b0851 --- /dev/null +++ b/AWS/ecs-fargate/modules/efs-volume/main.tf @@ -0,0 +1,41 @@ +resource "aws_efs_file_system" "this" { + creation_token = "${var.deployment_id}-${var.name}" + performance_mode = var.performance_mode + throughput_mode = var.throughput_mode + encrypted = var.encrypted + + tags = merge(var.tags, { + Name = "${var.name_prefix}-${var.deployment_id}-${var.name}" + }) +} + +resource "aws_efs_mount_target" "this" { + count = length(var.subnet_ids) + file_system_id = aws_efs_file_system.this.id + subnet_id = var.subnet_ids[count.index] + security_groups = var.security_group_ids +} + +resource "aws_efs_access_point" "this" { + count = var.access_point != null ? 1 : 0 + file_system_id = aws_efs_file_system.this.id + + root_directory { + path = var.access_point.path + + creation_info { + owner_uid = var.access_point.owner_uid + owner_gid = var.access_point.owner_gid + permissions = var.access_point.permissions + } + } + + posix_user { + uid = var.access_point.owner_uid + gid = var.access_point.owner_gid + } + + tags = merge(var.tags, { + Name = "${var.name_prefix}-${var.deployment_id}-${var.name}" + }) +} diff --git a/AWS/ecs-fargate/modules/efs-volume/outputs.tf b/AWS/ecs-fargate/modules/efs-volume/outputs.tf new file mode 100644 index 0000000..1bf5ccd --- /dev/null +++ b/AWS/ecs-fargate/modules/efs-volume/outputs.tf @@ -0,0 +1,28 @@ +output "file_system_id" { + description = "The ID of the EFS file system" + value = aws_efs_file_system.this.id + depends_on = [aws_efs_mount_target.this] +} + +output "file_system_arn" { + description = "The ARN of the EFS file system" + value = aws_efs_file_system.this.arn + depends_on = [aws_efs_mount_target.this] +} + +output "access_point_id" { + description = "The ID of the EFS access point" + value = try(aws_efs_access_point.this[0].id, null) + depends_on = [aws_efs_mount_target.this] +} + +output "access_point_arn" { + description = "The ARN of the EFS access point" + value = try(aws_efs_access_point.this[0].arn, null) + depends_on = [aws_efs_mount_target.this] +} + +output "mount_target_ids" { + description = "List of mount target IDs" + value = aws_efs_mount_target.this[*].id +} diff --git a/AWS/ecs-fargate/modules/efs-volume/variables.tf b/AWS/ecs-fargate/modules/efs-volume/variables.tf new file mode 100644 index 0000000..e0f0ce5 --- /dev/null +++ b/AWS/ecs-fargate/modules/efs-volume/variables.tf @@ -0,0 +1,59 @@ +variable "name" { + description = "Name suffix for the EFS file system" + type = string +} + +variable "deployment_id" { + description = "Deployment identifier used for naming and tagging" + type = string +} + +variable "name_prefix" { + description = "Prefix for resource names and tags" + type = string +} + +variable "subnet_ids" { + description = "List of subnet IDs for EFS mount targets" + type = list(string) +} + +variable "security_group_ids" { + description = "Security group IDs to attach to mount targets" + type = list(string) +} + +variable "encrypted" { + description = "Whether to enable encryption at rest" + type = bool + default = false +} + +variable "performance_mode" { + description = "EFS performance mode" + type = string + default = "generalPurpose" +} + +variable "throughput_mode" { + description = "EFS throughput mode" + type = string + default = "bursting" +} + +variable "access_point" { + description = "Optional access point configuration" + type = object({ + path = string + owner_uid = number + owner_gid = number + permissions = string + }) + default = null +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/iam/main.tf b/AWS/ecs-fargate/modules/iam/main.tf new file mode 100644 index 0000000..8cb4a8a --- /dev/null +++ b/AWS/ecs-fargate/modules/iam/main.tf @@ -0,0 +1,126 @@ +data "aws_iam_policy_document" "assume_role_policy" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "execution" { + name = "${var.name_prefix}-${var.deployment_id}-ecsTaskExecutionRole" + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "execution_ecs" { + role = aws_iam_role.execution.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +resource "aws_iam_policy" "efs_access" { + name = "${var.name_prefix}-${var.deployment_id}-EFSAccessPolicy" + description = "EFS access policy scoped to ${var.deployment_id} file systems" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "elasticfilesystem:DescribeFileSystems", + "elasticfilesystem:DescribeMountTargets", + "elasticfilesystem:DescribeMountTargetSecurityGroups", + "elasticfilesystem:DescribeTags", + "elasticfilesystem:CreateMountTarget", + "elasticfilesystem:DeleteMountTarget", + "elasticfilesystem:ModifyMountTargetSecurityGroups", + "elasticfilesystem:ListTagsForResource", + "elasticfilesystem:TagResource", + "elasticfilesystem:UntagResource" + ] + Resource = var.efs_arns + }, + { + Effect = "Allow" + Action = [ + "elasticfilesystem:CreateTags", + "elasticfilesystem:DeleteTags", + "elasticfilesystem:DescribeFileSystemPolicy", + "elasticfilesystem:PutFileSystemPolicy" + ] + Resource = var.efs_arns + }, + { + Effect = "Allow" + Action = [ + "elasticfilesystem:ClientMount", + "elasticfilesystem:ClientWrite", + "elasticfilesystem:ClientRootAccess" + ] + Resource = concat(var.efs_arns, var.efs_ap_arns) + }, + { + Effect = "Allow" + Action = "elasticfilesystem:DescribeAccessPoints" + Resource = "*" + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "execution_efs" { + role = aws_iam_role.execution.name + policy_arn = aws_iam_policy.efs_access.arn +} + +resource "aws_iam_policy" "cloudwatch_logs" { + name = "${var.name_prefix}-${var.deployment_id}-CloudWatchLogsPolicy" + description = "Scoped CloudWatch Logs policy for ${var.deployment_id}" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "logs:DescribeLogGroups" + ] + Resource = "arn:aws:logs:*:*:log-group:${var.name_prefix_full}-${var.deployment_id}*" + } + ] + }) + + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "execution_logs" { + role = aws_iam_role.execution.name + policy_arn = aws_iam_policy.cloudwatch_logs.arn +} + + +resource "aws_iam_role" "task" { + name = "${var.name_prefix}-${var.deployment_id}-ecsTaskRoleExec" + assume_role_policy = data.aws_iam_policy_document.assume_role_policy.json + tags = var.tags +} + +resource "aws_iam_role_policy_attachment" "task_ssm" { + role = aws_iam_role.task.name + policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" +} + +resource "aws_iam_role_policy_attachment" "task_logs" { + role = aws_iam_role.task.name + policy_arn = aws_iam_policy.cloudwatch_logs.arn +} diff --git a/AWS/ecs-fargate/modules/iam/outputs.tf b/AWS/ecs-fargate/modules/iam/outputs.tf new file mode 100644 index 0000000..25abee4 --- /dev/null +++ b/AWS/ecs-fargate/modules/iam/outputs.tf @@ -0,0 +1,28 @@ +output "execution_role_arn" { + description = "ARN of the ECS task execution role" + value = aws_iam_role.execution.arn + depends_on = [ + aws_iam_role_policy_attachment.execution_ecs, + aws_iam_role_policy_attachment.execution_efs, + aws_iam_role_policy_attachment.execution_logs, + ] +} + +output "execution_role_name" { + description = "Name of the ECS task execution role" + value = aws_iam_role.execution.name +} + +output "task_role_arn" { + description = "ARN of the ECS task role" + value = aws_iam_role.task.arn + depends_on = [ + aws_iam_role_policy_attachment.task_ssm, + aws_iam_role_policy_attachment.task_logs, + ] +} + +output "task_role_name" { + description = "Name of the ECS task role" + value = aws_iam_role.task.name +} diff --git a/AWS/ecs-fargate/modules/iam/variables.tf b/AWS/ecs-fargate/modules/iam/variables.tf new file mode 100644 index 0000000..2cc05e5 --- /dev/null +++ b/AWS/ecs-fargate/modules/iam/variables.tf @@ -0,0 +1,32 @@ +variable "deployment_id" { + description = "Deployment identifier used for naming" + type = string +} + +variable "name_prefix" { + description = "Short prefix for resource names" + type = string +} + +variable "name_prefix_full" { + description = "Full prefix for log group ARN pattern" + type = string +} + +variable "efs_arns" { + description = "List of EFS file system ARNs for the access policy" + type = list(string) + default = [] +} + +variable "efs_ap_arns" { + description = "List of EFS access point ARNs for the mount policy" + type = list(string) + default = [] +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/rds/main.tf b/AWS/ecs-fargate/modules/rds/main.tf new file mode 100644 index 0000000..800b521 --- /dev/null +++ b/AWS/ecs-fargate/modules/rds/main.tf @@ -0,0 +1,28 @@ +resource "aws_db_subnet_group" "this" { + name = var.subnet_group_name + subnet_ids = var.subnet_ids + + tags = var.tags +} + +resource "aws_db_instance" "this" { + identifier = var.identifier + + engine = var.engine + engine_version = var.engine_version + instance_class = var.instance_class + + allocated_storage = var.allocated_storage + storage_type = var.storage_type + + db_name = var.db_name + username = var.username + password = var.password + + db_subnet_group_name = aws_db_subnet_group.this.name + vpc_security_group_ids = var.vpc_security_group_ids + + skip_final_snapshot = var.skip_final_snapshot + + tags = var.tags +} \ No newline at end of file diff --git a/AWS/ecs-fargate/modules/rds/outputs.tf b/AWS/ecs-fargate/modules/rds/outputs.tf new file mode 100644 index 0000000..ea2ab02 --- /dev/null +++ b/AWS/ecs-fargate/modules/rds/outputs.tf @@ -0,0 +1,14 @@ +output "db_instance_address" { + description = "Hostname of the RDS instance" + value = aws_db_instance.this.address +} + +output "db_instance_endpoint" { + description = "Connection endpoint" + value = aws_db_instance.this.endpoint +} + +output "db_instance_port" { + description = "Port of the RDS instance" + value = aws_db_instance.this.port +} diff --git a/AWS/ecs-fargate/modules/rds/variables.tf b/AWS/ecs-fargate/modules/rds/variables.tf new file mode 100644 index 0000000..50f19b9 --- /dev/null +++ b/AWS/ecs-fargate/modules/rds/variables.tf @@ -0,0 +1,76 @@ +variable "identifier" { + description = "RDS instance identifier (lowercase, hyphens)" + type = string +} + +variable "subnet_group_name" { + description = "Name for the DB subnet group" + type = string +} + +variable "subnet_ids" { + description = "List of subnet IDs for the DB subnet group" + type = list(string) +} + +variable "engine" { + description = "Database engine" + type = string + default = "postgres" +} + +variable "engine_version" { + description = "Database engine version" + type = string +} + +variable "instance_class" { + description = "RDS instance class" + type = string + default = "db.t3.micro" +} + +variable "allocated_storage" { + description = "Allocated storage in GB" + type = number + default = 20 +} + +variable "storage_type" { + description = "Storage type (gp2, gp3)" + type = string + default = "gp2" +} + +variable "db_name" { + description = "Initial database name" + type = string +} + +variable "username" { + description = "Master username" + type = string +} + +variable "password" { + description = "Master password" + type = string + sensitive = true +} + +variable "vpc_security_group_ids" { + description = "List of security group IDs attached to the RDS instance" + type = list(string) +} + +variable "skip_final_snapshot" { + description = "Whether to skip the final snapshot on deletion" + type = bool + default = true +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/modules/vpc/main.tf b/AWS/ecs-fargate/modules/vpc/main.tf new file mode 100644 index 0000000..dc28ce6 --- /dev/null +++ b/AWS/ecs-fargate/modules/vpc/main.tf @@ -0,0 +1,132 @@ +################################################################################ +# Availability Zones +################################################################################ + +data "aws_availability_zones" "available" { + state = "available" +} + +locals { + azs = slice(data.aws_availability_zones.available.names, 0, length(var.public_subnet_cidrs)) +} + +################################################################################ +# VPC +################################################################################ + +resource "aws_vpc" "this" { + cidr_block = var.cidr + instance_tenancy = "default" + enable_dns_hostnames = true + enable_dns_support = true + + tags = merge(var.tags, { + Name = var.name + }) +} + +################################################################################ +# Subnets +################################################################################ + +resource "aws_subnet" "public" { + count = length(var.public_subnet_cidrs) + + vpc_id = aws_vpc.this.id + cidr_block = var.public_subnet_cidrs[count.index] + availability_zone = local.azs[count.index] + map_public_ip_on_launch = true + + tags = merge(var.tags, { + Name = "${var.name}-public-${count.index + 1}" + }) +} + +resource "aws_subnet" "private" { + count = length(var.private_subnet_cidrs) + + vpc_id = aws_vpc.this.id + cidr_block = var.private_subnet_cidrs[count.index] + availability_zone = local.azs[count.index] + + tags = merge(var.tags, { + Name = "${var.name}-private-${count.index + 1}" + }) +} + +################################################################################ +# Internet Gateway +################################################################################ + +resource "aws_internet_gateway" "this" { + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name}-igw" + }) +} + +resource "aws_route_table" "public" { + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name}-public-rt" + }) +} + +resource "aws_route" "public_internet_gateway" { + route_table_id = aws_route_table.public.id + destination_cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.this.id +} + +resource "aws_route_table_association" "public" { + count = length(aws_subnet.public) + + subnet_id = aws_subnet.public[count.index].id + route_table_id = aws_route_table.public.id +} + +################################################################################ +# NAT Gateway +################################################################################ + +resource "aws_eip" "nat" { + domain = "vpc" + + tags = merge(var.tags, { + Name = "${var.name}-nat-eip" + }) +} + +resource "aws_nat_gateway" "this" { + allocation_id = aws_eip.nat.id + subnet_id = aws_subnet.public[0].id + + tags = merge(var.tags, { + Name = "${var.name}-nat" + }) + + depends_on = [aws_internet_gateway.this] +} + +resource "aws_route_table" "private" { + vpc_id = aws_vpc.this.id + + tags = merge(var.tags, { + Name = "${var.name}-private-rt" + }) +} + +resource "aws_route" "private_nat_gateway" { + route_table_id = aws_route_table.private.id + destination_cidr_block = "0.0.0.0/0" + nat_gateway_id = aws_nat_gateway.this.id +} + +resource "aws_route_table_association" "private" { + count = length(aws_subnet.private) + + subnet_id = aws_subnet.private[count.index].id + route_table_id = aws_route_table.private.id +} diff --git a/AWS/ecs-fargate/modules/vpc/outputs.tf b/AWS/ecs-fargate/modules/vpc/outputs.tf new file mode 100644 index 0000000..bc37cbd --- /dev/null +++ b/AWS/ecs-fargate/modules/vpc/outputs.tf @@ -0,0 +1,24 @@ +output "vpc_id" { + description = "ID of the VPC" + value = aws_vpc.this.id +} + +output "vpc_cidr_block" { + description = "CIDR block of the VPC" + value = aws_vpc.this.cidr_block +} + +output "public_subnets" { + description = "List of public subnet IDs" + value = aws_subnet.public[*].id +} + +output "private_subnets" { + description = "List of private subnet IDs" + value = aws_subnet.private[*].id +} + +output "nat_gateway_id" { + description = "ID of the NAT gateway" + value = aws_nat_gateway.this.id +} diff --git a/AWS/ecs-fargate/modules/vpc/variables.tf b/AWS/ecs-fargate/modules/vpc/variables.tf new file mode 100644 index 0000000..6424ff9 --- /dev/null +++ b/AWS/ecs-fargate/modules/vpc/variables.tf @@ -0,0 +1,24 @@ +variable "name" { + description = "Name prefix for VPC and related resources" + type = string +} + +variable "cidr" { + description = "CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} + +variable "public_subnet_cidrs" { + type = list(string) +} + +variable "private_subnet_cidrs" { + type = list(string) +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = {} +} diff --git a/AWS/ecs-fargate/network.tf b/AWS/ecs-fargate/network.tf deleted file mode 100644 index 0b0bfac..0000000 --- a/AWS/ecs-fargate/network.tf +++ /dev/null @@ -1,111 +0,0 @@ -resource "aws_vpc" "dbeaver_net" { - cidr_block = var.vpc_cidr - instance_tenancy = "default" - enable_dns_hostnames = true - - tags = { - Env = var.deployment_id - Name = "DBeaverTeamEdition" - } -} - -data "aws_availability_zones" "available" { - state = "available" -} - -resource "aws_subnet" "public_subnets" { - count = length(var.public_subnet_cidrs) - vpc_id = aws_vpc.dbeaver_net.id - cidr_block = element(var.public_subnet_cidrs, count.index) - availability_zone = data.aws_availability_zones.available.names[count.index] - map_public_ip_on_launch = true - - tags = { - Env = var.deployment_id - Name = "DBeaverTE Public Subnet ${count.index + 1}" - } - - depends_on = [aws_vpc.dbeaver_net] -} - -resource "aws_subnet" "private_subnets" { - count = length(var.private_subnet_cidrs) - vpc_id = aws_vpc.dbeaver_net.id - cidr_block = element(var.private_subnet_cidrs, count.index) - availability_zone = data.aws_availability_zones.available.names[count.index] - - tags = { - Env = var.deployment_id - Name = "DBeaverTE Private Subnet ${count.index + 1}" - } - - depends_on = [aws_vpc.dbeaver_net] -} - -resource "aws_internet_gateway" "dbeaver_gw" { - vpc_id = aws_vpc.dbeaver_net.id - - tags = { - Env = var.deployment_id - Name = "DBeaverTE VPC IG" - } - depends_on = [aws_vpc.dbeaver_net] -} - -resource "aws_route" "dbeaver_vpc_main_gw" { - route_table_id = aws_vpc.dbeaver_net.main_route_table_id - - destination_cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.dbeaver_gw.id - depends_on = [ - aws_vpc.dbeaver_net, - aws_internet_gateway.dbeaver_gw - ] -} - - -resource "aws_eip" "dbeaver_nat_gateway" { - domain = "vpc" - tags = { - Env = var.deployment_id - Name = "DBeaverTE EIP for Private VPC " - } -} - -resource "aws_nat_gateway" "nat_gateway" { - allocation_id = aws_eip.dbeaver_nat_gateway.id - subnet_id = aws_subnet.public_subnets[0].id - tags = { - Env = var.deployment_id - Name = "DBeaverTE Private Subnets Nat Gateway" - } - - depends_on = [ - aws_vpc.dbeaver_net, - aws_subnet.public_subnets - ] -} - -resource "aws_route_table" "dbeaver_private_rt_nat" { - vpc_id = aws_vpc.dbeaver_net.id - - route { - cidr_block = "0.0.0.0/0" - gateway_id = aws_nat_gateway.nat_gateway.id - } - - tags = { - Env = var.deployment_id - Name = "DBeaver TE Private Route Table" - } - depends_on = [ - aws_vpc.dbeaver_net, - aws_eip.dbeaver_nat_gateway - ] -} - -resource "aws_route_table_association" "private_subnets_rt" { - count = length(var.private_subnet_cidrs) - subnet_id = aws_subnet.private_subnets[count.index].id - route_table_id = aws_route_table.dbeaver_private_rt_nat.id -} \ No newline at end of file diff --git a/AWS/ecs-fargate/outputs.tf b/AWS/ecs-fargate/outputs.tf new file mode 100644 index 0000000..88f05ab --- /dev/null +++ b/AWS/ecs-fargate/outputs.tf @@ -0,0 +1,4 @@ +output "alb_dns_name" { + description = "The DNS name of the load balancer" + value = module.alb.alb_dns_name +} diff --git a/AWS/ecs-fargate/providers.tf b/AWS/ecs-fargate/providers.tf new file mode 100644 index 0000000..0a87691 --- /dev/null +++ b/AWS/ecs-fargate/providers.tf @@ -0,0 +1,11 @@ +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Project = local.name_prefix_full + Deployment = var.deployment_id + ManagedBy = "terraform" + } + } +} diff --git a/AWS/ecs-fargate/rds.tf b/AWS/ecs-fargate/rds.tf index 4fbab4c..4b4184c 100644 --- a/AWS/ecs-fargate/rds.tf +++ b/AWS/ecs-fargate/rds.tf @@ -1,49 +1,30 @@ -variable "db_instance_class" { - description = "The instance type of the RDS instance" - default = "db.t3.micro" -} +################################################################################ +# RDS PostgreSQL (optional, enabled via var.rds_db) +################################################################################ -variable "db_allocated_storage" { - description = "The allocated storage in gigabytes" - default = 20 -} +module "rds" { + source = "./modules/rds" -resource "aws_db_subnet_group" "rds_dbeaver_db_subnet" { + count = var.rds_db ? 1 : 0 - depends_on = [ - aws_vpc.dbeaver_net, - aws_subnet.private_subnets - ] - count = var.rds_db ? 1 : 0 - name = "dbeaverte-${var.deployment_id}-rds_db_subnet" - subnet_ids = [aws_subnet.private_subnets[0].id, aws_subnet.private_subnets[1].id] + identifier = lower("${local.name_prefix}-${var.deployment_id}") + subnet_group_name = lower("${local.name_prefix}-${var.deployment_id}-rds_db_subnet") + subnet_ids = local.private_subnets - tags = { - Env = var.deployment_id - Name = "DBeaver Team Edition Database subnet" - } -} + engine = var.rds_db_type + engine_version = var.rds_db_version + instance_class = var.db_instance_class + + allocated_storage = var.db_allocated_storage + storage_type = "gp2" + + db_name = var.cloudbeaver-db-env[2].value + username = var.cloudbeaver-db-env[1].value + password = var.cloudbeaver-db-env[0].value -# For oracle db class db.m5.large && POSTGRES_DB < 8 charters -resource "aws_db_instance" "rds_dbeaver_db" { - - depends_on = [ - aws_vpc.dbeaver_net, - aws_subnet.private_subnets - ] - - count = var.rds_db ? 1 : 0 - allocated_storage = var.db_allocated_storage - storage_type = "gp2" - engine = var.rds_db_type - engine_version = var.rds_db_version - instance_class = var.db_instance_class - db_name = var.cloudbeaver-db-env[2].value - username = var.cloudbeaver-db-env[1].value - password = var.cloudbeaver-db-env[0].value - db_subnet_group_name = aws_db_subnet_group.rds_dbeaver_db_subnet[0].name vpc_security_group_ids = [aws_security_group.dbeaver_te_private.id] - skip_final_snapshot = true + + skip_final_snapshot = true tags = { Env = var.deployment_id diff --git a/AWS/ecs-fargate/security-groups.tf b/AWS/ecs-fargate/security-groups.tf index 3984a93..ccc952b 100644 --- a/AWS/ecs-fargate/security-groups.tf +++ b/AWS/ecs-fargate/security-groups.tf @@ -1,99 +1,123 @@ +################################################################################ +# Security Groups +################################################################################ + resource "aws_security_group" "dbeaver_alb" { - name = "DBeaverTE-${var.deployment_id}-sg-alb" - vpc_id = aws_vpc.dbeaver_net.id + name = "${local.name_prefix}-${var.deployment_id}-sg-alb" + vpc_id = local.vpc_id description = "DBeaverTE ${var.deployment_id} EKS Default SG" ingress { - protocol = "tcp" - from_port = 80 - to_port = 80 - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + protocol = "tcp" + from_port = 80 + to_port = 80 + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + description = "HTTP" } ingress { - protocol = "tcp" - from_port = 443 - to_port = 443 - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + protocol = "tcp" + from_port = 443 + to_port = 443 + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + description = "HTTPS" } egress { - protocol = "-1" - from_port = 0 - to_port = 0 - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Env = var.deployment_id } } resource "aws_security_group" "dbeaver_efs" { - name = "DBeaverTE-${var.deployment_id}-ecs-efs-sg" - vpc_id = aws_vpc.dbeaver_net.id + name = "${local.name_prefix}-${var.deployment_id}-ecs-efs-sg" + vpc_id = local.vpc_id description = "DBeaverTE ${var.deployment_id} efs SG" ingress { - protocol = "tcp" - from_port = 2049 - to_port = 2049 - cidr_blocks = var.private_subnet_cidrs - description = "Allow NFS traffic - TCP 2049" + protocol = "tcp" + from_port = 2049 + to_port = 2049 + cidr_blocks = var.private_subnet_cidrs + description = "Allow NFS traffic - TCP 2049" } egress { - protocol = "-1" - from_port = 0 - to_port = 0 - cidr_blocks = ["0.0.0.0/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Env = var.deployment_id } } resource "aws_security_group" "dbeaver_te_private" { - name = "DBeaverTE-${var.deployment_id}-ecs-service-postgres" - vpc_id = aws_vpc.dbeaver_net.id + name = "${local.name_prefix}-${var.deployment_id}-ecs-service-postgres" + vpc_id = local.vpc_id description = "DBeaverTE ${var.deployment_id} ECS Postgres SG" ingress { - protocol = "tcp" - from_port = 5432 - to_port = 5432 + protocol = "tcp" + from_port = 5432 + to_port = 5432 cidr_blocks = var.private_subnet_cidrs + description = "PostgreSQL" } - ingress { - protocol = "tcp" - from_port = 9092 - to_port = 9093 + ingress { + protocol = "tcp" + from_port = 9092 + to_port = 9093 cidr_blocks = var.private_subnet_cidrs + description = "Kafka" } egress { - protocol = "-1" - from_port = 0 - to_port = 0 - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Env = var.deployment_id } } resource "aws_security_group" "dbeaver_te" { - name = "DBeaverTE-${var.deployment_id}-ecs-service-dbeaver-te" - vpc_id = aws_vpc.dbeaver_net.id + name = "${local.name_prefix}-${var.deployment_id}-ecs-service-dbeaver-te" + vpc_id = local.vpc_id description = "DBeaverTE ${var.deployment_id} ECS DBeaverTE SG" ingress { - protocol = "tcp" - from_port = 8970 - to_port = 8980 - cidr_blocks = concat(var.private_subnet_cidrs, var.public_subnet_cidrs) + protocol = "tcp" + from_port = 8970 + to_port = 8980 + cidr_blocks = concat(var.private_subnet_cidrs, var.public_subnet_cidrs) } egress { - protocol = "-1" - from_port = 0 - to_port = 0 - cidr_blocks = ["0.0.0.0/0"] - ipv6_cidr_blocks = ["::/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Env = var.deployment_id } -} \ No newline at end of file +} diff --git a/AWS/ecs-fargate/variables.tf.example b/AWS/ecs-fargate/variables.tf.example index 53ef239..51070a5 100644 --- a/AWS/ecs-fargate/variables.tf.example +++ b/AWS/ecs-fargate/variables.tf.example @@ -13,7 +13,7 @@ variable "aws_region" { variable "dbeaver_te_version" { description = "The version of the cluster you want to deploy" type = string - default = "26.0.0" + default = "26.1.0" } variable "image_source" { @@ -61,9 +61,14 @@ variable "rds_db_type" { variable "rds_db_version" { description = "Version of type RDS DB instance" type = string - default = "16.1" + default = "16.6" } +variable "db_image" { + description = "Image for the internal database container" + type = string + default = "dbeaver/cloudbeaver-postgres:16" +} variable "cloudbeaver-db-env" { description = "Parameters for your internal database" @@ -79,11 +84,54 @@ variable "cloudbeaver-db-env" { } +variable "db_instance_class" { + description = "The instance type of the RDS instance" + type = string + default = "db.t3.micro" +} + +variable "db_allocated_storage" { + description = "The allocated storage in gigabytes" + type = number + default = 20 +} + +# WARNING: changing this on an existing deployment forces recreation and destroys all EFS data +variable "efs_encrypted" { + description = "Enable encryption for EFS file systems" + type = bool + default = true +} + variable "dbeaver_te_default_ns" { type = string default = "dbeaver-te.local" } +variable "create_vpc" { + description = "Whether to create a new VPC or use an existing one" + type = bool + default = true +} + +variable "vpc_id" { + description = "Existing VPC ID (used when create_vpc = false)" + type = string + default = "" +} + +variable "public_subnet_ids" { + description = "Existing public subnet IDs (used when create_vpc = false)" + type = list(string) + default = [] +} + +variable "private_subnet_ids" { + description = "Existing private subnet IDs (used when create_vpc = false)" + type = list(string) + default = [] +} + variable "vpc_cidr" { type = string default = "10.0.0.0/16" diff --git a/AWS/ecs-fargate/versions.tf b/AWS/ecs-fargate/versions.tf index 2fac521..498fb88 100644 --- a/AWS/ecs-fargate/versions.tf +++ b/AWS/ecs-fargate/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 5.0" } } } \ No newline at end of file diff --git a/AWS/ecs-fargate/vpc.tf b/AWS/ecs-fargate/vpc.tf new file mode 100644 index 0000000..132e00a --- /dev/null +++ b/AWS/ecs-fargate/vpc.tf @@ -0,0 +1,18 @@ +################################################################################ +# VPC (optional, created when var.create_vpc = true) +################################################################################ + +module "vpc" { + source = "./modules/vpc" + + count = var.create_vpc ? 1 : 0 + + name = local.name_prefix_full + cidr = var.vpc_cidr + public_subnet_cidrs = var.public_subnet_cidrs + private_subnet_cidrs = var.private_subnet_cidrs + + tags = { + Env = var.deployment_id + } +} diff --git a/README.md b/README.md index 41bd785..b79f035 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ## DBeaver Team Edition -#### Version 26.0 +#### Version 26.1 DBeaver Team Edition is a client-server application. It requires server deployment. You can deploy it on a single host (e.g. your local computer) @@ -38,7 +38,7 @@ DBeaver Team Edition works in conjunction with a desktop client application. Aft - **Web interface** – accessible directly through your browser - **Desktop client** – provides enhanced features and better performance -Download the desktop client for your platform: [**DBeaver Team Edition Desktop**](https://dbeaver.com/downloads-team/26.0.0/) +Download the desktop client for your platform: [**DBeaver Team Edition Desktop**](https://dbeaver.com/downloads-team/26.1.0/) ### Server version update Version update is handled differently for different deployment methods. To update the Team Edition version, follow these instructions: @@ -60,6 +60,7 @@ To change an internal PostgreSQL password use [this instruction](CHANGEPWD.md#ho - [Early access](https://github.com/dbeaver/team-edition-deploy/tree/devel) ### Older versions: +- [26.0.0](https://github.com/dbeaver/team-edition-deploy/tree/26.0.0) - [25.3.0](https://github.com/dbeaver/team-edition-deploy/tree/25.3.0) - [25.2.0](https://github.com/dbeaver/team-edition-deploy/tree/25.2.0) - [25.1.0](https://github.com/dbeaver/team-edition-deploy/tree/25.1.0) diff --git a/compose/README.md b/compose/README.md index c8587eb..fef209f 100644 --- a/compose/README.md +++ b/compose/README.md @@ -76,6 +76,20 @@ Example for proxy configuration: JAVA_TOOL_OPTIONS="-Dhttp.proxy.host= -Dhttps.proxy.host= -Dhttp.proxy.port= -Dhttps.proxy.port=" ``` +### Proxy configuration in front of Team Edition + +If you place your own reverse proxy in front of Team Edition instead of the bundled `web-proxy`, it must forward the following headers so the backend can resolve the correct public origin. + +The backend looks for the public origin in this order (first non-empty wins): + +- `Origin` — public URL the client used (`scheme://host[:port]`). +- `X-Origin` — fallback when `Origin` is absent. +- `Referer` — last-resort fallback. +- `X-Forwarded-Scheme` — original scheme (`http` / `https`). +- `X-Forwarded-Proto` — fallback for `X-Forwarded-Scheme` (`http` / `https`). +- `X-Forwarded-Host` — original host. If both the scheme and host above are set, they override `Origin` / `X-Origin` / `Referer`. +- `X-Forwarded-Port` — original port (only needed if it differs from 80/443). + ## Configuring and starting Team Edition cluster 1. Clone Git repository to your local machine by running the following command in your terminal: @@ -220,7 +234,7 @@ For detailed instructions on how to use the script manager, refer to [manager do 2. Stop the cluster: `docker-compose down` or `docker compose down` 3. Update your deployment files: - Fetch latest changes: `git fetch` - - Switch to new release version: `git checkout ` (e.g., `git checkout 26.0.0`) + - Switch to new release version: `git checkout ` (e.g., `git checkout 26.1.0`) - Change value of `CLOUDBEAVER_VERSION_TAG` in `.env` with a preferred version (skip if tag `latest` is set) 4. Pull new docker images: `docker-compose pull` or `docker compose pull` 5. Start the cluster: `docker-compose up -d` or `docker compose up -d` diff --git a/compose/cbte/.env.example b/compose/cbte/.env.example index d567ab1..ce36bcb 100644 --- a/compose/cbte/.env.example +++ b/compose/cbte/.env.example @@ -1,6 +1,6 @@ # CloudBeaver TE server version. Based on DockerHub images tag # -CLOUDBEAVER_VERSION_TAG=26.0.0 +CLOUDBEAVER_VERSION_TAG=26.1.0 IMAGE_SOURCE=dbeaver PODMAN_IMAGE_SOURCE=docker.io/dbeaver # Domain name of cluster endpoint. eg. dbeaver-te.example.com diff --git a/k8s/cbte/Chart.yaml b/k8s/cbte/Chart.yaml index 10c07ec..7b2e39d 100644 --- a/k8s/cbte/Chart.yaml +++ b/k8s/cbte/Chart.yaml @@ -3,4 +3,4 @@ name: cbte description: A Helm chart for CloudBeaver TE application type: application version: 0.0.1 -appVersion: 26.0.0 +appVersion: 26.1.0 diff --git a/k8s/cbte/values.yaml.example b/k8s/cbte/values.yaml.example index 705f3d0..11f7a48 100644 --- a/k8s/cbte/values.yaml.example +++ b/k8s/cbte/values.yaml.example @@ -4,7 +4,7 @@ # cloudbeaver general values for all TE services cloudbeaver: - imageTag: "26.0.0" + imageTag: "26.1.0" pullPolicy: Always # pullCredsName - name of a secret config map that contains docker repo auths # pullCredsName: regcred