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,14 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.pattern = "./spec/**/*_spec.rb"
5
+ # Put spec opts in a file named .rspec in root
6
+ end
7
+
8
+ namespace :spec do
9
+ desc "Run tests with coverage check"
10
+ task :covered do
11
+ ENV['RSPEC_COVERED'] = '1'
12
+ Rake::Task['spec'].execute
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Validations
2
+
3
+ class Validation
4
+ def self.validates_format_of string, regex
5
+ raise ArgumentError, "Validation error. '#{string}' does not match '#{regex.inspect}'" unless regex.match(string)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Dew
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,39 @@
1
+ require 'terminal-table/import'
2
+
3
+ class String
4
+ def indent(n)
5
+ if n >= 0
6
+ gsub(/^/, ' ' * n)
7
+ else
8
+ gsub(/^ {0,#{-n}}/, "")
9
+ end
10
+ end
11
+ end
12
+
13
+
14
+ class View
15
+ def initialize(name, items, keys)
16
+ @name = name
17
+ @items = items
18
+ @keys = keys
19
+ end
20
+
21
+ def index
22
+ rows = @items.collect { |item| collect_values(item) }
23
+ "#{@name}:\n#{rows.empty? ? "None\n".indent(2) : table(@keys, *rows).to_s.indent(2)}"
24
+ end
25
+
26
+ def show(i)
27
+ "#{@name}:\n" +
28
+ table(nil, *@keys.collect { |item| item }.zip( collect_values(@items[i]))).to_s.indent(2)
29
+ end
30
+
31
+ private
32
+ def collect_values(item)
33
+ @keys.collect { |key|
34
+ v = item.is_a?(Hash) ? (item.has_key?(key) && item.fetch(key) || item.has_key?(key.to_sym) && item.fetch(key.to_sym)) : item.send(key)
35
+ v.inspect
36
+ }
37
+ end
38
+ end
39
+
@@ -0,0 +1,14 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.pattern = "./spec/**/*_spec.rb"
5
+ # Put spec opts in a file named .rspec in root
6
+ end
7
+
8
+ namespace :spec do
9
+ desc "Run tests with coverage check"
10
+ task :covered do
11
+ ENV['RSPEC_COVERED'] = '1'
12
+ Rake::Task['spec'].execute
13
+ end
14
+ end
@@ -0,0 +1,90 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ describe :Cloud do
4
+
5
+ let (:region) { 'ap-southeast-1' }
6
+ let (:account_name) { 'development' }
7
+ let (:profile_name) { 'test-light' }
8
+
9
+ let (:aws_credentials) { {:aws_access_key_id => '1234', :aws_secret_access_key => '5678', :region => region} }
10
+ let (:root_aws_credentials) { aws_credentials.merge(:provider => 'AWS')}
11
+ let (:account) { double('Account', aws_credentials) }
12
+ let (:profile) { double('Profile') }
13
+
14
+ context "after connect is called" do
15
+ before :each do
16
+ Account.stub(:read => account)
17
+ Cloud.connect(region, account_name, profile_name)
18
+ end
19
+
20
+ it { Cloud.region.should == region }
21
+ it { Cloud.account_name.should == account_name }
22
+ it { Cloud.profile_name.should == profile_name }
23
+
24
+ it "should provide the Account" do
25
+ Account.should_receive(:read).with(account_name).and_return(account)
26
+ Cloud.account.should == account
27
+ end
28
+
29
+ it "should provide the Profile" do
30
+ Profile.should_receive(:read).with(profile_name).and_return(profile)
31
+ Cloud.profile.should == profile
32
+ end
33
+
34
+ it "should provide a compute hook" do
35
+ Fog::Compute.should_receive(:new).with(root_aws_credentials).and_return('compute')
36
+ Cloud.compute.should == 'compute'
37
+ end
38
+
39
+ it "should provide a security groups hook" do
40
+ security_group = double(:security_group, :name => 'foo')
41
+ Fog::Compute.should_receive(:new).with(root_aws_credentials).and_return(double(:compute, :security_groups => [security_group]))
42
+ Cloud.security_groups.should == { 'foo' => security_group }
43
+ end
44
+
45
+ it "should return valid servers" do
46
+ servers = [
47
+ double(:server, :state => 'running'),
48
+ double(:server, :state => 'terminated'),
49
+ double(:server, :state => 'pending')
50
+ ]
51
+ Cloud.stub_chain(:compute, :servers).and_return( servers )
52
+ Cloud.valid_servers.should == [servers[0], servers[2]]
53
+ end
54
+
55
+ it "should check AWS to ensure the given keypair exists" do
56
+ Fog::Compute.should_receive(:new).with(root_aws_credentials).and_return(
57
+ double(:compute, :key_pairs => mock(:some_key_pairs, :get => true))
58
+ )
59
+ Cloud.keypair_exists?('a_keypair').should be_true
60
+ end
61
+
62
+ it "should provide an ELB hook" do
63
+ Fog::AWS::ELB.should_receive(:new).with(aws_credentials).and_return('elb')
64
+ Cloud.elb.should == 'elb'
65
+ end
66
+
67
+ it "should provide an RDS hook" do
68
+ Fog::AWS::RDS.should_receive(:new).with(aws_credentials).and_return('rds')
69
+ Cloud.rds.should == 'rds'
70
+ end
71
+
72
+ # TODO: should this be here?
73
+ it "should provide a hook to the rds_authorized_ec2_owner_ids" do
74
+ Fog::AWS::RDS.should_receive(:new).with(aws_credentials).and_return(
75
+ double(:rds, :security_groups => [ double(:security_group, :id => 'default', :ec2_security_groups => [
76
+ { "EC2SecurityGroupName" => "default", "Status" => "authorized", "EC2SecurityGroupOwnerId" => '12345' }
77
+ ]) ])
78
+ )
79
+ Cloud.rds_authorized_ec2_owner_ids.should == ['12345']
80
+ end
81
+
82
+ describe :keyfile_path do
83
+ it "should look for the keypair in the ~/.dew/accounts directory" do
84
+ Cloud.connect(region, account_name)
85
+ Cloud.keyfile_path('devops').should == "#{ENV['HOME']}/.dew/accounts/keys/#{account_name}/#{region}/devops.pem"
86
+ end
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,137 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
2
+
3
+ describe AMIsController do
4
+
5
+ let (:controller) { AMIsController.new }
6
+ let (:ami_name) { 'my-ami' }
7
+ let (:puppet_node_name) { 'puppet-node-name' }
8
+ let (:ssh) { double('SSH', :run => nil, :upload => nil) }
9
+ let (:server) { double('Server', :id => 'i-12345', :ssh => ssh, :public_ip_address => '127.0.0.1', :create_ami => nil) }
10
+ let (:environment) { double('Environment', :servers => [server], :destroy => nil) }
11
+ let (:ami) { double('AMI', :owner_id => '1234') }
12
+ let (:images) { double('ComputeImages') }
13
+
14
+ before { Cloud.stub(:compute => double('Compute', :images => images)) }
15
+
16
+ describe :create do
17
+ before :each do
18
+ Profile.stub(:read => nil)
19
+ Environment.stub(:create => environment)
20
+ end
21
+ after :each do
22
+ controller.create(ami_name, puppet_node_name)
23
+ end
24
+ it "should create a new environment using the ami-prototype profile" do
25
+ Profile.should_receive(:read).with('ami-prototype').and_return('ami_profile')
26
+ Environment.should_receive(:create).with(/#{ami_name}/, 'ami_profile').and_return environment
27
+ end
28
+ # Not all elements of the script are tested - just the important bits
29
+ it "should upload our puppet configuration to the instance" do
30
+ ssh.should_receive(:upload)#.with(File.join(ROOT_DIR, 'puppet'), '/tmp/puppet')
31
+ end
32
+ it "should run puppet using the node name we specified" do
33
+ ssh.should_receive(:run).with(%r{puppet.+/etc/puppet/manifests/nodes/#{puppet_node_name}.pp})
34
+ end
35
+ it "should create an AMI from the resulting server" do
36
+ server.should_receive(:create_ami).with(ami_name)
37
+ end
38
+ it "should finally destroy the environment" do
39
+ environment.should_receive(:destroy)
40
+ end
41
+ end
42
+
43
+ describe :index do
44
+ before { images.stub(:all => [ami]) }
45
+ after { controller.index }
46
+
47
+ it "should show an index of the amis" do
48
+ Cloud.should_receive(:account).at_least(1).and_return(double(:account, :aws_user_id => '1234'))
49
+ View.should_receive(:new).at_least(1).and_return(double(:view, :index => true))
50
+ Inform.should_receive(:info).at_least(1)
51
+ end
52
+ end
53
+
54
+ describe :show do
55
+ context "AMI doesn't exist" do
56
+ it "should raise error" do
57
+ images.stub(:all => [])
58
+ lambda { controller.show(ami_name) }.should raise_error /not found/i
59
+ end
60
+ end
61
+
62
+ context "AMI exists" do
63
+ before { images.stub(:all => [ami]) }
64
+ after { controller.show(ami_name) }
65
+
66
+ it "should show the ami" do
67
+ View.should_receive(:new).and_return(double(:view, :show => true))
68
+ Inform.should_receive(:info)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe :show do
74
+ context "AMI doesn't exist" do
75
+ it "should raise error" do
76
+ images.stub(:all => [])
77
+ lambda { controller.show(ami_name) }.should raise_error /not found/i
78
+ end
79
+ end
80
+
81
+ context "AMI exists" do
82
+ before { images.stub(:all => [ami]) }
83
+ after { controller.show(ami_name) }
84
+
85
+ it "should show the ami" do
86
+ View.should_receive(:new).and_return(double(:view, :show => true))
87
+ Inform.should_receive(:info)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe :destroy do
93
+ context "AMI doesn't exist" do
94
+ it "should error if the ami doesn't exist" do
95
+ images.stub(:all => [])
96
+ lambda { controller.destroy(ami_name) }.should raise_error /not found/i
97
+ end
98
+ end
99
+
100
+ context "AMI exists" do
101
+ before do
102
+ controller.stub(:agree => true)
103
+ images.stub(:all => [ami])
104
+ end
105
+
106
+ after { controller.destroy(ami_name, options) }
107
+
108
+ context "with no options" do
109
+ let(:options) { {} }
110
+
111
+ it "should find an AMI and destroy it if agreement is given" do
112
+ images.should_receive(:all).with('name' => ami_name).and_return([ami])
113
+ ami.should_receive(:deregister)
114
+ end
115
+
116
+ it "should ask the user for confirmation before destroying the AMI" do
117
+ controller.should_receive(:agree).and_return(true)
118
+ ami.should_receive(:deregister)
119
+ end
120
+
121
+ it "should not destroy the AMI if agreement is not given" do
122
+ controller.should_receive(:agree).and_return(false)
123
+ ami.should_not_receive(:deregister)
124
+ end
125
+ end
126
+
127
+ context "with :force => true" do
128
+ let(:options) { { :force => true } }
129
+
130
+ it "should not ask for agreement" do
131
+ controller.should_not_receive(:agree)
132
+ ami.should_receive(:deregister)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,38 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
2
+
3
+ describe DeployController do
4
+
5
+ let (:type) { 'puge' }
6
+ let (:tag) { 'puge-1.16.1' }
7
+ let (:rails_env) { 'development' }
8
+ let (:environment_name) { 'myenvironment' }
9
+ let (:opts) { { 'tag' => tag, 'rails_env' => rails_env } }
10
+
11
+ describe :create do
12
+ before :each do
13
+ @servers = []
14
+ (0..1).each { @servers << double('Server').as_null_object }
15
+ @server = @servers.first
16
+ Server.stub(:find => [])
17
+ Database.stub(:get => nil)
18
+ @environment = double('Environment', :servers => @servers)
19
+ Environment.stub(:get => @environment)
20
+ end
21
+
22
+ context "no servers exist for the nominated environment" do
23
+ it "should not raise an error" do
24
+ environment = double('Environment', :servers => [])
25
+ Environment.stub(:get).and_return(environment)
26
+ lambda { DeployController.new.create(type, environment_name, { 'tag' => tag, 'rails_env' => rails_env }) }.should raise_error /instances already terminated/
27
+ end
28
+ end
29
+
30
+ it "should create an instance of Deploy and deploy" do
31
+ deploy_run = double('Deploy::Run')
32
+ Deploy::Run.should_receive(:new).with(type, @environment, opts).and_return(deploy_run)
33
+ deploy_run.should_receive(:deploy)
34
+ DeployController.new.create(type, environment_name, opts)
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '../spec_helper'))
2
+
3
+ describe EnvironmentsController do
4
+ subject { EnvironmentsController.new }
5
+
6
+ after { subject }
7
+
8
+ describe :create do
9
+ let(:profile) { mock(:profile, :keypair => 'default') }
10
+ before {
11
+ @env = double(:environment)
12
+ Profile.should_receive(:read).with('profile_name').and_return(profile)
13
+ subject.stub(:agree => true)
14
+ }
15
+
16
+ after { subject.create(:name , 'profile_name', options) }
17
+
18
+
19
+ context "no options" do
20
+ let(:options) { {} }
21
+
22
+ it "should ask the user for confirmation before destroying the environment" do
23
+ Environment.should_receive(:create).with(:name, profile).and_return(@env)
24
+ @env.should_receive(:show)
25
+ subject.should_receive(:agree).and_return(true)
26
+ end
27
+
28
+ it "should not create the environment if agreement is not given" do
29
+ subject.should_receive(:agree).and_return(false)
30
+ @env.should_not_receive(:create)
31
+ end
32
+ end
33
+
34
+ context ":force => true" do
35
+ let(:options) { {:force => true} }
36
+
37
+ it "should not ask for agreement" do
38
+ Environment.should_receive(:create).with(:name, profile).and_return(@env)
39
+ subject.should_not_receive(:agree)
40
+ @env.should_receive(:show)
41
+ end
42
+ end
43
+ end
44
+
45
+ describe :index do
46
+ after { subject.index }
47
+
48
+ it "should index the environments" do
49
+ Environment.should_receive(:index)
50
+ end
51
+ end
52
+
53
+ describe :show do
54
+ let(:name) { 'foo' }
55
+
56
+ before {
57
+ Environment.should_receive(:get).with(name).and_return(environment)
58
+ }
59
+
60
+ context "environment doesn't exist" do
61
+ let(:environment) { nil }
62
+
63
+ it "should raise an exception" do
64
+ lambda { subject.show('foo') }.should raise_error /not found/
65
+ end
66
+ end
67
+
68
+ context "environment exists" do
69
+ let(:environment) { double('Environment') }
70
+
71
+ after { subject.show(name) }
72
+
73
+ it "should show an environment" do
74
+ environment.should_receive(:show)
75
+ end
76
+ end
77
+
78
+ end
79
+
80
+ describe :destroy do
81
+ let(:name) { 'foo' }
82
+
83
+ before {
84
+ Environment.should_receive(:get).with(name).and_return(environment)
85
+ }
86
+
87
+ context "environment doesn't exist" do
88
+ let(:environment) { nil }
89
+
90
+ it "should raise an exception" do
91
+ lambda { subject.destroy(name) }.should raise_error /not found/
92
+ end
93
+ end
94
+
95
+ context "environment exists" do
96
+ let(:environment) { double('Environment', :destroy => true, :show => true) }
97
+
98
+ before { subject.stub(:agree => true) }
99
+
100
+ after { subject.destroy(name, options) }
101
+
102
+ context "no options" do
103
+ let(:options) { {} }
104
+
105
+ it "should show the environment before destroying it" do
106
+ environment.should_receive(:show)
107
+ end
108
+
109
+ it "should find an environment and destroy it if agreement is given" do
110
+ environment.should_receive(:destroy)
111
+ end
112
+
113
+ it "should ask the user for confirmation before destroying the environment" do
114
+ subject.should_receive(:agree).and_return(true)
115
+ end
116
+
117
+ it "should not destroy the environment if agreement is not given" do
118
+ subject.should_receive(:agree).and_return(false)
119
+ environment.should_not_receive(:destroy)
120
+ end
121
+ end
122
+
123
+ context ":force => true" do
124
+ let(:options) { {:force => true} }
125
+
126
+ it "should not ask for agreement" do
127
+ subject.should_not_receive(:agree)
128
+ environment.should_receive(:destroy)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end