dew 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +38 -0
  3. data/Rakefile +26 -0
  4. data/bin/dew +87 -0
  5. data/config/cucumber.yaml +4 -0
  6. data/features/create-ami.feature +16 -0
  7. data/features/create-environments.feature +46 -0
  8. data/features/deploy-puge.feature +16 -0
  9. data/features/step_definitions/aws-steps.rb +101 -0
  10. data/features/step_definitions/deploy-puge-steps.rb +27 -0
  11. data/features/support/env.rb +38 -0
  12. data/features/support/hooks.rb +10 -0
  13. data/lib/dew.rb +7 -0
  14. data/lib/dew/aws_resources.yaml +122 -0
  15. data/lib/dew/base_command.rb +24 -0
  16. data/lib/dew/cloud.rb +79 -0
  17. data/lib/dew/commands.rb +6 -0
  18. data/lib/dew/commands/ami.rb +67 -0
  19. data/lib/dew/commands/console.rb +17 -0
  20. data/lib/dew/commands/console/irb_override.rb +24 -0
  21. data/lib/dew/commands/deploy.rb +114 -0
  22. data/lib/dew/commands/deploy/templates/apache.conf.erb +28 -0
  23. data/lib/dew/commands/deploy/templates/known_hosts +2 -0
  24. data/lib/dew/commands/deploy/templates/rvmrc +2 -0
  25. data/lib/dew/commands/environments.rb +110 -0
  26. data/lib/dew/commands/tidy.rb +35 -0
  27. data/lib/dew/controllers.rb +3 -0
  28. data/lib/dew/controllers/amis_controller.rb +82 -0
  29. data/lib/dew/controllers/deploy_controller.rb +10 -0
  30. data/lib/dew/controllers/environments_controller.rb +48 -0
  31. data/lib/dew/models.rb +7 -0
  32. data/lib/dew/models/account.rb +30 -0
  33. data/lib/dew/models/database.rb +32 -0
  34. data/lib/dew/models/deploy.rb +2 -0
  35. data/lib/dew/models/deploy/puge.rb +61 -0
  36. data/lib/dew/models/deploy/run.rb +19 -0
  37. data/lib/dew/models/environment.rb +199 -0
  38. data/lib/dew/models/fog_model.rb +23 -0
  39. data/lib/dew/models/profile.rb +60 -0
  40. data/lib/dew/models/server.rb +134 -0
  41. data/lib/dew/password.rb +7 -0
  42. data/lib/dew/tasks/spec.rake +14 -0
  43. data/lib/dew/validations.rb +8 -0
  44. data/lib/dew/version.rb +3 -0
  45. data/lib/dew/view.rb +39 -0
  46. data/lib/tasks/spec.rake +14 -0
  47. data/spec/dew/cloud_spec.rb +90 -0
  48. data/spec/dew/controllers/amis_controller_spec.rb +137 -0
  49. data/spec/dew/controllers/deploy_controller_spec.rb +38 -0
  50. data/spec/dew/controllers/environments_controller_spec.rb +133 -0
  51. data/spec/dew/models/account_spec.rb +47 -0
  52. data/spec/dew/models/database_spec.rb +58 -0
  53. data/spec/dew/models/deploy/puge_spec.rb +72 -0
  54. data/spec/dew/models/deploy/run_spec.rb +38 -0
  55. data/spec/dew/models/environment_spec.rb +374 -0
  56. data/spec/dew/models/fog_model_spec.rb +24 -0
  57. data/spec/dew/models/profile_spec.rb +85 -0
  58. data/spec/dew/models/server_spec.rb +190 -0
  59. data/spec/dew/password_spec.rb +11 -0
  60. data/spec/dew/spec_helper.rb +22 -0
  61. data/spec/dew/view_spec.rb +38 -0
  62. metadata +284 -0
@@ -0,0 +1,7 @@
1
+ require 'inform'
2
+ require 'dew/commands'
3
+ require 'dew/models'
4
+ require 'dew/controllers'
5
+ require 'dew/cloud'
6
+ require 'dew/view'
7
+ require 'dew/password'
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'dew/base_command'
2
+ require 'dew/commands/ami'
3
+ require 'dew/commands/deploy'
4
+ require 'dew/commands/environments'
5
+ require 'dew/commands/console'
6
+ require 'dew/commands/tidy'
@@ -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