cluster_chef-knife 3.0.5

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 (42) hide show
  1. data/.gitignore +51 -0
  2. data/.rspec +3 -0
  3. data/CHANGELOG.md +63 -0
  4. data/Gemfile +18 -0
  5. data/LICENSE +201 -0
  6. data/README.md +332 -0
  7. data/Rakefile +92 -0
  8. data/TODO.md +8 -0
  9. data/VERSION +1 -0
  10. data/chefignore +41 -0
  11. data/cluster_chef-knife.gemspec +111 -0
  12. data/clusters/website_demo.rb +65 -0
  13. data/config/client.rb +59 -0
  14. data/lib/chef/knife/bootstrap/ubuntu10.04-basic.erb +78 -0
  15. data/lib/chef/knife/bootstrap/ubuntu10.04-cluster_chef.erb +139 -0
  16. data/lib/chef/knife/bootstrap/ubuntu11.10-cluster_chef.erb +128 -0
  17. data/lib/chef/knife/cluster_bootstrap.rb +69 -0
  18. data/lib/chef/knife/cluster_kick.rb +86 -0
  19. data/lib/chef/knife/cluster_kill.rb +73 -0
  20. data/lib/chef/knife/cluster_launch.rb +168 -0
  21. data/lib/chef/knife/cluster_list.rb +50 -0
  22. data/lib/chef/knife/cluster_proxy.rb +118 -0
  23. data/lib/chef/knife/cluster_show.rb +56 -0
  24. data/lib/chef/knife/cluster_ssh.rb +94 -0
  25. data/lib/chef/knife/cluster_start.rb +32 -0
  26. data/lib/chef/knife/cluster_stop.rb +37 -0
  27. data/lib/chef/knife/cluster_sync.rb +76 -0
  28. data/lib/chef/knife/generic_command.rb +66 -0
  29. data/lib/chef/knife/knife_common.rb +199 -0
  30. data/notes/aws_console_screenshot.jpg +0 -0
  31. data/rspec.watchr +29 -0
  32. data/spec/cluster_chef/cluster_spec.rb +13 -0
  33. data/spec/cluster_chef/facet_spec.rb +70 -0
  34. data/spec/cluster_chef/server_slice_spec.rb +19 -0
  35. data/spec/cluster_chef/server_spec.rb +112 -0
  36. data/spec/cluster_chef_spec.rb +193 -0
  37. data/spec/spec_helper/dummy_chef.rb +25 -0
  38. data/spec/spec_helper.rb +50 -0
  39. data/spec/test_config.rb +20 -0
  40. data/tasks/chef_config.rb +38 -0
  41. data/tasks/jeweler_use_alt_branch.rb +47 -0
  42. metadata +223 -0
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require CLUSTER_CHEF_DIR("lib/cluster_chef")
3
+
4
+ describe ClusterChef::ServerSlice do
5
+ before do
6
+ @slice = ClusterChef.slice('webserver_demo')
7
+ end
8
+
9
+ describe 'attributes' do
10
+ it 'security groups' do
11
+ @slice.security_groups.keys.sort.should == [
12
+ "default",
13
+ "webserver_demo", "webserver_demo-awesome_website", "webserver_demo-dbnode", "webserver_demo-esnode",
14
+ "webserver_demo-redis_client", "webserver_demo-redis_server",
15
+ "webserver_demo-webnode", "nfs_client", "ssh"
16
+ ]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,112 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+ require CLUSTER_CHEF_DIR("lib/cluster_chef")
3
+
4
+ describe ClusterChef::Server do
5
+ include_context 'dummy_chef'
6
+
7
+ ClusterChef::Server.class_eval do
8
+ def chef_node
9
+ Chef::Node.new
10
+ end
11
+ end
12
+
13
+ ClusterChef::DryRunnable.class_eval do
14
+ def unless_dry_run
15
+ puts "Not doing that"
16
+ end
17
+ end
18
+
19
+ before do
20
+ ClusterChef::Server.stub!(:chef_node).and_return( "HI" )
21
+ Chef::Config.stub!(:validation_key).and_return("I_AM_VALID")
22
+
23
+ foo = ClusterChef::Server.new(ClusterChef::Facet.new(ClusterChef::Cluster.new('hi'),'there'),0)
24
+ puts foo.inspect
25
+ puts foo.chef_node
26
+ @cluster = get_example_cluster('webserver_demo')
27
+ @cluster.resolve!
28
+ @facet = @cluster.facet(:dbnode)
29
+ @server = @facet.server(0)
30
+ end
31
+
32
+ describe 'volumes' do
33
+ describe '#composite_volumes' do
34
+ it 'assembles cluster, facet and server volumes' do
35
+ @server.composite_volumes.length.should == 5
36
+ @cluster.volumes.length.should == 4
37
+ @facet.volumes.length.should == 1
38
+ @server.volumes.length.should == 1
39
+ end
40
+
41
+ it 'composites server attributes onto a volume defined in the facet' do
42
+ vol = @server.composite_volumes[:data]
43
+ vol.to_hash.should == {
44
+ :name => :data,
45
+ :tags => {},
46
+ :snapshot_id => "snap-d9c1edb1",
47
+ :size => 50,
48
+ :keep => true,
49
+ :device => "/dev/sdi",
50
+ :mount_point => "/data/db",
51
+ :mount_options => "defaults,nouuid,noatime",
52
+ :fs_type => "xfs",
53
+ :availability_zone => "us-east-1d"
54
+ }
55
+ end
56
+
57
+ it 'makes block_device_mapping for non-ephemeral storage' do
58
+ vol = @server.composite_volumes[:data]
59
+ vol.block_device_mapping.should == {
60
+ "DeviceName" => "/dev/sdi",
61
+ "Ebs.SnapshotId" => "snap-d9c1edb1",
62
+ "Ebs.VolumeSize" => 50,
63
+ "Ebs.DeleteOnTermination" => "false"
64
+ }
65
+ end
66
+
67
+ it 'skips block_device_mapping for non-ephemeral storage if volume id is present' do
68
+ vol = @facet.server(1).composite_volumes[:data]
69
+ vol.block_device_mapping.should be_nil
70
+ end
71
+
72
+ end
73
+ end
74
+
75
+ describe 'launch' do
76
+ describe '#fog_description_for_launch' do
77
+ it 'has right attributes' do
78
+
79
+ hsh = @server.fog_description_for_launch
80
+ hsh.delete(:user_data)
81
+ hsh.should == {
82
+ :image_id => "ami-08f40561",
83
+ :flavor_id => "m1.large",
84
+ :groups => ["webserver_demo-redis_client", "webserver_demo-dbnode", "default", "ssh", "nfs_client", "webserver_demo"],
85
+ :key_name => :webserver_demo,
86
+ :tags => {:cluster=>:webserver_demo, :facet=>:dbnode, :index=>0},
87
+ :block_device_mapping => [
88
+ {"DeviceName"=>"/dev/sdi", "Ebs.SnapshotId"=>"snap-d9c1edb1", "Ebs.VolumeSize"=>50, "Ebs.DeleteOnTermination"=>"false"},
89
+ {"DeviceName"=>"/dev/sdb", "VirtualName"=>"ephemeral0"},
90
+ {"DeviceName"=>"/dev/sdc", "VirtualName"=>"ephemeral1"},
91
+ {"DeviceName"=>"/dev/sdd", "VirtualName"=>"ephemeral2"},
92
+ {"DeviceName"=>"/dev/sde", "VirtualName"=>"ephemeral3"},
93
+ ],
94
+ :availability_zone => "us-east-1d",
95
+ :monitoring => nil
96
+ }
97
+ end
98
+
99
+ it 'has right user_data' do
100
+ hsh = @server.fog_description_for_launch
101
+ user_data_hsh = JSON.parse( hsh[:user_data] )
102
+ user_data_hsh.keys.should == ["chef_server", "validation_client_name", "validation_key", "attributes"]
103
+ user_data_hsh["attributes"].keys.sort.should == [
104
+ "cluster_name", "facet_name", "facet_index",
105
+ "node_name",
106
+ "webnode_count",
107
+ ]
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,193 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require CLUSTER_CHEF_DIR("lib/cluster_chef")
4
+
5
+ describe "cluster_chef" do
6
+ describe 'successfuly runs example' do
7
+
8
+ describe 'webserver_demo:' do
9
+ before :all do
10
+ @cluster = get_example_cluster(:webserver_demo)
11
+ @cluster.resolve!
12
+ end
13
+
14
+ it 'loads successfuly' do
15
+ @cluster.should be_a(ClusterChef::Cluster)
16
+ @cluster.name.should == :webserver_demo
17
+ end
18
+
19
+ it 'cluster is right' do
20
+ @cluster.to_hash.should == {
21
+ :name => :webserver_demo,
22
+ :run_list => ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]"],
23
+ :chef_attributes => { :webnode_count => 6 },
24
+ :facet_name => "webserver_demo_cluster",
25
+ }
26
+ end
27
+
28
+ it 'defaults cluster' do
29
+ defaults_cluster = ClusterChef.cluster(:defaults)
30
+ cloud_hash = defaults_cluster.cloud.to_hash
31
+ [:security_groups, :user_data].each{|k| cloud_hash.delete k }
32
+ cloud_hash.should == {
33
+ :availability_zones => ['us-east-1d'],
34
+ :region => "us-east-1",
35
+ :flavor => "m1.small",
36
+ :image_name => "lucid",
37
+ :backing => "ebs",
38
+ :disable_api_termination => false,
39
+ :public_ip => false,
40
+ :bootstrap_distro => "ubuntu10.04-cluster_chef",
41
+ }
42
+ end
43
+
44
+ it 'cluster cloud is right' do
45
+ cloud_hash = @cluster.cloud.to_hash
46
+ [:security_groups, :user_data].each{|k| cloud_hash.delete k }
47
+ cloud_hash.should == {
48
+ :availability_zones => ['us-east-1d'],
49
+ :region => "us-east-1",
50
+ :flavor => "t1.micro",
51
+ :image_name => "maverick",
52
+ :backing => "instance",
53
+ :disable_api_termination => false,
54
+ :public_ip => false,
55
+ :bootstrap_distro => "ubuntu10.04-cluster_chef",
56
+ :keypair => :webserver_demo,
57
+ }
58
+ end
59
+
60
+ it 'facet cloud is right' do
61
+ cloud_hash = @cluster.facet(:webnode).cloud.to_hash
62
+ [:security_groups, :user_data].each{|k| cloud_hash.delete k }
63
+ cloud_hash.should == {
64
+ :backing => "ebs",
65
+ }
66
+ end
67
+
68
+ it 'webnode facets are right' do
69
+ @cluster.facets.length.should == 3
70
+ fct = @cluster.facet(:webnode)
71
+ fct.to_hash.should == {
72
+ :name => :webnode,
73
+ :run_list => ["role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"],
74
+ :chef_attributes => {:split_testing=>{:group=>"A"}},
75
+ :facet_role => "webserver_demo_webnode",
76
+ :instances => 6,
77
+ }
78
+ end
79
+
80
+ it 'dbnode facets are right' do
81
+ fct = @cluster.facet(:dbnode)
82
+ fct.to_hash.should == {
83
+ :name => :dbnode,
84
+ :run_list => ["role[mysql_server]", "role[redis_client]", "role[webserver_demo_dbnode]" ],
85
+ :chef_attributes => {},
86
+ :facet_role => "webserver_demo_dbnode",
87
+ :instances => 2,
88
+ }
89
+ fct.cloud.flavor.should == 'c1.xlarge'
90
+ fct.server(0).cloud.flavor.should == 'm1.large'
91
+ end
92
+
93
+ it 'esnode facets are right' do
94
+ fct = @cluster.facet(:esnode)
95
+ fct.to_hash.should == {
96
+ :name => :esnode,
97
+ :run_list => ["role[nginx]", "role[redis_server]", "role[elasticsearch_data_esnode]", "role[elasticsearch_http_esnode]", "role[webserver_demo_esnode]"],
98
+ :chef_attributes => {},
99
+ :facet_role => "webserver_demo_esnode",
100
+ :instances => 1,
101
+ }
102
+ fct.cloud.flavor.should == 'm1.large'
103
+ end
104
+
105
+ it 'cluster security groups are right' do
106
+ gg = @cluster.security_groups
107
+ gg.keys.should == ['default', 'ssh', 'nfs_client', 'webserver_demo']
108
+ end
109
+
110
+ it 'facet webnode security groups are right' do
111
+ gg = @cluster.facet(:webnode).security_groups
112
+ gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-awesome_website", "webserver_demo-redis_client", "webserver_demo-webnode", "nfs_client", "ssh"]
113
+ gg['webserver_demo-awesome_website'].range_authorizations.should == [[80..80, "0.0.0.0/0", "tcp"], [443..443, "0.0.0.0/0", "tcp"]]
114
+ end
115
+
116
+ it 'facet dbnode security groups are right' do
117
+ gg = @cluster.facet(:dbnode).security_groups
118
+ gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-dbnode", "webserver_demo-redis_client", "nfs_client", "ssh"]
119
+ end
120
+
121
+ it 'facet esnode security groups are right' do
122
+ gg = @cluster.facet(:esnode).security_groups
123
+ gg.keys.sort.should == ["default", "webserver_demo", "webserver_demo-esnode", "webserver_demo-redis_server", "nfs_client", "ssh"]
124
+ gg['webserver_demo-redis_server'][:name].should == "webserver_demo-redis_server"
125
+ gg['webserver_demo-redis_server'][:description].should == "cluster_chef generated group webserver_demo-redis_server"
126
+ gg['webserver_demo-redis_server'].group_authorizations.should == [['webserver_demo-redis_client', nil]]
127
+ end
128
+
129
+ it 'has servers' do
130
+ @cluster.servers.map(&:fullname).should == [
131
+ "webserver_demo-dbnode-0", "webserver_demo-dbnode-1",
132
+ "webserver_demo-esnode-0",
133
+ "webserver_demo-webnode-0", "webserver_demo-webnode-1", "webserver_demo-webnode-2", "webserver_demo-webnode-3", "webserver_demo-webnode-4", "webserver_demo-webnode-5"
134
+ ]
135
+ end
136
+
137
+ describe 'resolving servers gets right' do
138
+ before do
139
+ @server = @cluster.slice(:webnode, 5).first
140
+ @server.cloud.stub!(:validation_key).and_return("I_AM_VALID")
141
+ @server.resolve!
142
+ end
143
+
144
+ it 'attributes' do
145
+ @server.to_hash.should == {
146
+ :name => 'webserver_demo-webnode-5',
147
+ :run_list => ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]", "role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"],
148
+ :instances => 6,
149
+ :chef_attributes => {
150
+ :split_testing => {:group=>"B"},
151
+ :webnode_count => 6,
152
+ :node_name => "webserver_demo-webnode-5",
153
+ :cluster_name => :webserver_demo, :facet_name => :webnode, :facet_index => 5,
154
+ },
155
+ }
156
+ end
157
+
158
+ it 'security groups' do
159
+ @server.security_groups.keys.sort.should == ['default', 'webserver_demo', 'webserver_demo-awesome_website', 'webserver_demo-redis_client', 'webserver_demo-webnode', 'nfs_client', 'ssh']
160
+ end
161
+ it 'run list' do
162
+ @server.run_list.should == ["role[base_role]", "role[chef_client]", "role[ssh]", "role[nfs_client]", "role[big_package]", "role[webserver_demo_cluster]", "role[nginx]", "role[redis_client]", "role[mysql_client]", "role[elasticsearch_client]", "role[awesome_website]", "role[webserver_demo_webnode]"]
163
+ end
164
+
165
+ it 'user_data' do
166
+ @server.cloud.user_data.should == {
167
+ "chef_server" => "https://api.opscode.com/organizations/infochimps",
168
+ "validation_client_name" => "chef-validator",
169
+ "validation_key" => "I_AM_VALID",
170
+ }
171
+ end
172
+
173
+ it 'cloud settings' do
174
+ hsh = @server.cloud.to_hash
175
+ hsh.delete(:security_groups)
176
+ hsh.delete(:user_data)
177
+ hsh.should == {
178
+ :availability_zones => ["us-east-1c"],
179
+ :region => "us-east-1",
180
+ :flavor => "t1.micro",
181
+ :image_name => "maverick",
182
+ :backing => "ebs",
183
+ :disable_api_termination => false,
184
+ :public_ip => false,
185
+ :bootstrap_distro => "ubuntu10.04-cluster_chef",
186
+ :keypair => :webserver_demo,
187
+ }
188
+ end
189
+
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,25 @@
1
+ shared_context 'dummy_chef' do
2
+ before(:each) do
3
+ Chef::Log.logger = Logger.new(StringIO.new)
4
+
5
+ Chef::Config[:node_name] = "webmonkey.example.com"
6
+ ClusterChef.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
7
+ ClusterChef.ui.stub!(:puts)
8
+ ClusterChef.ui.stub!(:print)
9
+ Chef::Log.stub!(:init)
10
+ Chef::Log.stub!(:level)
11
+ [:debug, :info, :warn, :error, :crit].each do |level_sym|
12
+ Chef::Log.stub!(level_sym)
13
+ end
14
+ Chef::Knife.stub!(:puts)
15
+ @stdout = StringIO.new
16
+ end
17
+
18
+
19
+ let(:node_name){ 'a_dummy_node' }
20
+ let(:dummy_node){ Chef::Node.new }
21
+ before(:each) do
22
+ # ClusterChef::Cluster.stub!(:chef_nodes).and_return( [dummy_node] )
23
+ ClusterChef::Server.stub!(:chef_node).and_return( dummy_node )
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ require 'rubygems' unless defined?(Gem)
2
+ require 'bundler'
3
+ # begin
4
+ # Bundler.setup(:default, :development)
5
+ # rescue Bundler::BundlerError => e
6
+ # $stderr.puts e.message
7
+ # $stderr.puts "Run `bundle install` to install missing gems"
8
+ # exit e.status_code
9
+ # end
10
+ require 'spork'
11
+
12
+ unless defined?(CLUSTER_CHEF_DIR)
13
+ CLUSTER_CHEF_DIR = File.expand_path(File.dirname(__FILE__)+'/..')
14
+ def CLUSTER_CHEF_DIR(*paths) File.join(CLUSTER_CHEF_DIR, *paths); end
15
+ # load from vendored libraries, if present
16
+ Dir[CLUSTER_CHEF_DIR("vendor/*/lib")].each{|dir| p dir ; $LOAD_PATH.unshift(File.expand_path(dir)) } ; $LOAD_PATH.uniq!
17
+ end
18
+
19
+ Spork.prefork do # This code is run only once when the spork server is started
20
+
21
+ require 'rspec'
22
+ require 'chef'
23
+ require 'chef/knife'
24
+ require 'fog'
25
+
26
+ Fog.mock!
27
+ Fog::Mock.delay = 0
28
+
29
+ CHEF_CONFIG_FILE = File.expand_path(CLUSTER_CHEF_DIR('spec/test_config.rb')) unless defined?(CHEF_CONFIG_FILE)
30
+ Chef::Config.from_file(CHEF_CONFIG_FILE)
31
+
32
+ # Requires custom matchers & macros, etc from files in ./spec_helper/
33
+ Dir[CLUSTER_CHEF_DIR("spec/spec_helper/*.rb")].each {|f| require f}
34
+
35
+ def load_example_cluster(name)
36
+ require(CLUSTER_CHEF_DIR('clusters', "#{name}.rb"))
37
+ end
38
+ def get_example_cluster name
39
+ load_example_cluster(name)
40
+ ClusterChef.cluster(name)
41
+ end
42
+
43
+ # Configure rspec
44
+ RSpec.configure do |config|
45
+ end
46
+ end
47
+
48
+ Spork.each_run do
49
+ # This code will be run each time you run your specs.
50
+ end
@@ -0,0 +1,20 @@
1
+ current_dir = File.expand_path('~/.chef')
2
+ organization = 'infochimps'
3
+ username = 'mrflip'
4
+
5
+ cookbook_root = ENV['PATH_TO_COOKBOOK_REPOS'] || File.expand_path('~/ics/sysadmin')
6
+
7
+ cluster_chef_path File.expand_path(cookbook_root+'/cluster_chef')
8
+ keypair_path File.expand_path(current_dir+"/keypairs")
9
+ cookbook_path [
10
+ "cluster_chef/cookbooks", "cluster_chef/site-cookbooks",
11
+ ].map{|path| File.join(cookbook_root, path) }
12
+ cluster_path [
13
+ 'cluster_chef/clusters',
14
+ ].map{|path| File.join(cookbook_root, path) }
15
+
16
+ node_name username
17
+ validation_client_name "chef-validator"
18
+ validation_key "#{keypair_path}/#{organization}-validator.pem"
19
+ client_key "#{keypair_path}/#{username}-client_key.pem"
20
+ chef_server_url "https://api.opscode.com/organizations/#{organization}"
@@ -0,0 +1,38 @@
1
+ # Configure the Rakefile's tasks.
2
+
3
+ ###
4
+ # Company and SSL Details
5
+ # Used with the ssl_cert task.
6
+ ###
7
+
8
+ # The company name - used for SSL certificates, and in srvious other places
9
+ COMPANY_NAME = "Infochimps, Inc"
10
+
11
+ # The Country Name to use for SSL Certificates
12
+ SSL_COUNTRY_NAME = "US"
13
+
14
+ # The State Name to use for SSL Certificates
15
+ SSL_STATE_NAME = "Several"
16
+
17
+ # The Locality Name for SSL - typically, the city
18
+ SSL_LOCALITY_NAME = "Locality"
19
+
20
+ # What department?
21
+ SSL_ORGANIZATIONAL_UNIT_NAME = "Operations"
22
+
23
+ # The SSL contact email address
24
+ SSL_EMAIL_ADDRESS = "coders@infochimps.com"
25
+
26
+ # License for new Cookbooks
27
+ # Can be :apachev2 or :none
28
+ NEW_COOKBOOK_LICENSE = :apachev2
29
+
30
+ ###
31
+ # Useful Extras (which you probably don't need to change)
32
+ ###
33
+
34
+ # The top of the repository checkout
35
+ TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), ".."))
36
+
37
+ # Where to store certificates generated with ssl_cert
38
+ CADIR = File.expand_path(File.join(TOPDIR, "certificates"))
@@ -0,0 +1,47 @@
1
+
2
+ #
3
+ # Jeweler has hardcoded the 'master' branch as where to push from.
4
+ # We hardcode it right back in.
5
+ #
6
+
7
+ module Jeweler::Commands
8
+
9
+ PUSH_FROM_BRANCH = 'version_3' unless defined?(PUSH_FROM_BRANCH)
10
+
11
+ ReleaseToGit.class_eval do
12
+ def run
13
+ unless clean_staging_area?
14
+ system "git status"
15
+ raise "Unclean staging area! Be sure to commit or .gitignore everything first. See `git status` above."
16
+ end
17
+
18
+ repo.checkout(PUSH_FROM_BRANCH)
19
+ repo.push
20
+
21
+ if release_not_tagged?
22
+ output.puts "Tagging #{release_tag}"
23
+ repo.add_tag(release_tag)
24
+
25
+ output.puts "Pushing #{release_tag} to origin"
26
+ repo.push('origin', release_tag)
27
+ end
28
+ end
29
+ end
30
+
31
+ ReleaseGemspec.class_eval do
32
+ def run
33
+ unless clean_staging_area?
34
+ system "git status"
35
+ raise "Unclean staging area! Be sure to commit or .gitignore everything first. See `git status` above."
36
+ end
37
+
38
+ repo.checkout(PUSH_FROM_BRANCH)
39
+
40
+ regenerate_gemspec!
41
+ commit_gemspec! if gemspec_changed?
42
+
43
+ output.puts "Pushing #{PUSH_FROM_BRANCH} to origin"
44
+ repo.push
45
+ end
46
+ end
47
+ end