terra_boi 0.0.11 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +84 -159
- data/lib/generators/extensions.rb +5 -5
- data/lib/generators/terra_boi/boilerplate_generator.rb +39 -32
- data/lib/generators/terra_boi/dockerfile_generator.rb +19 -18
- data/lib/generators/terra_boi/host_initializer_generator.rb +18 -18
- data/lib/generators/terra_boi/templates/Dockerfile.erb +4 -23
- data/lib/generators/terra_boi/templates/cert/main.tf.erb +32 -0
- data/lib/generators/terra_boi/templates/cert/var.tf.erb +15 -0
- data/lib/generators/terra_boi/templates/data_storage_config.erb +7 -7
- data/lib/generators/terra_boi/templates/ecr/ecs_role.tf.erb +41 -0
- data/lib/generators/terra_boi/templates/ecr/main.tf.erb +26 -0
- data/lib/generators/terra_boi/templates/ecr/output.tf.erb +11 -0
- data/lib/generators/terra_boi/templates/ecr/var.tf.erb +15 -0
- data/lib/generators/terra_boi/templates/{data_main.erb → env/data/main.tf.erb} +2 -2
- data/lib/generators/terra_boi/templates/{data_output.erb → env/data/output.tf.erb} +1 -1
- data/lib/generators/terra_boi/templates/env/ecs_cluster/ecs_cluster.tf.erb +24 -0
- data/lib/generators/terra_boi/templates/env/head_worker/ecs.tf.erb +55 -0
- data/lib/generators/terra_boi/templates/env/web_app/ecs.tf.erb +59 -0
- data/lib/generators/terra_boi/templates/lib/scripts/push_to_ecr.sh.erb +21 -0
- data/lib/generators/terra_boi/templates/lib/scripts/update_service_pull_from_ecr.sh.erb +18 -0
- data/lib/generators/terra_boi/templates/lib/task_templates/head_worker.json.erb +61 -0
- data/lib/generators/terra_boi/templates/lib/task_templates/web_app.json.erb +58 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_cluster/main.tf.erb +12 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_cluster/var.tf.erb +20 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_web_app/ecs_role.tf.erb +7 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_web_app/load_balancer.tf.erb +92 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_web_app/main.tf.erb +134 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_web_app/output.tf.erb +11 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_web_app/var.tf.erb +63 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_worker/ecs_role.tf.erb +7 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_worker/main.tf.erb +120 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_worker/output.tf.erb +7 -0
- data/lib/generators/terra_boi/templates/lib/terraform_modules/ecs_worker/var.tf.erb +57 -0
- data/lib/generators/terra_boi/templates/state_main.erb +1 -1
- data/lib/generators/terra_boi/tf_cert_generator.rb +28 -0
- data/lib/generators/terra_boi/tf_ecr_generator.rb +28 -0
- data/lib/generators/terra_boi/tf_env_generator.rb +54 -0
- data/lib/generators/terra_boi/tf_lib_generator.rb +57 -0
- data/lib/generators/terra_boi/tf_state_generator.rb +24 -0
- data/lib/tasks/terra_boi_tasks.rake +280 -4
- data/lib/terra_boi/railtie.rb +5 -2
- data/lib/terra_boi/version.rb +1 -1
- metadata +51 -22
- data/lib/generators/terra_boi/data_generator.rb +0 -38
- data/lib/generators/terra_boi/master_worker_generator.rb +0 -47
- data/lib/generators/terra_boi/packer_generator.rb +0 -26
- data/lib/generators/terra_boi/state_generator.rb +0 -25
- data/lib/generators/terra_boi/templates/master_worker_main.erb +0 -26
- data/lib/generators/terra_boi/templates/master_worker_output.erb +0 -14
- data/lib/generators/terra_boi/templates/master_worker_user_data.erb +0 -33
- data/lib/generators/terra_boi/templates/packer_ami_build.erb +0 -27
- data/lib/generators/terra_boi/templates/packer_application.erb +0 -49
- data/lib/generators/terra_boi/templates/web_servers_main.erb +0 -31
- data/lib/generators/terra_boi/templates/web_servers_output.erb +0 -14
- data/lib/generators/terra_boi/templates/web_servers_user_data.erb +0 -29
- data/lib/generators/terra_boi/web_servers_generator.rb +0 -48
@@ -0,0 +1,7 @@
|
|
1
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
2
|
+
# 1. ECS IAM ROLE (Created in web_app)
|
3
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
4
|
+
|
5
|
+
data "aws_iam_role" "ecs_execution_role" {
|
6
|
+
name = "ecs_${var.app_name}_execution_role"
|
7
|
+
}
|
@@ -0,0 +1,120 @@
|
|
1
|
+
provider "aws" {
|
2
|
+
version = "~> 2.0"
|
3
|
+
region = var.region
|
4
|
+
}
|
5
|
+
|
6
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
7
|
+
# 1. ECS TASK
|
8
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
9
|
+
|
10
|
+
data "aws_ecr_repository" "ecr_repo" {
|
11
|
+
name = "${var.app_name}-ecr-repo"
|
12
|
+
}
|
13
|
+
|
14
|
+
data "aws_ecs_cluster" "ecs_cluster" {
|
15
|
+
cluster_name = "${var.app_name}-${var.environment}"
|
16
|
+
}
|
17
|
+
|
18
|
+
data "terraform_remote_state" "db" {
|
19
|
+
backend = "s3"
|
20
|
+
|
21
|
+
config = {
|
22
|
+
bucket = "${var.app_name}-terraform-state-storage"
|
23
|
+
key = "terraform/${var.environment}-state/terra-boi-data"
|
24
|
+
region = "us-east-2"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
data "template_file" "task" {
|
29
|
+
template = "${file("../../lib/task_templates/${var.template_filename}")}"
|
30
|
+
|
31
|
+
vars = {
|
32
|
+
image = data.aws_ecr_repository.ecr_repo.repository_url
|
33
|
+
app_name = var.app_name
|
34
|
+
app_type = var.app_type
|
35
|
+
environment = var.environment
|
36
|
+
region = var.region
|
37
|
+
memory = var.worker_task.memory
|
38
|
+
cpu = var.worker_task.cpu
|
39
|
+
db_host = data.terraform_remote_state.db.outputs.address
|
40
|
+
db_username = data.terraform_remote_state.db.outputs.db_username
|
41
|
+
db_password = var.db_password
|
42
|
+
aws_access_key = var.aws_access_key
|
43
|
+
aws_secret_key = var.aws_secret_key
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
resource "aws_ecs_task_definition" "web_app" {
|
48
|
+
family = var.app_type # Naming our first task
|
49
|
+
requires_compatibilities = ["FARGATE"] # Stating that we are using ECS Fargate
|
50
|
+
network_mode = "awsvpc" # Using awsvpc as our network mode as this is required for Fargate
|
51
|
+
memory = var.worker_task.memory # Specifying the memory our container requires
|
52
|
+
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html
|
53
|
+
cpu = var.worker_task.cpu # Specifying the CPU our container requires
|
54
|
+
task_role_arn = data.aws_iam_role.ecs_execution_role.arn
|
55
|
+
execution_role_arn = data.aws_iam_role.ecs_execution_role.arn
|
56
|
+
container_definitions = data.template_file.task.rendered
|
57
|
+
}
|
58
|
+
|
59
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
60
|
+
# 2. ECS SERVICE
|
61
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
62
|
+
|
63
|
+
resource "aws_ecs_service" "app" {
|
64
|
+
name = var.app_type # Naming our first service
|
65
|
+
cluster = data.aws_ecs_cluster.ecs_cluster.id # Referencing our created Cluster
|
66
|
+
task_definition = aws_ecs_task_definition.web_app.arn # Referencing the task our service will spin up
|
67
|
+
launch_type = "FARGATE"
|
68
|
+
desired_count = var.worker_task.desired_count
|
69
|
+
|
70
|
+
network_configuration {
|
71
|
+
subnets = [aws_default_subnet.default_subnet_a.id, aws_default_subnet.default_subnet_b.id, aws_default_subnet.default_subnet_c.id]
|
72
|
+
assign_public_ip = true
|
73
|
+
security_groups = [aws_security_group.service_security_group.id]
|
74
|
+
}
|
75
|
+
|
76
|
+
depends_on = [aws_cloudwatch_log_group.logs]
|
77
|
+
}
|
78
|
+
|
79
|
+
resource "aws_security_group" "service_security_group" {
|
80
|
+
egress {
|
81
|
+
from_port = 0
|
82
|
+
to_port = 0
|
83
|
+
protocol = "-1"
|
84
|
+
cidr_blocks = ["0.0.0.0/0"]
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
89
|
+
# 3. REFERENCE NETWORK RESOURCES (to default VPC)
|
90
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
91
|
+
|
92
|
+
# # Providing a reference to our default VPC
|
93
|
+
resource "aws_default_vpc" "default_vpc" {
|
94
|
+
}
|
95
|
+
|
96
|
+
# Providing a reference to our default subnets
|
97
|
+
resource "aws_default_subnet" "default_subnet_a" {
|
98
|
+
availability_zone = "${var.region}a"
|
99
|
+
}
|
100
|
+
|
101
|
+
resource "aws_default_subnet" "default_subnet_b" {
|
102
|
+
availability_zone = "${var.region}b"
|
103
|
+
}
|
104
|
+
|
105
|
+
resource "aws_default_subnet" "default_subnet_c" {
|
106
|
+
availability_zone = "${var.region}c"
|
107
|
+
}
|
108
|
+
|
109
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
110
|
+
# 4. Logging
|
111
|
+
# ---------------------------------------------------------------------------------------------------------------------
|
112
|
+
|
113
|
+
resource "aws_cloudwatch_log_group" "logs" {
|
114
|
+
name = "/fargate/service/${var.app_name}-${var.environment}-${var.app_type}"
|
115
|
+
retention_in_days = 90
|
116
|
+
|
117
|
+
tags = {
|
118
|
+
app = var.app_name
|
119
|
+
}
|
120
|
+
}
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# -----------------------------
|
2
|
+
# 1. GENERAL
|
3
|
+
# -----------------------------
|
4
|
+
|
5
|
+
# Optional
|
6
|
+
|
7
|
+
variable "region" {
|
8
|
+
type = string
|
9
|
+
default = "us-east-2"
|
10
|
+
}
|
11
|
+
|
12
|
+
variable "app_name" {
|
13
|
+
type = string
|
14
|
+
default = "<%= application_name %>"
|
15
|
+
}
|
16
|
+
|
17
|
+
variable "environment" {
|
18
|
+
type = string
|
19
|
+
default = "dev"
|
20
|
+
}
|
21
|
+
|
22
|
+
variable "app_type" {
|
23
|
+
type = string
|
24
|
+
default = "web_app"
|
25
|
+
}
|
26
|
+
|
27
|
+
variable "template_filename" {
|
28
|
+
type = string
|
29
|
+
default = "web_app.json"
|
30
|
+
}
|
31
|
+
|
32
|
+
variable "db_password" {
|
33
|
+
description = "The password for the database"
|
34
|
+
type = string
|
35
|
+
}
|
36
|
+
|
37
|
+
variable "aws_access_key" {
|
38
|
+
type = string
|
39
|
+
}
|
40
|
+
|
41
|
+
variable "aws_secret_key" {
|
42
|
+
type = string
|
43
|
+
}
|
44
|
+
|
45
|
+
# -----------------------------
|
46
|
+
# 2. WEB APP TASK
|
47
|
+
# -----------------------------
|
48
|
+
|
49
|
+
variable "worker_task" {
|
50
|
+
type = map
|
51
|
+
|
52
|
+
default = {
|
53
|
+
memory = 512
|
54
|
+
cpu = 256
|
55
|
+
desired_count = 2
|
56
|
+
}
|
57
|
+
}
|
@@ -3,7 +3,7 @@
|
|
3
3
|
# ---------------------------------------------------------------------------------------------------------------------
|
4
4
|
|
5
5
|
module "remote_state_locking" {
|
6
|
-
source = "github.com/charliereese/terraform_modules//state?ref=v0.0.
|
6
|
+
source = "github.com/charliereese/terraform_modules//state?ref=v0.0.25"
|
7
7
|
|
8
8
|
app_name = "<%= application_name %>"
|
9
9
|
region = "us-east-2"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "generators/extensions"
|
2
|
+
|
3
|
+
module TerraBoi
|
4
|
+
class TfCertGenerator < Rails::Generators::Base
|
5
|
+
attr_accessor :application_name, :class_options
|
6
|
+
class_option :domain_name, type: :string, default: 'example.com', aliases: ["d"]
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
|
9
|
+
desc (<<-EOF
|
10
|
+
Generate HTTPS cert for domain
|
11
|
+
|
12
|
+
To execute, run rails generate terra_boi:tf_cert
|
13
|
+
EOF
|
14
|
+
.gsub(/\t/, '')
|
15
|
+
)
|
16
|
+
|
17
|
+
def init
|
18
|
+
# defined in lib/generators/extensions
|
19
|
+
self.application_name = generate_application_name
|
20
|
+
self.class_options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_cert
|
24
|
+
template "cert/main.tf.erb", "terraform/cert/main.tf"
|
25
|
+
template "cert/var.tf.erb", "terraform/cert/var.tf"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "generators/extensions"
|
2
|
+
|
3
|
+
module TerraBoi
|
4
|
+
class TfEcrGenerator < Rails::Generators::Base
|
5
|
+
attr_accessor :application_name
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
|
8
|
+
desc (<<-EOF
|
9
|
+
Generate AWS Elastic Container Registry (for storing container images)
|
10
|
+
|
11
|
+
To execute, run rails generate terra_boi:tf_ecr
|
12
|
+
EOF
|
13
|
+
.gsub(/\t/, '')
|
14
|
+
)
|
15
|
+
|
16
|
+
def init
|
17
|
+
# defined in lib/generators/extensions
|
18
|
+
self.application_name = generate_application_name
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_ecr
|
22
|
+
template "ecr/ecs_role.tf.erb", "terraform/ecr/ecs_role.tf"
|
23
|
+
template "ecr/main.tf.erb", "terraform/ecr/main.tf"
|
24
|
+
template "ecr/output.tf.erb", "terraform/ecr/output.tf"
|
25
|
+
template "ecr/var.tf.erb", "terraform/ecr/var.tf"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "generators/extensions"
|
2
|
+
|
3
|
+
module TerraBoi
|
4
|
+
class TfEnvGenerator < Rails::Generators::Base
|
5
|
+
attr_accessor :application_name, :class_options
|
6
|
+
class_option :envs, type: :array, default: ['staging', 'prod'], aliases: ["e"]
|
7
|
+
class_option :domain_name, type: :string, default: 'example.com', aliases: ["d"]
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
TEMPLATES = {
|
11
|
+
ecs_cluster: [
|
12
|
+
"ecs_cluster.tf",
|
13
|
+
],
|
14
|
+
head_worker: [
|
15
|
+
"ecs.tf",
|
16
|
+
],
|
17
|
+
web_app: [
|
18
|
+
"ecs.tf",
|
19
|
+
],
|
20
|
+
data: [
|
21
|
+
"main.tf",
|
22
|
+
"output.tf",
|
23
|
+
]
|
24
|
+
}
|
25
|
+
|
26
|
+
desc (<<-EOF
|
27
|
+
Generate ecs directory (with cluster, web app service and head worker service) for each env
|
28
|
+
|
29
|
+
To execute, run rails generate terra_boi:tf_ecs
|
30
|
+
|
31
|
+
Note: use -e or --env flag to specify list of infrastructure environments. Defaults to staging and prod
|
32
|
+
EOF
|
33
|
+
.gsub(/\t/, '')
|
34
|
+
)
|
35
|
+
|
36
|
+
def init
|
37
|
+
# defined in lib/generators/extensions
|
38
|
+
self.application_name = generate_application_name
|
39
|
+
self.class_options = options
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_ecs
|
43
|
+
class_options[:envs].each do |env|
|
44
|
+
TEMPLATES.each do |dir, file_arr|
|
45
|
+
file_arr.each do |filename|
|
46
|
+
template "env/#{dir}/#{filename}.erb", "terraform/#{env}/#{dir}/#{filename}", {
|
47
|
+
env: env,
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "generators/extensions"
|
2
|
+
|
3
|
+
module TerraBoi
|
4
|
+
class TfLibGenerator < Rails::Generators::Base
|
5
|
+
attr_accessor :application_name, :class_options
|
6
|
+
class_option :domain_name, type: :string, default: 'example.com', aliases: ["d"]
|
7
|
+
source_root File.expand_path('templates', __dir__)
|
8
|
+
|
9
|
+
TEMPLATES = {
|
10
|
+
scripts: [
|
11
|
+
"push_to_ecr.sh",
|
12
|
+
"update_service_pull_from_ecr.sh",
|
13
|
+
],
|
14
|
+
task_templates: [
|
15
|
+
"web_app.json",
|
16
|
+
"head_worker.json",
|
17
|
+
],
|
18
|
+
terraform_modules: [
|
19
|
+
"ecs_cluster/main.tf",
|
20
|
+
"ecs_cluster/var.tf",
|
21
|
+
|
22
|
+
"ecs_web_app/ecs_role.tf",
|
23
|
+
"ecs_web_app/load_balancer.tf",
|
24
|
+
"ecs_web_app/main.tf",
|
25
|
+
"ecs_web_app/output.tf",
|
26
|
+
"ecs_web_app/var.tf",
|
27
|
+
|
28
|
+
"ecs_worker/ecs_role.tf",
|
29
|
+
"ecs_worker/main.tf",
|
30
|
+
"ecs_worker/output.tf",
|
31
|
+
"ecs_worker/var.tf",
|
32
|
+
]
|
33
|
+
}
|
34
|
+
|
35
|
+
desc (<<-EOF
|
36
|
+
Generate lib directory with templates, scripts, and modules for deploying application containers to AWS ECS
|
37
|
+
|
38
|
+
To execute, run rails generate terra_boi:tf_lib
|
39
|
+
EOF
|
40
|
+
.gsub(/\t/, '')
|
41
|
+
)
|
42
|
+
|
43
|
+
def init
|
44
|
+
# defined in lib/generators/extensions
|
45
|
+
self.application_name = generate_application_name
|
46
|
+
self.class_options = options
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_ecr
|
50
|
+
TEMPLATES.each do |dir, file_arr|
|
51
|
+
file_arr.each do |filename|
|
52
|
+
template "lib/#{dir}/#{filename}.erb", "terraform/lib/#{dir}/#{filename}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "generators/extensions"
|
2
|
+
|
3
|
+
module TerraBoi
|
4
|
+
class TfStateGenerator < Rails::Generators::Base
|
5
|
+
attr_accessor :application_name
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
7
|
+
|
8
|
+
desc (<<-EOF
|
9
|
+
Generate DB and S3 bucket for storing and locking terraform state
|
10
|
+
|
11
|
+
To execute, run rails generate terra_boi:tf_state
|
12
|
+
EOF
|
13
|
+
.gsub(/\t/, '')
|
14
|
+
)
|
15
|
+
|
16
|
+
def init
|
17
|
+
self.application_name = generate_application_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_main_terraform_file
|
21
|
+
template "state_main.erb", "terraform/state/main.tf"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,4 +1,280 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
namespace :terra_boi do
|
4
|
+
desc """
|
5
|
+
Generate terraform (AWS) infrastructure code for your rails application.
|
6
|
+
|
7
|
+
Creates infrastructure code for RDS DBs, ALBs, ECRs, ECS clusters, services,
|
8
|
+
and tasks, and various security groups and other resources for each
|
9
|
+
deployment environment (e.g. staging and prod)
|
10
|
+
|
11
|
+
Usage without ENVS arg (creates staging and prod envs by default):
|
12
|
+
rake \"terra_boi:generate_infra\"
|
13
|
+
|
14
|
+
Usage with ENVS arg:
|
15
|
+
rake \"terra_boi:generate_infra[ENV1 ENV2 ENVn]\"
|
16
|
+
e.g. rake \"terra_boi:generate_infra[dev staging prod]\"
|
17
|
+
|
18
|
+
Takes one optional arg:
|
19
|
+
arg 1: envs (default value: staging prod)
|
20
|
+
"""
|
21
|
+
task :generate_infra, [:envs] => [:environment] do |_, args|
|
22
|
+
ENVS = get_envs(args)
|
23
|
+
|
24
|
+
create_boilerplate_files
|
25
|
+
apply_terraform_state
|
26
|
+
apply_terraform_cert
|
27
|
+
apply_ecr
|
28
|
+
apply_data
|
29
|
+
push_container_to_ecr
|
30
|
+
apply_web_app_and_worker
|
31
|
+
puts_urls_for_alb
|
32
|
+
puts_how_to_connect_domain_and_load_balancer
|
33
|
+
puts_twitter_plug
|
34
|
+
end
|
35
|
+
|
36
|
+
desc """
|
37
|
+
Destroy terraform (AWS) infrastructure code for your rails application.
|
38
|
+
|
39
|
+
Usage without ENVS arg (creates staging and prod envs by default):
|
40
|
+
rake \"terra_boi:destroy_infra\"
|
41
|
+
|
42
|
+
Usage with ENVS arg:
|
43
|
+
rake \"terra_boi:destroy_infra[ENV1 ENV2 ENVn]\"
|
44
|
+
e.g. rake \"terra_boi:destroy_infra[dev staging prod]\"
|
45
|
+
|
46
|
+
Takes one optional arg:
|
47
|
+
arg 1: envs (default value: staging prod)
|
48
|
+
"""
|
49
|
+
task :destroy_infra, [:envs] => [:environment] do |_, args|
|
50
|
+
ENVS = get_envs(args)
|
51
|
+
|
52
|
+
ENVS.each do |env|
|
53
|
+
puts "\nDestroying application infrastructure for #{env}...\n".cyan.bold
|
54
|
+
|
55
|
+
directories = [:head_worker, :web_app, :ecs_cluster, :data]
|
56
|
+
directories.each do |dir_name|
|
57
|
+
sh "cd terraform/#{env}/#{dir_name} && terraform destroy"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
sh "cd terraform/ecr && terraform destroy"
|
62
|
+
sh "cd terraform/cert && terraform destroy"
|
63
|
+
sh "cd terraform/state && terraform destroy"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc """
|
68
|
+
Deploy your rails application.
|
69
|
+
|
70
|
+
By default, deploys to staging and prod.
|
71
|
+
|
72
|
+
Usage without ENVS arg (deploys to staging and prod envs by default):
|
73
|
+
rake \"terra_boi:deploy\"
|
74
|
+
|
75
|
+
Usage with ENVS arg:
|
76
|
+
rake \"terra_boi:deploy[ENV1 ENV2 ENVn]\"
|
77
|
+
e.g. rake \"terra_boi:deploy[staging prod]\"
|
78
|
+
|
79
|
+
Takes one arg:
|
80
|
+
arg 1: envs (default value: staging prod)
|
81
|
+
"""
|
82
|
+
task :deploy, [:envs] => [:environment] do |task, args|
|
83
|
+
ENVS = get_envs(args)
|
84
|
+
puts "\nDeploying rails application to #{ENVS.to_sentence} infrastructure\n".cyan.bold
|
85
|
+
|
86
|
+
conditional_push_container_to_ecr
|
87
|
+
|
88
|
+
ENVS.each do |env|
|
89
|
+
ecs_tasks = [:web_app, :head_worker]
|
90
|
+
ecs_tasks.each do |task_name|
|
91
|
+
puts "\nDeploying #{env} #{task_name} task\n".cyan.bold
|
92
|
+
sh "./terraform/lib/scripts/update_service_pull_from_ecr.sh #{env} #{task_name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# ---------------------------------
|
98
|
+
# Helper methods
|
99
|
+
# ---------------------------------
|
100
|
+
|
101
|
+
def get_envs(args)
|
102
|
+
if args[:envs]
|
103
|
+
args[:envs].split(' ')
|
104
|
+
else
|
105
|
+
[:staging, :prod]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_boilerplate_files
|
110
|
+
config = {}
|
111
|
+
|
112
|
+
puts "\nTERRA_BOI | Generating boilerplate infrastructure as code with terra_boi for your rails project...\n".cyan.bold
|
113
|
+
sleep 1
|
114
|
+
config[:ruby_version] = get_ruby_docker_base_image
|
115
|
+
sleep 1
|
116
|
+
config[:domain_name] = get_domain_name
|
117
|
+
sleep 1
|
118
|
+
|
119
|
+
puts "\nTERRA_BOI | Generating infrastructure code using the configuration you provided...\n".cyan.bold
|
120
|
+
sleep 1
|
121
|
+
|
122
|
+
sh "rails g terra_boi:boilerplate -d #{config[:domain_name]} -r #{config[:ruby_version]}"
|
123
|
+
|
124
|
+
sleep 1
|
125
|
+
puts "\nTERRA_BOI | Marking deployment scripts executable...\n".bold
|
126
|
+
sh "chmod +x ./terraform/lib/scripts/*"
|
127
|
+
end
|
128
|
+
|
129
|
+
def apply_terraform_state
|
130
|
+
puts "\nTERRA_BOI | Creating terraform state...\n".cyan.bold
|
131
|
+
sh "cd terraform/state && terraform init && terraform apply -input=false -auto-approve"
|
132
|
+
end
|
133
|
+
|
134
|
+
def apply_terraform_cert
|
135
|
+
puts "\nTERRA_BOI | Creating HTTPS certificate in AWS Certificate Manager...\n".cyan.bold
|
136
|
+
sh "cd terraform/cert && terraform init"
|
137
|
+
|
138
|
+
print_certificate_validation_instructions
|
139
|
+
sleep 2
|
140
|
+
sh "cd terraform/cert && terraform apply -input=false -auto-approve"
|
141
|
+
confirm_certificate_successfully_validated
|
142
|
+
end
|
143
|
+
|
144
|
+
def apply_data
|
145
|
+
ENVS.each do |env|
|
146
|
+
puts "\nTERRA_BOI | Creating RDS DB instance and S3 bucket for #{env}...\n".cyan.bold
|
147
|
+
sh "cd terraform/#{env}/data && terraform init && terraform apply -input=false -auto-approve"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def apply_ecr
|
152
|
+
puts "\nTERRA_BOI | Creating AWS ECR (Elastic Container Registry) for your application's docker images...\n".cyan.bold
|
153
|
+
sh "cd terraform/ecr && terraform init && terraform apply -input=false -auto-approve"
|
154
|
+
end
|
155
|
+
|
156
|
+
def push_container_to_ecr
|
157
|
+
puts "\nTERRA_BOI | Building application docker container then pushing to ECR...\n".cyan.bold
|
158
|
+
sh "./terraform/lib/scripts/push_to_ecr.sh" do |ok, res|
|
159
|
+
if !ok
|
160
|
+
puts "\nTERRA_BOI | Docker container build and push failed (status = #{res.exitstatus})".cyan.bold
|
161
|
+
puts "\nTERRA_BOI | Pruning docker (to create more memory) and retrying...\n".cyan.bold
|
162
|
+
sh "docker system prune -a && ./terraform/lib/scripts/push_to_ecr.sh"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def apply_web_app_and_worker
|
168
|
+
directories = [:ecs_cluster, :web_app, :head_worker]
|
169
|
+
ENVS.each do |env|
|
170
|
+
puts "\nTERRA_BOI | Building web app and worker ECS infrastructure for #{env}...\n".cyan.bold
|
171
|
+
directories.each do |dir_name|
|
172
|
+
sh "cd terraform/#{env}/#{dir_name} && terraform init && terraform apply -input=false -auto-approve"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def puts_urls_for_alb
|
178
|
+
ENVS.each do |env|
|
179
|
+
url = `cd terraform/#{env}/web_app && terraform output alb_dns`
|
180
|
+
puts "\nTERRA_BOI | Public application load balancer URL for #{env}:".cyan.bold
|
181
|
+
puts "#{env} alb_dns: #{url}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def puts_how_to_connect_domain_and_load_balancer
|
186
|
+
puts "\nTERRA_BOI | GIDDY UP! TERRA_BOI HAS FINISHED CREATING INFRASTRUCTURE FOR YOUR RAILS APPLICATION!".cyan.bold
|
187
|
+
|
188
|
+
puts "\nTERRA_BOI | To connect your domain name to your application's new AWS infrastructure:".red
|
189
|
+
puts "1) Go to your domain register (e.g. Namecheap)"
|
190
|
+
puts "2) Add the alb_dns output value (i.e. the public URL for your load balancer) to your domain's DNS records. alb_dns public URL values are output above"
|
191
|
+
puts """E.g. to redirect staging.YOUR_DOMAIN_NAME to the load balancer you just deployed, add the following record to your DNS records:
|
192
|
+
TYPE: ALIAS
|
193
|
+
HOST: staging
|
194
|
+
VALUE: alb_dns output value (something like app-staging-725123955.us-east-2.elb.amazonaws.com)"""
|
195
|
+
|
196
|
+
puts "\nNote: you can have multiple ALIAS records for different subdomains\n"
|
197
|
+
end
|
198
|
+
|
199
|
+
def puts_twitter_plug
|
200
|
+
puts "\nTERRA_BOI | Let me know what you think about terra_boi on Twitter @charlieinthe6!".cyan.bold
|
201
|
+
end
|
202
|
+
|
203
|
+
def conditional_push_container_to_ecr
|
204
|
+
print "TERRA_BOI | Question: ".red
|
205
|
+
puts "Build and push container updates to ECR first (y/n)?"
|
206
|
+
puts "Answer y if you haven't built and pushed most recent updates to ECR yet."
|
207
|
+
puts "...and answer y if you aren't sure."
|
208
|
+
print "==> ".red
|
209
|
+
answer = STDIN.gets.downcase.gsub(/[^yn]/, "")
|
210
|
+
|
211
|
+
push_container_to_ecr if answer == 'y'
|
212
|
+
end
|
213
|
+
|
214
|
+
# ---------------------------------
|
215
|
+
# Helper^2 methods
|
216
|
+
# ---------------------------------
|
217
|
+
|
218
|
+
def print_certificate_validation_instructions
|
219
|
+
puts "\nTERRA_BOI | Certificate validation instructions:".red
|
220
|
+
puts "1) Log into AWS Console and go to AWS Certificate Manager (default region is us-east-2)"
|
221
|
+
puts "2) Expand issued certificate for your domain, and find CNAME record for domain validation"
|
222
|
+
puts "3) Add CNAME record listed for your domain to your domain's DNS records (i.e. log into where you purchased your domain and add CNAME record to it)\n\n"
|
223
|
+
puts "NOTE: the Host field for your CNAME record will be something like _123f2cc99f15298ff717ac26dd6993. The Value field for your CNAME record will be something like _bf123a01234a134123412341324.dasfjkhasd.acm-validation.aws.\n\n".bold
|
224
|
+
end
|
225
|
+
|
226
|
+
def confirm_certificate_successfully_validated
|
227
|
+
answer = ''
|
228
|
+
until answer == 'y'
|
229
|
+
print "TERRA_BOI | Question 3: ".red
|
230
|
+
puts "Has your HTTPS / SSL certificate successfully validated in AWS Console - AWS Certificate Manager (y/n)?"
|
231
|
+
print "==> ".red
|
232
|
+
answer = STDIN.gets.downcase.gsub(/[^yn]/, "")
|
233
|
+
|
234
|
+
if answer == 'y'
|
235
|
+
next
|
236
|
+
else
|
237
|
+
print_certificate_validation_instructions
|
238
|
+
sleep 5
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def get_ruby_docker_base_image
|
244
|
+
print "TERRA_BOI | Question 1: ".red
|
245
|
+
puts "Do you want to use the default ruby Docker base image 2.7.1 (y/n)?"
|
246
|
+
print "==> ".red
|
247
|
+
answer = STDIN.gets.downcase.gsub(/[^yn]/, "")
|
248
|
+
|
249
|
+
if answer == 'y'
|
250
|
+
ruby_docker_base_image = "2.7.1"
|
251
|
+
else
|
252
|
+
ruby_docker_base_image = ""
|
253
|
+
print "\nTERRA_BOI | Question 1 follow-up: ".red
|
254
|
+
puts "Which ruby Docker base image would you like to use (https://hub.docker.com/_/ruby/)?"
|
255
|
+
puts "Recommended answer: 2.7.1"
|
256
|
+
|
257
|
+
until ruby_docker_base_image.length > 0
|
258
|
+
print "==> ".red
|
259
|
+
ruby_docker_base_image = STDIN.gets.gsub(/[^0-9\.]/, "")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
puts "\nGreat! Using #{ruby_docker_base_image} as the base Docker image for your infrastructure.".bold
|
264
|
+
return ruby_docker_base_image
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_domain_name
|
268
|
+
print "\nTERRA_BOI | Question 2: ".red
|
269
|
+
puts "What domain name will you be using for your project?"
|
270
|
+
puts "E.g. example.com (do not include a subdomain or 'www')"
|
271
|
+
|
272
|
+
domain_name = ""
|
273
|
+
until domain_name.length > 2
|
274
|
+
print "==> ".red
|
275
|
+
domain_name = STDIN.gets.chomp
|
276
|
+
end
|
277
|
+
|
278
|
+
puts "\nRad! Setting #{domain_name} as your domain_name.".bold
|
279
|
+
return domain_name
|
280
|
+
end
|