cluster_chef-knife 3.0.5

Sign up to get free protection for your applications and to get access to all the features.
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