dew 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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