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,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