dew 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +38 -0
- data/Rakefile +26 -0
- data/bin/dew +87 -0
- data/config/cucumber.yaml +4 -0
- data/features/create-ami.feature +16 -0
- data/features/create-environments.feature +46 -0
- data/features/deploy-puge.feature +16 -0
- data/features/step_definitions/aws-steps.rb +101 -0
- data/features/step_definitions/deploy-puge-steps.rb +27 -0
- data/features/support/env.rb +38 -0
- data/features/support/hooks.rb +10 -0
- data/lib/dew.rb +7 -0
- data/lib/dew/aws_resources.yaml +122 -0
- data/lib/dew/base_command.rb +24 -0
- data/lib/dew/cloud.rb +79 -0
- data/lib/dew/commands.rb +6 -0
- data/lib/dew/commands/ami.rb +67 -0
- data/lib/dew/commands/console.rb +17 -0
- data/lib/dew/commands/console/irb_override.rb +24 -0
- data/lib/dew/commands/deploy.rb +114 -0
- data/lib/dew/commands/deploy/templates/apache.conf.erb +28 -0
- data/lib/dew/commands/deploy/templates/known_hosts +2 -0
- data/lib/dew/commands/deploy/templates/rvmrc +2 -0
- data/lib/dew/commands/environments.rb +110 -0
- data/lib/dew/commands/tidy.rb +35 -0
- data/lib/dew/controllers.rb +3 -0
- data/lib/dew/controllers/amis_controller.rb +82 -0
- data/lib/dew/controllers/deploy_controller.rb +10 -0
- data/lib/dew/controllers/environments_controller.rb +48 -0
- data/lib/dew/models.rb +7 -0
- data/lib/dew/models/account.rb +30 -0
- data/lib/dew/models/database.rb +32 -0
- data/lib/dew/models/deploy.rb +2 -0
- data/lib/dew/models/deploy/puge.rb +61 -0
- data/lib/dew/models/deploy/run.rb +19 -0
- data/lib/dew/models/environment.rb +199 -0
- data/lib/dew/models/fog_model.rb +23 -0
- data/lib/dew/models/profile.rb +60 -0
- data/lib/dew/models/server.rb +134 -0
- data/lib/dew/password.rb +7 -0
- data/lib/dew/tasks/spec.rake +14 -0
- data/lib/dew/validations.rb +8 -0
- data/lib/dew/version.rb +3 -0
- data/lib/dew/view.rb +39 -0
- data/lib/tasks/spec.rake +14 -0
- data/spec/dew/cloud_spec.rb +90 -0
- data/spec/dew/controllers/amis_controller_spec.rb +137 -0
- data/spec/dew/controllers/deploy_controller_spec.rb +38 -0
- data/spec/dew/controllers/environments_controller_spec.rb +133 -0
- data/spec/dew/models/account_spec.rb +47 -0
- data/spec/dew/models/database_spec.rb +58 -0
- data/spec/dew/models/deploy/puge_spec.rb +72 -0
- data/spec/dew/models/deploy/run_spec.rb +38 -0
- data/spec/dew/models/environment_spec.rb +374 -0
- data/spec/dew/models/fog_model_spec.rb +24 -0
- data/spec/dew/models/profile_spec.rb +85 -0
- data/spec/dew/models/server_spec.rb +190 -0
- data/spec/dew/password_spec.rb +11 -0
- data/spec/dew/spec_helper.rb +22 -0
- data/spec/dew/view_spec.rb +38 -0
- metadata +284 -0
data/lib/dew.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
---
|
2
|
+
regions:
|
3
|
+
us-east-1: US East (Virginia)
|
4
|
+
us-west-1: US West (North California)
|
5
|
+
eu-west-1: EU West (Ireland)
|
6
|
+
ap-southeast-1: Asia Pacific (Singapore)
|
7
|
+
ap-northeast-1: Asia Pacific (Tokyo)
|
8
|
+
|
9
|
+
instance_types:
|
10
|
+
m1.small:
|
11
|
+
memory: 1.7 GB
|
12
|
+
processor: 1 ECU
|
13
|
+
storage: 160 GB
|
14
|
+
platform: 32-bit
|
15
|
+
io_performance: Moderate
|
16
|
+
|
17
|
+
m1.large:
|
18
|
+
memory: 7.5 GB
|
19
|
+
processor: 4 ECUs
|
20
|
+
storage: 850 GB
|
21
|
+
platform: 64-bit
|
22
|
+
io_performance: High
|
23
|
+
|
24
|
+
m1.xlarge:
|
25
|
+
memory: 15 GB
|
26
|
+
processor: 8 ECUs
|
27
|
+
storage: 1,690 GB
|
28
|
+
platform: 64-bit
|
29
|
+
io_performance: High
|
30
|
+
|
31
|
+
t1.micro:
|
32
|
+
memory: 613MB
|
33
|
+
processor: Up to 2 ECUs
|
34
|
+
storage: EBS storage only
|
35
|
+
platform: 32-bit or 64-bit
|
36
|
+
io_performance: Low
|
37
|
+
|
38
|
+
m2.xlarge:
|
39
|
+
memory: 17.1 GB
|
40
|
+
processor: 6.5 ECUs
|
41
|
+
storage: 420 GB
|
42
|
+
platform: 64-bit
|
43
|
+
io_performance: Moderate
|
44
|
+
|
45
|
+
m2.2xlarge:
|
46
|
+
memory: 34.2 GB
|
47
|
+
processor: 13 ECUs
|
48
|
+
storage: 850 GB
|
49
|
+
platform: 64-bit
|
50
|
+
io_performance: High
|
51
|
+
|
52
|
+
m2.4xlarge:
|
53
|
+
memory: 68.4 GB
|
54
|
+
processor: 26 ECUs
|
55
|
+
storage: 1690 GB
|
56
|
+
platform: 64-bit
|
57
|
+
io_performance: High
|
58
|
+
|
59
|
+
c1.medium:
|
60
|
+
memory: 1.7 GB
|
61
|
+
processor: 5 ECUs
|
62
|
+
storage: 350 GB
|
63
|
+
platform: 32-bit
|
64
|
+
io_performance: Moderate
|
65
|
+
|
66
|
+
c1.xlarge:
|
67
|
+
memory: 7 GB
|
68
|
+
processor: 20 ECUs
|
69
|
+
storage: 1690 GB
|
70
|
+
platform: 64-bit
|
71
|
+
io_performance: High
|
72
|
+
|
73
|
+
cc1.4xlarge:
|
74
|
+
memory: 23 GB
|
75
|
+
processor: 33.5 ECUs
|
76
|
+
storage: 1690 GB
|
77
|
+
platform: 64-bit
|
78
|
+
io_performance: Very High
|
79
|
+
|
80
|
+
cg1.4xlarge:
|
81
|
+
memory: 22 GB
|
82
|
+
processor: 33.5 ECUs
|
83
|
+
storage: 1690 GB
|
84
|
+
platform: 64-bit
|
85
|
+
io_performance: Very High
|
86
|
+
|
87
|
+
db_instance_types:
|
88
|
+
db.m1.small:
|
89
|
+
memory: 1.7 GB
|
90
|
+
processor: 1 ECU
|
91
|
+
platform: 64-bit
|
92
|
+
io_performance: Moderate
|
93
|
+
|
94
|
+
db.m1.large:
|
95
|
+
memory: 7.5 GB
|
96
|
+
processor: 4 ECUs
|
97
|
+
platform: 64-bit
|
98
|
+
io_performance: High
|
99
|
+
|
100
|
+
db.m1.xlarge:
|
101
|
+
memory: 15 GB
|
102
|
+
processor: 8 ECUs
|
103
|
+
platform: 64-bit
|
104
|
+
io_performance: High
|
105
|
+
|
106
|
+
db.m2.xlarge:
|
107
|
+
memory: 17.1 GB
|
108
|
+
processor: 6.5 ECU
|
109
|
+
platform: 64-bit
|
110
|
+
io_performance: High
|
111
|
+
|
112
|
+
db.m2.2xlarge:
|
113
|
+
memory: 34 GB
|
114
|
+
processor: 13 ECUs
|
115
|
+
platform: 64-bit
|
116
|
+
io_performance: High
|
117
|
+
|
118
|
+
db.m2.4xlarge:
|
119
|
+
memory: 68 GB
|
120
|
+
processor: 26 ECUs
|
121
|
+
platform: 64-bit
|
122
|
+
io_performance: High
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
|
3
|
+
# Monkey patch clamp to remove duplicate options from help
|
4
|
+
module Clamp::Option::Declaration
|
5
|
+
alias_method :non_uniq_documented_options, :documented_options
|
6
|
+
def documented_options
|
7
|
+
non_uniq_documented_options.uniq
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
class DewBaseCommand < Clamp::Command
|
13
|
+
def configure
|
14
|
+
$debug = debug?
|
15
|
+
Inform.level = quiet? ? :warning : (verbose? ? :debug : :info)
|
16
|
+
Cloud.region = region
|
17
|
+
Cloud.account_name = account
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute
|
21
|
+
configure
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
data/lib/dew/cloud.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
class Cloud
|
2
|
+
|
3
|
+
attr_reader :region, :account_name, :profile_name
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def connect region, account_name, profile_name = nil
|
8
|
+
@connection = new(region, account_name, profile_name)
|
9
|
+
Inform.info("Connected to AWS in %{region} using account %{account_name}", :region => region, :account_name => account_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing method, *args
|
13
|
+
@connection.send(method, *args)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def account
|
19
|
+
@account ||= Account.read(account_name)
|
20
|
+
end
|
21
|
+
|
22
|
+
def compute
|
23
|
+
@compute ||= Fog::Compute.new(fog_credentials.merge({:provider => 'AWS'}))
|
24
|
+
end
|
25
|
+
|
26
|
+
def security_groups
|
27
|
+
@security_groups ||= compute.security_groups.inject({}) { |h, g| h[g.name] = g; h }
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid_servers
|
31
|
+
@valid_servers ||= Cloud.compute.servers.select{ |s| %w{running pending}.include?(s.state) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def keypair_exists? keypair
|
35
|
+
!!compute.key_pairs.get(keypair)
|
36
|
+
end
|
37
|
+
|
38
|
+
def elb
|
39
|
+
@elb ||= Fog::AWS::ELB.new(fog_credentials)
|
40
|
+
end
|
41
|
+
|
42
|
+
def rds
|
43
|
+
@rds ||= Fog::AWS::RDS.new(fog_credentials)
|
44
|
+
end
|
45
|
+
|
46
|
+
def rds_authorized_ec2_owner_ids
|
47
|
+
# XXX - Does this belong in Fog::AWS::RDS ?
|
48
|
+
@rds_authorized_ec2_owner_ids ||= rds.security_groups.detect { |security_group|
|
49
|
+
security_group.id == 'default'
|
50
|
+
}.ec2_security_groups.select { |ec2_security_group|
|
51
|
+
ec2_security_group["EC2SecurityGroupName"] == "default" && ec2_security_group["Status"] == "authorized"
|
52
|
+
}.collect { |h|
|
53
|
+
h["EC2SecurityGroupOwnerId"]
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def profile
|
58
|
+
if profile_name
|
59
|
+
@profile ||= Profile.read(profile_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def keyfile_path(key_name)
|
64
|
+
account_dir = File.join(ENV['HOME'], '.dew','accounts')
|
65
|
+
File.join(account_dir, 'keys', account_name, region, "#{key_name}.pem")
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def initialize region, account_name, profile_name = nil
|
71
|
+
@region = region
|
72
|
+
@account_name = account_name
|
73
|
+
@profile_name = profile_name
|
74
|
+
end
|
75
|
+
|
76
|
+
def fog_credentials
|
77
|
+
{:region => region, :aws_access_key_id => account.aws_access_key_id, :aws_secret_access_key => account.aws_secret_access_key}
|
78
|
+
end
|
79
|
+
end
|
data/lib/dew/commands.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'dew/controllers/amis_controller'
|
2
|
+
|
3
|
+
class AMIsCommand < Clamp::Command
|
4
|
+
|
5
|
+
def puppet_node_filename node_name
|
6
|
+
File.join(ENV['HOME'], '.dew', 'puppet', 'manifests', 'nodes', "#{node_name}.pp")
|
7
|
+
end
|
8
|
+
|
9
|
+
def controller
|
10
|
+
@controller ||= AMIsController.new
|
11
|
+
end
|
12
|
+
|
13
|
+
default_subcommand "index", "Show AMIs" do
|
14
|
+
|
15
|
+
def execute
|
16
|
+
controller.index
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
subcommand "show", "Show an AMI" do
|
22
|
+
|
23
|
+
parameter "AMI_NAME", "Name of AMI"
|
24
|
+
|
25
|
+
def execute
|
26
|
+
controller.show(ami_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
subcommand "show", "Show an AMI" do
|
32
|
+
|
33
|
+
parameter "AMI_NAME", "Name of AMI"
|
34
|
+
|
35
|
+
def execute
|
36
|
+
controller.show(ami_name)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
subcommand "create", "Create a new AMI" do
|
42
|
+
|
43
|
+
parameter "PUPPET_NODE_NAME", "Puppet node (in puppet/manifests/nodes/*.pp) to run on AMI" do |puppet_node_name|
|
44
|
+
unless File.exist?(puppet_node_filename(puppet_node_name))
|
45
|
+
raise ArgumentError, "Can't find puppet/#{puppet_node_filename(puppet_node_name)}: check that puppet submodule is checked out and node exists"
|
46
|
+
end
|
47
|
+
puppet_node_name
|
48
|
+
end
|
49
|
+
parameter "AMI_NAME", "What to call the newly created AMI"
|
50
|
+
|
51
|
+
def execute
|
52
|
+
controller.create(ami_name, puppet_node_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
subcommand "destroy", "Destroy an existing AMI" do
|
58
|
+
|
59
|
+
parameter "AMI_NAME", "Name of AMI to destroy"
|
60
|
+
option ['-f', '--force'], :flag, "Don't ask for confirmation before destroying", :default => false
|
61
|
+
|
62
|
+
def execute
|
63
|
+
controller.destroy(ami_name, :force => force?)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class ConsoleCommand < Clamp::Command
|
2
|
+
def execute
|
3
|
+
load File.expand_path(File.join(File.dirname(__FILE__), 'console', 'irb_override.rb'))
|
4
|
+
ARGV.reject! { true }
|
5
|
+
puts <<-EOS
|
6
|
+
===============================================================
|
7
|
+
Objects available: -
|
8
|
+
|
9
|
+
Cloud - Cloud Handle
|
10
|
+
Cloud.compute - Access AWS EC2 (Elastic Compute Cloud) instances
|
11
|
+
Cloud.elb - Access AWS ELB (Elastic Load Balancers)
|
12
|
+
Cloud.rds - Access AWS RDS (Relational Database Service)
|
13
|
+
===============================================================
|
14
|
+
EOS
|
15
|
+
IRB.start_session(binding)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'irb'
|
2
|
+
|
3
|
+
module IRB # :nodoc:
|
4
|
+
def self.start_session(binding)
|
5
|
+
unless @__initialized
|
6
|
+
args = ARGV
|
7
|
+
ARGV.replace(ARGV.dup)
|
8
|
+
IRB.setup(nil)
|
9
|
+
ARGV.replace(args)
|
10
|
+
@__initialized = true
|
11
|
+
end
|
12
|
+
|
13
|
+
workspace = WorkSpace.new(binding)
|
14
|
+
|
15
|
+
irb = Irb.new(workspace)
|
16
|
+
|
17
|
+
@CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
|
18
|
+
@CONF[:MAIN_CONTEXT] = irb.context
|
19
|
+
|
20
|
+
catch(:IRB_EXIT) do
|
21
|
+
irb.eval_input
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'dew/controllers/amis_controller'
|
2
|
+
require 'erb' # XXX
|
3
|
+
require 'ostruct' # XXX
|
4
|
+
require 'open-uri' # XXX
|
5
|
+
require 'json' # XXX
|
6
|
+
|
7
|
+
class DeployCommand < Clamp::Command
|
8
|
+
|
9
|
+
def template filename
|
10
|
+
File.join(File.dirname(__FILE__), 'deploy', 'templates', filename)
|
11
|
+
end
|
12
|
+
|
13
|
+
subcommand 'passenger', "Deploy a Passenger-compatiable application to an environment" do
|
14
|
+
parameter "APPLICATION_NAME", "Repository and application name"
|
15
|
+
parameter "REVISION", "Git revision to deploy (tag, branch, or commit id)"
|
16
|
+
parameter "ENVIRONMENT_NAME", "Environment to deploy into"
|
17
|
+
|
18
|
+
option ['--rails-env'], 'ENVIRONMENT', "Rails environment to use", :default => 'production'
|
19
|
+
|
20
|
+
def execute
|
21
|
+
env = Environment.get(environment_name)
|
22
|
+
env.servers.each do |server|
|
23
|
+
Inform.info("Working with server %{id} of %{l} servers", :id => server.id, :l => env.servers.length)
|
24
|
+
env.remove_server_from_elb(server) if env.has_elb?
|
25
|
+
|
26
|
+
ssh = server.ssh
|
27
|
+
initial = !ssh.exist?(application_name)
|
28
|
+
|
29
|
+
Inform.info("%{app} doesn't exist - initial install", :app => application_name) if initial
|
30
|
+
|
31
|
+
Inform.info("Stopping apache") do
|
32
|
+
ssh.run "sudo apache2ctl stop"
|
33
|
+
end
|
34
|
+
|
35
|
+
Inform.info("Obtaining version %{v} of %{app}", :v => revision, :app => application_name) do
|
36
|
+
if initial
|
37
|
+
Inform.debug("Writing out ~/.ssh/known_hosts file to allow github clone")
|
38
|
+
ssh.upload template('known_hosts'), ".ssh/known_hosts"
|
39
|
+
Inform.debug("Cloning %{app} in to ~/%{app}", :app => application_name)
|
40
|
+
ssh.run "git clone git@github.com:playup/#{application_name}.git #{application_name}"
|
41
|
+
else
|
42
|
+
Inform.debug("Updating %{app} repository", :app => application_name)
|
43
|
+
ssh.run "cd #{application_name}; git fetch -q"
|
44
|
+
end
|
45
|
+
|
46
|
+
Inform.debug("Checking out version %{version}", :version => revision)
|
47
|
+
ssh.run "cd #{application_name} && git checkout -q -f origin/#{revision}"
|
48
|
+
end
|
49
|
+
|
50
|
+
cd_and_rvm = "cd #{application_name} && . /usr/local/rvm/scripts/rvm && rvm use ruby-1.9.2 && RAILS_ENV=#{rails_env} "
|
51
|
+
|
52
|
+
Inform.info("Updating/installing gems") do
|
53
|
+
ssh.run cd_and_rvm + "bundle install"
|
54
|
+
end
|
55
|
+
|
56
|
+
if ssh.exist?("#{application_name}/config/database.yml")
|
57
|
+
Inform.info("config/database.yml exists, creating and/or updating database") do
|
58
|
+
Inform.debug("Creating database") if initial
|
59
|
+
ssh.run cd_and_rvm + "rake db:create" if initial
|
60
|
+
Inform.debug("Updating database")
|
61
|
+
ssh.run cd_and_rvm + "rake db:migrate"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
Inform.info("No config/database.yml, skipping database step")
|
65
|
+
end
|
66
|
+
|
67
|
+
Inform.info("Starting application with passenger") do
|
68
|
+
if ssh.exist?('/etc/apache2/sites-enabled/000-default')
|
69
|
+
Inform.debug("Disabling default apache site")
|
70
|
+
ssh.run "sudo a2dissite default"
|
71
|
+
end
|
72
|
+
Inform.debug("Uploading passenger config")
|
73
|
+
passenger_config = ERB.new(File.read template('apache.conf.erb')).result(OpenStruct.new(
|
74
|
+
:rails_env => rails_env,
|
75
|
+
:application_name => application_name,
|
76
|
+
:working_directory => "/home/ubuntu/#{application_name}"
|
77
|
+
).instance_eval {binding})
|
78
|
+
ssh.write passenger_config, "/tmp/apache.conf"
|
79
|
+
ssh.run "sudo cp /tmp/apache.conf /etc/apache2/sites-available/#{application_name}"
|
80
|
+
ssh.run "sudo chmod 0644 /etc/apache2/sites-available/#{application_name}" # yeah, I don't know why it gets written as 0600
|
81
|
+
unless ssh.exist?('/etc/apache2/sites-enabled/#{application_name}')
|
82
|
+
Inform.debug("Enabling passenger site in apache")
|
83
|
+
ssh.run "sudo a2ensite #{application_name}"
|
84
|
+
end
|
85
|
+
Inform.debug("Restarting apache")
|
86
|
+
ssh.run "sudo apache2ctl restart"
|
87
|
+
end
|
88
|
+
|
89
|
+
status_url = "http://#{server.public_ip_address}/status"
|
90
|
+
Inform.info("Checking status URL at %{u}", :u => status_url) do
|
91
|
+
response = JSON.parse(open(status_url).read)
|
92
|
+
unless response.include?('status') && response['status'] == 'OK'
|
93
|
+
raise "Did not receive an OK status response."
|
94
|
+
end
|
95
|
+
end
|
96
|
+
env.add_server_to_elb(server) if env.has_elb?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
subcommand 'puge', "Deploy PUGE" do
|
103
|
+
parameter "TAG", "Git revision to deploy (tag, branch, or commit id)"
|
104
|
+
parameter "ENVIRONMENT_NAME", "Environment to deploy into"
|
105
|
+
parameter "RAILS_ENV", "Rails environment used for db setup"
|
106
|
+
|
107
|
+
def execute
|
108
|
+
Inform.info("Deploying PUGE tag %{tag} using RAILS_ENV %{rails_env} to environment %{environment_name}", :tag => tag, :rails_env => rails_env, :environment_name => environment_name)
|
109
|
+
|
110
|
+
DeployController.new.create('puge', environment_name, { 'tag' => tag, 'rails_env' => rails_env })
|
111
|
+
Inform.info("Deployed successfully.")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|