forked from pulumi/examples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an AWS EC2 Ruby on Rails example
This ports the official AWS CloudFormation Application Framework template for a Ruby on Rails server that uses EC2 and a MySQL local database. I've implemented some experimental support for AWS::CloudFormation::Init-like config-sets, that install files and packages, and run scripts and services, on the VM during startup.
- Loading branch information
Showing
13 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
name: rails | ||
runtime: nodejs | ||
description: A Ruby on Rails stack using a single EC2 instance with a local MySQL database for storage. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# AWS EC2 Ruby on Rails | ||
|
||
This is a conversion of the AWS CloudFormation Application Framework template for a basic Ruby on Rails server. | ||
It creates a single EC2 instance and uses a local MySQL database for storage. | ||
See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/sample-templates-appframeworks-us-west-2.html. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import * as aws from "@pulumi/aws"; | ||
|
||
const instanceTypeToArch: {[instanceType: string]: string} = { | ||
"t1.micro" : "PV64" , | ||
"t2.nano" : "HVM64", | ||
"t2.micro" : "HVM64", | ||
"t2.small" : "HVM64", | ||
"t2.medium" : "HVM64", | ||
"t2.large" : "HVM64", | ||
"m1.small" : "PV64" , | ||
"m1.medium" : "PV64" , | ||
"m1.large" : "PV64" , | ||
"m1.xlarge" : "PV64" , | ||
"m2.xlarge" : "PV64" , | ||
"m2.2xlarge" : "PV64" , | ||
"m2.4xlarge" : "PV64" , | ||
"m3.medium" : "HVM64", | ||
"m3.large" : "HVM64", | ||
"m3.xlarge" : "HVM64", | ||
"m3.2xlarge" : "HVM64", | ||
"m4.large" : "HVM64", | ||
"m4.xlarge" : "HVM64", | ||
"m4.2xlarge" : "HVM64", | ||
"m4.4xlarge" : "HVM64", | ||
"m4.10xlarge" : "HVM64", | ||
"c1.medium" : "PV64" , | ||
"c1.xlarge" : "PV64" , | ||
"c3.large" : "HVM64", | ||
"c3.xlarge" : "HVM64", | ||
"c3.2xlarge" : "HVM64", | ||
"c3.4xlarge" : "HVM64", | ||
"c3.8xlarge" : "HVM64", | ||
"c4.large" : "HVM64", | ||
"c4.xlarge" : "HVM64", | ||
"c4.2xlarge" : "HVM64", | ||
"c4.4xlarge" : "HVM64", | ||
"c4.8xlarge" : "HVM64", | ||
"g2.2xlarge" : "HVMG2", | ||
"g2.8xlarge" : "HVMG2", | ||
"r3.large" : "HVM64", | ||
"r3.xlarge" : "HVM64", | ||
"r3.2xlarge" : "HVM64", | ||
"r3.4xlarge" : "HVM64", | ||
"r3.8xlarge" : "HVM64", | ||
"i2.xlarge" : "HVM64", | ||
"i2.2xlarge" : "HVM64", | ||
"i2.4xlarge" : "HVM64", | ||
"i2.8xlarge" : "HVM64", | ||
"d2.xlarge" : "HVM64", | ||
"d2.2xlarge" : "HVM64", | ||
"d2.4xlarge" : "HVM64", | ||
"d2.8xlarge" : "HVM64", | ||
"hi1.4xlarge" : "HVM64", | ||
"hs1.8xlarge" : "HVM64", | ||
"cr1.8xlarge" : "HVM64", | ||
"cc2.8xlarge" : "HVM64", | ||
}; | ||
|
||
const regionArchToAmi: {[region: string]: {[arch: string]: string}} = { | ||
"us-east-1" : {"PV64" : "ami-2a69aa47", "HVM64" : "ami-97785bed", "HVMG2" : "ami-0a6e3770"}, | ||
"us-west-2" : {"PV64" : "ami-7f77b31f", "HVM64" : "ami-f2d3638a", "HVMG2" : "ami-ee15a196"}, | ||
"us-west-1" : {"PV64" : "ami-a2490dc2", "HVM64" : "ami-824c4ee2", "HVMG2" : "ami-0da4a46d"}, | ||
"eu-west-1" : {"PV64" : "ami-4cdd453f", "HVM64" : "ami-d834aba1", "HVMG2" : "ami-af8013d6"}, | ||
"eu-west-2" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-403e2524", "HVMG2" : "NOT_SUPPORTED"}, | ||
"eu-west-3" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-8ee056f3", "HVMG2" : "NOT_SUPPORTED"}, | ||
"eu-central-1" : {"PV64" : "ami-6527cf0a", "HVM64" : "ami-5652ce39", "HVMG2" : "ami-1d58ca72"}, | ||
"ap-northeast-1" : {"PV64" : "ami-3e42b65f", "HVM64" : "ami-ceafcba8", "HVMG2" : "ami-edfd658b"}, | ||
"ap-northeast-2" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-863090e8", "HVMG2" : "NOT_SUPPORTED"}, | ||
"ap-northeast-3" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-83444afe", "HVMG2" : "NOT_SUPPORTED"}, | ||
"ap-southeast-1" : {"PV64" : "ami-df9e4cbc", "HVM64" : "ami-68097514", "HVMG2" : "ami-c06013bc"}, | ||
"ap-southeast-2" : {"PV64" : "ami-63351d00", "HVM64" : "ami-942dd1f6", "HVMG2" : "ami-85ef12e7"}, | ||
"ap-south-1" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-531a4c3c", "HVMG2" : "ami-411e492e"}, | ||
"us-east-2" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-f63b1193", "HVMG2" : "NOT_SUPPORTED"}, | ||
"ca-central-1" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-a954d1cd", "HVMG2" : "NOT_SUPPORTED"}, | ||
"sa-east-1" : {"PV64" : "ami-1ad34676", "HVM64" : "ami-84175ae8", "HVMG2" : "NOT_SUPPORTED"}, | ||
"cn-north-1" : {"PV64" : "ami-77559f1a", "HVM64" : "ami-cb19c4a6", "HVMG2" : "NOT_SUPPORTED"}, | ||
"cn-northwest-1" : {"PV64" : "ami-80707be2", "HVM64" : "ami-3e60745c", "HVMG2" : "NOT_SUPPORTED"}, | ||
}; | ||
|
||
// get looks up the appropriate AMI for the given region and instance type. | ||
export function get(region: aws.Region, instanceType: aws.ec2.InstanceType): string { | ||
let arch = instanceTypeToArch[instanceType]; | ||
if (!arch) { | ||
throw new Error(`Unsupported instance type: ${instanceType}`); | ||
} | ||
let archToAmi = regionArchToAmi[region]; | ||
if (!archToAmi) { | ||
throw new Error(`Unsupported region: ${region}`); | ||
} | ||
let ami = archToAmi[arch]; | ||
if (!ami || ami === "NOT_SUPPORTED") { | ||
throw new Error(`Unsupported region and instance type combination: ${region} / ${instanceType} (${arch})`); | ||
} | ||
return ami; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import * as aws from "@pulumi/aws"; | ||
import * as pulumi from "@pulumi/pulumi"; | ||
|
||
const config = new pulumi.Config(pulumi.getProject()); | ||
|
||
// keyName is the name of an existing EC2 KeyPair to enable SSH access to the instances. | ||
export const keyName = config.get("keyName"); | ||
|
||
// dbName is a MySQL database name. | ||
export const dbName = config.get("dbName") || "MyDatabase"; | ||
if (!/[a-zA-Z][a-zA-Z0-9]*/.test(dbName)) { | ||
throw new Error("dbName must begin with a letter and contain only alphanumeric characters"); | ||
} else if (dbName.length < 1 || dbName.length > 64) { | ||
throw new Error("dbName must between 1-64 characters, inclusively") | ||
} | ||
|
||
// dbUser is the username for MySQL database access. | ||
export const dbUser = config.require("dbUser"); | ||
if (!/[a-zA-Z][a-zA-Z0-9]*/.test(dbUser)) { | ||
throw new Error("dbUser must begin with a letter and contain only alphanumeric characters"); | ||
} else if (dbUser.length < 1 || dbUser.length > 16) { | ||
throw new Error("dbUser must between 1-16 characters, inclusively"); | ||
} | ||
|
||
// dbPassword is the password for MySQL database access. | ||
export const dbPassword = config.require("dbPassword"); | ||
if (!/[a-zA-Z0-9]*/.test(dbPassword)) { | ||
throw new Error("dbPassword must only alphanumeric characters"); | ||
} else if (dbPassword.length < 1 || dbPassword.length > 41) { | ||
throw new Error("dbPassword must between 1-41 characters, inclusively"); | ||
} | ||
|
||
// dbRootPassword is the root password for MySQL. | ||
export const dbRootPassword = config.require("dbRootPassword"); | ||
if (!/[a-zA-Z0-9]*/.test(dbRootPassword)) { | ||
throw new Error("dbRootPassword must only alphanumeric characters"); | ||
} else if (dbRootPassword.length < 1 || dbRootPassword.length > 41) { | ||
throw new Error("dbRootPassword is must between 1-41 characters, inclusively"); | ||
} | ||
|
||
// instanceType is the WebServer EC2 instance type. | ||
export const instanceType: aws.ec2.InstanceType = <aws.ec2.InstanceType>config.get("instanceType") || "t2.small"; | ||
if (false) { | ||
// TODO: dynamically verify the values. | ||
throw new Error("instanceType must be a valid EC2 instance type"); | ||
} | ||
|
||
// sshLocation is the IP address range that can be used to SSH to the EC2 instances. | ||
export const sshLocation = config.get("sshLocation") || "0.0.0.0/0"; | ||
if (!new RegExp("(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})").test(sshLocation)) { | ||
throw new Error("sshLocation must be a valid IP CIDR range of the form x.x.x.x/x"); | ||
} else if (dbName.length < 1 || dbName.length > 41) { | ||
throw new Error("sshLocation is must between 9-18 characters, inclusively"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
development: | ||
adapter: mysql2 | ||
encoding: utf8 | ||
reconnect: false | ||
pool: 5 | ||
database: {{dbName}} | ||
username: {{dbUser}} | ||
password: {{dbPassword}} | ||
socket: /var/lib/mysql/mysql.sock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
#!/bin/bash -e | ||
source /etc/profile.d/rvm.sh | ||
rvm use 2.3.1 | ||
export HOME=/home/ec2-user | ||
export PATH=$PATH:/usr/local/bin | ||
cd /home/ec2-user | ||
|
||
# Kill the rails server if it is running to allow update | ||
if pgrep ruby &> /dev/null ; then pkill -TERM ruby ; fi | ||
|
||
# This sample template creates a new application inline | ||
# Typically you would use files and/or sources to download | ||
# your application package and perform any configuration here. | ||
|
||
# Create a new application, with therubyracer javascript library | ||
rails new sample -d mysql --skip-spring --skip-bundle --force | ||
cd /home/ec2-user/sample | ||
sed -i 's/^# \\(.*therubyracer.*$\\)/\\1/' Gemfile | ||
bundle install | ||
|
||
# Create a sample scoffold | ||
rails generate scaffold Note title:string body:text --force | ||
|
||
# Configure the database connection | ||
mv /tmp/database.yml config | ||
rake db:create db:migrate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/bin/bash | ||
curl -sSL https://get.rvm.io | bash | ||
source /etc/profile.d/rvm.sh | ||
rvm install 2.3.1 | ||
rvm --default use 2.3.1 | ||
gem install rails |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
CREATE USER '{{dbUser}}'@'localhost' IDENTIFIED BY '{{dbPassword}}'; | ||
GRANT ALL ON {{dbName}}.* TO '{{dbUser}}'@'localhost'; | ||
FLUSH PRIVILEGES; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
#!/bin/bash -e | ||
source /etc/profile.d/rvm.sh | ||
rvm use 2.3.1 | ||
export HOME=/home/ec2-user | ||
export PATH=$PATH:/usr/local/bin | ||
cd /home/ec2-user/sample | ||
|
||
# Startup the application | ||
rails server --binding 0.0.0.0 -p 80 -d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import * as aws from "@pulumi/aws"; | ||
import * as ami from "./ami"; | ||
import * as config from "./config"; | ||
import { createUserData, renderConfigFile } from "./init"; | ||
|
||
const webSg = new aws.ec2.SecurityGroup("webServerSecurityGroup", { | ||
description: "Enable HTTP and SSH access", | ||
egress: [ | ||
{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: [ "0.0.0.0/0" ] }, | ||
], | ||
ingress: [ | ||
{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: [ "0.0.0.0/0" ] }, | ||
{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: [ config.sshLocation ] }, | ||
], | ||
}); | ||
|
||
const webServer = new aws.ec2.Instance("webServer", { | ||
ami: ami.get(aws.config.requireRegion(), config.instanceType), | ||
instanceType: config.instanceType, | ||
securityGroups: [ webSg.name ], | ||
keyName: config.keyName, | ||
userData: createUserData( | ||
[ "install_ruby_2_3_1", "install_mysql", "configure_mysql", "install_application" ], | ||
{ | ||
"install_ruby_2_3_1": { | ||
files: { | ||
"/tmp/install_ruby": { | ||
content: renderConfigFile("./files/install_ruby", config), | ||
mode: "000500", | ||
owner: "root", | ||
group: "root", | ||
}, | ||
}, | ||
commands: { | ||
"01_install_ruby": { | ||
command: "/tmp/install_ruby > /var/log/install_ruby.log", | ||
}, | ||
}, | ||
}, | ||
"install_mysql": { | ||
packages: { | ||
yum: [ "mysql", "mysql-server", "mysql-devel", "mysql-libs" ], | ||
}, | ||
files: { | ||
"/tmp/setup.mysql": { | ||
content: renderConfigFile("./files/setup.mysql", config), | ||
mode: "000400", | ||
owner: "root", | ||
group: "root", | ||
}, | ||
}, | ||
services: { | ||
"sysvinit": { | ||
"mysqld": { enabled: true, ensureRunning: true }, | ||
}, | ||
}, | ||
}, | ||
"configure_mysql": { | ||
commands: { | ||
"01_set_mysql_root_password": { | ||
command: `mysqladmin -u root password '${config.dbRootPassword}'`, | ||
test: `$(mysql ${config.dbName} -u root --password='${config.dbRootPassword}' >/dev/null 2>&1 </dev/null); (( $? != 0 ))`, | ||
}, | ||
"02_create_database": { | ||
command: `mysql -u root --password='${config.dbRootPassword}' < /tmp/setup.mysql`, | ||
test: `$(mysql ${config.dbName} -u root --password='${config.dbRootPassword}' >/dev/null 2>&1 </dev/null); (( $? != 0 ))`, | ||
}, | ||
"03_cleanup": { | ||
command: "rm /tmp/setup.mysql", | ||
}, | ||
}, | ||
}, | ||
"install_application": { | ||
files: { | ||
"/tmp/database.yml": { | ||
content: renderConfigFile("./files/database.yml", config), | ||
mode: "000400", | ||
owner: "root", | ||
group: "root", | ||
}, | ||
"/tmp/install_application": { | ||
content: renderConfigFile("./files/install_application", config), | ||
mode: "000500", | ||
owner: "root", | ||
group: "root", | ||
}, | ||
"/home/ec2-user/start-application": { | ||
content: renderConfigFile("./files/start-application", config), | ||
mode: "000500", | ||
owner: "root", | ||
group: "root", | ||
}, | ||
}, | ||
commands: { | ||
"01_install_application": { | ||
command: "/tmp/install_application > /var/log/install_application.log", | ||
}, | ||
"02_configure_reboot": { | ||
command: "echo /home/ec2-user/start-application >> /etc/rc.local", | ||
}, | ||
"03_start_application": { | ||
command: "/home/ec2-user/start-application", | ||
}, | ||
"04_cleanup": { | ||
command: "rm /tmp/install_application", | ||
}, | ||
}, | ||
}, | ||
}, | ||
), | ||
}); | ||
|
||
// Expor the URL for our newly created Rails application. | ||
export let websiteURL = webServer.publicDns.apply(url => `https://${url}/notes`); |
Oops, something went wrong.