diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d3635cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +*.tfvars + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore ide configs +.idea +.vscode diff --git a/examples/load_balanced/main.tf b/examples/load_balanced/main.tf new file mode 100644 index 0000000..0c48eef --- /dev/null +++ b/examples/load_balanced/main.tf @@ -0,0 +1,71 @@ +provider "aws" { + region = var.region + profile = var.aws_profile +} + +module "vpc" { + source = "scalereal/vpc/aws" + version = "0.0.1" + availability_zones = var.vpc_availability_zones + cidr_block = var.vpc_cidr_block + database_subnets = var.vpc_database_subnets + env = var.env + private_subnets = var.vpc_private_subnets + public_subnets = var.vpc_public_subnets + service_name = var.service_name +} + +data "aws_iam_policy_document" "this" { + statement { + actions = ["sts:AssumeRole"] + principals { + identifiers = ["elasticbeanstalk.amazonaws.com"] + type = "Service" + } + effect = "Allow" + } +} + +resource "aws_iam_role" "this" { + name = "eb_appversion_deletion_role" + assume_role_policy = data.aws_iam_policy_document.this.json +} + +module "elastic_beanstalk_application" { + source = "scalereal/elastic-beanstalk-application/aws" + version = "0.0.1" + name = "Ruby App" + appversion_service_role_arn = aws_iam_role.this.arn + appversion_max_age_in_days = 90 +} + +//Error 1 +#Error: InvalidParameterValue: Health transition option settings require enhanced SystemType. +//status code: 400, request id: 1f25b699-a4f8-4269-b0b1-3654c25de702yes +// +//on ../../main.tf line 138, in resource "aws_elastic_beanstalk_environment" "env": +//138: resource "aws_elastic_beanstalk_environment" "env" { + + +//Error 2 +//Error: ConfigurationValidationException: Configuration validation exception: Invalid option value: 'null' (Namespace: 'aws:ec2:vpc', OptionName: 'Subnets'): Specify the subnets for the VPC. +//status code: 400, request id: c06491c6-35e0-4119-b702-c95edd1ea632 +// +//on ../../main.tf line 138, in resource "aws_elastic_beanstalk_environment" "env": +//138: resource "aws_elastic_beanstalk_environment" "env" { + + +module "elastic_beanstalk_environment" { + source = "../../" + eb_app_name = module.elastic_beanstalk_application.name + env = var.env + service_name = var.service_name + description = "My Ruby test Env" + solution_stack_name = var.eb_solution_stack_name + vpc_id = module.vpc.id + enable_enhanced_healthreporting = true + private_subnets = module.vpc.private_subnets_ids + public_subnets = module.vpc.public_subnet_ids + asg_max_count = "2" + asg_min_count = "1" +} diff --git a/examples/load_balanced/out.tf b/examples/load_balanced/out.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/load_balanced/vars.tf b/examples/load_balanced/vars.tf new file mode 100644 index 0000000..0dcf042 --- /dev/null +++ b/examples/load_balanced/vars.tf @@ -0,0 +1,22 @@ +variable "aws_profile" { + type = string + description = "AWS profile for running the TF scripts" +} + +variable "region" { + type = string + default = "ap-south-1" + description = "AWS region for running the TF scripts" +} + +variable "service_name" {} +variable "env" {} +variable "vpc_cidr_block" {} +variable "vpc_public_subnets" {} +variable "vpc_private_subnets" {} +variable "vpc_database_subnets" {} +variable "vpc_availability_zones" {} +variable eb_solution_stack_name { + type = string + default = "64bit Amazon Linux 2 v3.0.1 running Ruby 2.7" +} \ No newline at end of file diff --git a/examples/single_instance/main.tf b/examples/single_instance/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/single_instance/out.tf b/examples/single_instance/out.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/single_instance/vars.tf b/examples/single_instance/vars.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/worker_tier/main.tf b/examples/worker_tier/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/worker_tier/out.tf b/examples/worker_tier/out.tf new file mode 100644 index 0000000..e69de29 diff --git a/examples/worker_tier/vars.tf b/examples/worker_tier/vars.tf new file mode 100644 index 0000000..e69de29 diff --git a/iam.tf b/iam.tf new file mode 100644 index 0000000..5db7ae5 --- /dev/null +++ b/iam.tf @@ -0,0 +1,61 @@ +data "aws_iam_policy_document" "ec2_role" { + statement { + actions = ["sts:AssumeRole"] + principals { + identifiers = ["ec2.amazonaws.com"] + type = "Service" + } + effect = "Allow" + } +} + +resource "aws_iam_role" "ec2_role" { + name = "${var.service_name}-${var.env}-ec2-role" + assume_role_policy = data.aws_iam_policy_document.ec2_role.json +} + +resource "aws_iam_instance_profile" "ec2_iam_instance_profile" { + name = "${var.service_name}-${var.env}-iam-instance-profile" + role = aws_iam_role.ec2_role.name +} + +resource "aws_iam_role_policy_attachment" "policy_attachment_1" { + role = aws_iam_role.ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier" +} + +resource "aws_iam_role_policy_attachment" "policy_attachment_2" { + role = aws_iam_role.ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier" +} + +resource "aws_iam_role_policy_attachment" "policy_attachment_3" { + role = aws_iam_role.ec2_role.name + policy_arn = "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker" +} + +data "aws_iam_policy_document" "service_role" { + statement { + actions = ["sts:AssumeRole"] + principals { + identifiers = ["elasticbeanstalk.amazonaws.com"] + type = "Service" + } + effect = "Allow" + } +} + +resource "aws_iam_role" "service_role" { + name = "${var.service_name}-${var.env}-service-role" + assume_role_policy = data.aws_iam_policy_document.service_role.json +} + +resource "aws_iam_role_policy_attachment" "policy_attachment_4" { + role = aws_iam_role.service_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth" +} + +resource "aws_iam_role_policy_attachment" "service" { + role = aws_iam_role.service_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkService" +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..ed4ea75 --- /dev/null +++ b/main.tf @@ -0,0 +1,419 @@ +locals { + elb_settings = [ + { + namespace = "aws:elb:loadbalancer" + name = "CrossZone" + value = "true" + resource = "" + }, + { + namespace = "aws:ec2:vpc" + name = "ELBSubnets" + value = join(",", var.public_subnets) + resource = "" + }, + // { + // namespace = "aws:elb:loadbalancer" + // name = "SecurityGroups" + // value = join(",", var.loadbalancer_security_groups) + // }, + // { + // namespace = "aws:elb:loadbalancer" + // name = "ManagedSecurityGroup" + // value = var.loadbalancer_managed_security_group + // }, + { + namespace = "aws:elb:listener" + name = "ListenerProtocol" + value = "HTTP" + resource = "" + }, + { + namespace = "aws:elb:listener" + name = "InstancePort" + value = var.application_port + resource = "" + }, + { + namespace = "aws:ec2:vpc" + name = "ELBScheme" + value = var.elb_scheme + resource = "" + }, + { + namespace = "aws:elb:listener" + name = "ListenerEnabled" + value = "true" + resource = "" + }, + { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "HealthCheckPath" + value = var.healthcheck_url + resource = "" + }, + { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "Port" + value = var.application_port + resource = "" + }, + { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "Protocol" + value = "HTTP" + resource = "" + }, + { + namespace = "aws:elbv2:listener:default" + name = "ListenerEnabled" + value = true + resource = "" + }, + { + namespace = "aws:elasticbeanstalk:environment:process:default" + name = "StickinessEnabled" + value = "true" + resource = "" + } + ] + + ssl_settings = [ + { + namespace = "aws:elb:listener:443" + name = "ListenerProtocol" + value = "HTTPS" + resource = "" + }, + { + namespace = "aws:elb:listener:443" + name = "InstancePort" + value = "80" + resource = "" + }, + { + namespace = "aws:elb:listener:443" + name = "SSLCertificateArns" + value = var.aws_acm_certificate_arn + resource = "" + }, + { + namespace = "aws:elb:listener:443" + name = "ListenerEnabled" + value = "true" + resource = "" + }, + { + namespace = "aws:elbv2:listener:443" + name = "ListenerEnabled" + value = true + resource = "" + }, + { + namespace = "aws:elbv2:listener:443" + name = "Protocol" + value = "HTTPS" + resource = "" + }, + { + namespace = "aws:elbv2:listener:443" + name = "SSLCertificateArns" + value = var.aws_acm_certificate_arn + resource = "" + } + ] + + elb_settings_acc = var.enable_ssl && var.aws_acm_certificate_arn != "" ? concat(local.elb_settings, local.ssl_settings) : local.elb_settings + + elb_settings_to_use = var.tier == "Worker" ? [] : local.elb_settings_acc +} + + +resource "aws_security_group" "elastic_beanstalk_app_sg" { + vpc_id = var.vpc_id + name = "${var.service_name}-${var.env}-elastic-beanstalk-app-sg" + tags = var.tags +} + +resource "aws_elastic_beanstalk_environment" "env" { + name = "${var.service_name}-${var.env}-env" + description = var.description + application = var.eb_app_name + solution_stack_name = var.solution_stack_name + wait_for_ready_timeout = var.wait_for_ready_timeout + tags = var.tags + tier = var.tier + + #=============================================VPC========================================================= + + setting { + namespace = "aws:ec2:vpc" + name = "VPCId" + value = var.vpc_id + resource = "" + } + + setting { + namespace = "aws:ec2:vpc" + name = "Subnets" + value = join(",", var.environment_type != "LoadBalanced" ? var.public_subnets : var.private_subnets) + resource = "" + } + + setting { + namespace = "aws:ec2:vpc" + name = "AssociatePublicIpAddress" + value = var.environment_type != "LoadBalanced" ? true : false + resource = "" + } + + #=============================================Autoscaling================================================ + + + setting { + namespace = "aws:autoscaling:launchconfiguration" + name = "IamInstanceProfile" + value = aws_iam_instance_profile.ec2_iam_instance_profile.name + resource = "" + } + + setting { + namespace = "aws:autoscaling:launchconfiguration" + name = "SecurityGroups" + value = aws_security_group.elastic_beanstalk_app_sg.id + resource = "" + } + + setting { + name = "EC2KeyName" + namespace = "aws:autoscaling:launchconfiguration" + value = var.aws_key_pair_name + resource = "" + } + + setting { + namespace = "aws:autoscaling:launchconfiguration" + name = "InstanceType" + value = var.instance_type + resource = "" + } + + setting { + namespace = "aws:autoscaling:asg" + name = "MinSize" + value = var.asg_min_count + resource = "" + } + + setting { + namespace = "aws:autoscaling:asg" + name = "Availability Zones" + value = var.availability_zones + resource = "" + } + + setting { + namespace = "aws:autoscaling:asg" + name = "MaxSize" + value = var.asg_max_count + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "BreachDuration" + value = var.autoscale_breach_duration + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "LowerBreachScaleIncrement" + value = var.autoscale_lower_breach_scale_increment + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "LowerThreshold" + value = var.autoscale_lower_threshold + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "MeasureName" + value = var.autoscale_measure_name + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "Period" + value = var.autoscale_period + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "EvaluationPeriods" + value = var.evaluation_periods + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "Statistic" + value = var.statistic + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "Unit" + value = var.autoscale_unit + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "UpperBreachScaleIncrement" + value = var.autoscale_upper_breach_scale_increment + resource = "" + } + + setting { + namespace = "aws:autoscaling:trigger" + name = "UpperThreshold" + value = var.autoscale_upper_threshold + resource = "" + } + + #================================================eb environment========================================= + + + setting { + namespace = "aws:elasticbeanstalk:environment" + name = "LoadBalancerType" + value = var.load_balancer_type + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:environment" + name = "ServiceRole" + value = aws_iam_role.service_role.id + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:environment" + name = "EnvironmentType" + value = var.environment_type + resource = "" + } + + #==============================================elb====================================================== + + dynamic "setting" { + for_each = local.elb_settings_to_use + content { + namespace = setting.value["namespace"] + name = setting.value["name"] + value = setting.value["value"] + resource = "" + } + } + + #==============================================cloudwatch & health======================================= + + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "StreamLogs" + value = var.enable_logs_streaming + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs:health" + name = "HealthStreamingEnabled" + value = var.enable_health_logs_streaming + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:healthreporting:system" + name = "SystemType" + value = var.enable_enhanced_healthreporting ? "enhanced" : "basic" + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:healthreporting:system" + name = "ConfigDocument" + value = jsonencode({ "Rules" : { "Environment" : { "ELB" : { "ELBRequests4xx" : { "Enabled" : var.disable_ignore_4xx_errors } }, "Application" : { "ApplicationRequests4xx" : { "Enabled" : var.disable_ignore_4xx_errors } } } }, "Version" : 1 }) + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "DeleteOnTerminate" + value = var.enable_delete_logs_on_terminate + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs:health" + name = "DeleteOnTerminate" + value = var.enable_delete_health_logs_on_terminate + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs" + name = "RetentionInDays" + value = var.log_retention_in_days + resource = "" + } + + setting { + namespace = "aws:elasticbeanstalk:cloudwatch:logs:health" + name = "RetentionInDays" + value = var.health_log_retention_in_days + resource = "" + } + + #===============================================SNS====================================== + + setting { + namespace = "aws:elasticbeanstalk:sns:topics" + name = "Notification Endpoint" + value = var.notification_email + resource = "" + } + + #===============================================Extra=================================== + + dynamic "setting" { + for_each = var.extra_settings + content { + namespace = setting.value.namespace + name = setting.value.name + value = setting.value.value + resource = "" + } + } + + #==============================================environment_variables==================== + + dynamic "setting" { + for_each = var.environment_variables + content { + namespace = "aws:elasticbeanstalk:application:environment" + name = setting.key + value = setting.value + resource = "" + } + } +} diff --git a/out.tf b/out.tf new file mode 100644 index 0000000..d1a30a1 --- /dev/null +++ b/out.tf @@ -0,0 +1,7 @@ +output "beanstalk_env_url" { + value = aws_elastic_beanstalk_environment.env.cname +} + +output "elastic_load_balancers" { + value = aws_elastic_beanstalk_environment.env.load_balancers +} diff --git a/vars.tf b/vars.tf new file mode 100644 index 0000000..7d33341 --- /dev/null +++ b/vars.tf @@ -0,0 +1,222 @@ +variable "service_name" { + type = string + description = "" +} + +variable "description" { + type = string + description = "" +} + +variable "env" { + type = string + description = "" +} + +variable "vpc_id" { + type = string + description = "" +} + +variable "eb_app_name" { + type = string + description = "" +} + +variable "aws_acm_certificate_arn" { + description = "ACM Certificate ARN" + default = "" +} + +variable "public_subnets" { + type = list(string) + default = [] + description = "CIDRs for public subnets" +} + +variable "private_subnets" { + type = list(string) + default = [] + description = "CIDR for private subnet vpc" +} + +variable "aws_key_pair_name" { + type = string + default = "" + description = "SSH key for elastic beanstalk ec2 instances" +} + +variable "healthcheck_url" { + type = string + default = "/" + description = "" +} + +variable "tags" { + type = map(string) + default = {} + description = "Tags for EB" +} + +variable instance_type { + type = string + default = "t2.micro" + description = "" +} + +variable asg_max_count { + type = string + default = "1" + description = "" +} + +variable asg_min_count { + type = string + default = "1" + description = "" +} + +variable notification_email { + type = string + default = "" + description = "" +} + +variable "autoscale_breach_duration" { + default = "1" +} + +variable "autoscale_lower_breach_scale_increment" { + default = "-1" +} + +variable "autoscale_lower_threshold" { + default = "20" +} + +variable "autoscale_measure_name" { + default = "CPUUtilization" +} + +variable "autoscale_period" { + default = "1" +} + +variable "evaluation_periods" { + default = "1" +} + +variable "statistic" { + default = "Minimum" +} + +variable "autoscale_unit" { + default = "Percent" +} + +variable "autoscale_upper_breach_scale_increment" { + default = "1" +} + +variable "autoscale_upper_threshold" { + default = "80" +} + +variable "environment_variables" { + type = map(string) + default = {} +} + +variable "extra_settings" { + type = list(object({ + namespace = string + name = string + value = string + })) + default = [] +} + +variable elb_scheme { + type = string + default = "public" +} + +variable "application_port" { + default = "80" +} + +variable "enable_ssl" { + default = false +} + +variable "tier" { + default = "WebServer" +} + +variable solution_stack_name { + type = string +} + +variable "environment_type" { + default = "LoadBalanced" +} + +variable load_balancer_type { + type = string + default = "application" +} + +variable availability_zones { + type = string + default = "Any" +} + +variable wait_for_ready_timeout { + type = string + default = "20m" +} + +variable "disable_ignore_4xx_errors" { + type = bool + default = false +} + +variable "enable_enhanced_healthreporting" { + type = bool + default = false +} + +variable log_retention_in_days { + type = string + default = "3" +} + +variable health_log_retention_in_days { + type = string + default = "3" +} + +variable enable_logs_streaming { + type = bool + default = true +} + +variable enable_health_logs_streaming { + type = string + default = true +} + +variable enable_delete_logs_on_terminate { + type = string + default = true +} + +variable enable_delete_health_logs_on_terminate { + type = string + default = true +} + +variable instance_port { + type = string + default = "80" +} diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..0cef117 --- /dev/null +++ b/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = "~> 0.12" + + required_providers { + aws = "~> 2.0" + } +}