# terraform-elasticbeanstalk

## Overview

This module provides functionality to create Elastic Beanstalk resources. It is tailored towards web applications but can be used with any load-balanced environment.

This module follows a few conventions:

* Applications will be named `var.service_name` (e.g. ows-search), unless `var.use_existing_application` is set to `true` and `var.existing_application_name` is specified.
* Environments will be named `var.environment-var.service_name` (e.g. qa-ows-search)
* IAM roles will be named `var.environment-var.service_name-service-role` and `var.environment-var.service_name-instance-profile`
* User-provided IAM policies will be named `var.environment-var.service_name-service-policy` 
* `var.datadog_enabled` controls whether or not the SecretsManager policy allowing access to Datadog API keys is attached. Default is true.
* Security groups will be named as follows:
    - Instance security group: `var.environment-var.service_name-asg`
    - Load balancer security group: `var.environment-var.service_name-lb`
* The latest version of the single-container Docker solution stack will always be selected.
* Enhanced monitoring is always enabled
* Log streaming is enabled by default (can be overridden)
* Rolling updates are always enabled
* Managed updates are always enabled

## Workflow

### Variables

There are several variables that must be provided for the module to function properly:
```
* environment
* environment_variables
* service_name
```

Similarly, there are a number of variables for which you should likely provide values specific to the AWS account and region:
```
* asg_additional_inbound_rules
* elb_v2_access_logs_s3_bucket
* load_balancer_ssl_certificate_arn
* vpc_elb_subnets
* vpc_subnets
* vpc_id
```

Finally, there are variables that may vary between environments (e.g. qa vs prod):
```
* launchconfiguration_instance_type
* launchconfiguration_keypair
* launchconfiguration_root_volume_size
* notification_endpoints
* update_min_in_service
```
Here is a sample main.tf:

```
variable env {}

# Elastic Beanstalk environment variables
variable LOGGER_DSN {}
variable SENTRY_DSN {}

provider "aws" {
  region = "us-east-1"
}

module "ows_sample_service_owsrequest" {
  source = "git@github.com:theorchard/terraform-owsrequest.git//"

  environment_name = "${var.env}"
  service_name     = "ows-sample-service"
}

module "ows_sample_service_environment" {
  source = "git@github.com:theorchard/terraform-elasticbeanstalk.git//"

  environment                        = "${var.env}"
  service_name                       = "ows-sample-service"
  update_max_batch                   = "1"
  vpc_elb_subnets                    = "subnet-44c19a21,subnet-b649dfef"
  vpc_subnets                        = "subnet-44c19a21,subnet-b649dfef"
  vpc_id                             = "vpc-34dbfd51"
  iam_managed_policy_attachments     = [
    "${module.ows_sample_service_owsrequest.policy_arn_output}"
  ]
  launchconfiguration_instance_type  = "t2.medium"
  launchconfiguration_keypair        = "test_ssh_key"
  load_balancer_listener_port        = "443"
  load_balancer_listener_protocol    = "HTTPS"
  load_balancer_ssl_certificate_arn  = "arn:aws:acm:us-east-1:1234567890:certificate/abcd1234-abc-1234-abcd-abcd1234"
  notification_endpoints             = "systems@theorchard.com"
  process_port                       = "80"
  process_protocol                   = "HTTP"
  process_instance_health_check_path = "/hello/"

  environment_variables = [
    {
      namespace = "aws:elasticbeanstalk:application:environment"
      name = "Environment"
      value = "${var.env}"
    },
    {
      namespace = "aws:elasticbeanstalk:application:environment"
      name = "LOGGER_DSN"
      value = "${var.LOGGER_DSN}"
    },
    {
      namespace = "aws:elasticbeanstalk:application:environment"
      name = "SENTRY_DSN"
      value = "${var.SENTRY_DSN}"
    }
  ]
}
```

Provide environment variables by specifying `environment_variables` (as a list of maps), which should not contain sensitive credentials in plaintext. Instead, use the approach detailed above, whereby local variables are defined, the environment variables of the environment are set to the value of said local variables, and the values for local variables are provided in a terraform.tfvars file in the same directory (reference: https://www.terraform.io/intro/getting-started/variables.html#from-a-file)

### Resources

#### The module manages the following: 

* Elastic Beanstalk applications
* Elastic Beanstalk environments
* Elastic Beanstalk IAM roles
* Elastic Beanstalk IAM secrets manager policies
* Elastic Beanstalk load balancers + listeners
* Elastic Beanstalk security groups

#### The module does not manage the following: 

* Deployments
* Subnets

This module assumes that subnets already exist and can be specified in variables.

## Notes
### owsrequest modules 

In the example above, an output named `policy_arn_output` from the owsrequest module is used as an input variable called `iam_managed_policy_attachments` in the elastic beanstalk module. Because `iam_managed_policy_attachments` is a list of ARNs of variable length, the elastic beanstalk module uses the count of list elements in `iam_managed_policy_attachments` to programmatically create the correct number of policy attachments; however, the module cannot compute the count of `iam_managed_policy_attachments` in this case until the owsrequest module has run and produced an output; this is a bug/limitation in Terraform itself (https://github.com/hashicorp/terraform/issues/12570). Simply running a `terraform plan` or `terraform apply` with fail with an error like this: 

`...value of 'count' cannot be computed`

As such, the workaround is to first plan/apply the owsrequest module via the `target` option, and then apply the remaining code. That looks like this: 

`terraform apply -target module.ows_sample_service_owsrequest` (this will work)

`terraform apply` (this will now work and create everything else)

### Application Imports
When applying Terraform for a second environment (typically prod , following qa) in a single application, Terraform will fail trying to create the Elastic Beanstalk application, since it already exists. In order to avoid this problem, import the existing application into the Terraform state of the new environment before applying Terraform by running the following:

`terraform import module.whatever_you_named_your_module.aws_elastic_beanstalk_application.elasticbeanstalk_application service_name`

Example:

`terraform import module.bff_mobile_environment.aws_elastic_beanstalk_application.elasticbeanstalk_application bff-mobile`

### Using Existing Applications
If you intend to use an existing application with a name other than the value of `var.service_name` for your new environment (e.g. swf-feed-ingestion for a new environment called swf-my-analytics), you should set `var.use_existing_application` to `true` and specify `var.existing_application_name`, in this case setting it to swf-feed-ingestion. You will then need to import the existing application into the Terraform state as described above.
