Skip to content

Commit

Permalink
Use heroku-api for client calls
Browse files Browse the repository at this point in the history
  • Loading branch information
kmayer committed Apr 11, 2012
1 parent 2a303c6 commit 5471893
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Gemfile
@@ -1,4 +1,4 @@
source :rubygems

# Specify dependencies in heroku_san.gemspec
gemspec
gemspec
2 changes: 1 addition & 1 deletion features/step_definitions/remote_steps.rb
Expand Up @@ -112,7 +112,7 @@

When /^I deploy my project$/ do
run_simple 'git init .'
run_simple 'rails generate scaffold droids'
run_simple 'rails generate scaffold droid'
append_to_file 'app/views/droids/index.html.erb', %Q{\n<div><code><%= ENV['DROIDS'] -%></code></div>\n}
run_simple 'git add .'
run_simple 'git commit -m "Initial commit"'
Expand Down
3 changes: 3 additions & 0 deletions heroku_san.gemspec
Expand Up @@ -24,6 +24,7 @@ Gem::Specification.new do |s|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_development_dependency(%q<rails>, ['>= 2'])
s.add_runtime_dependency(%q<heroku>, ['>= 2'])
s.add_runtime_dependency(%q<heroku-api>, ['>= 0.1.2'])
s.add_runtime_dependency(%q<rake>)
s.add_development_dependency(%q<aruba>)
s.add_development_dependency(%q<cucumber>)
Expand All @@ -32,12 +33,14 @@ Gem::Specification.new do |s|
else
s.add_dependency(%q<rails>, ['>= 2'])
s.add_dependency(%q<heroku>, ['>= 2'])
s.add_dependency(%q<heroku-api>, ['>= 0.1.2'])
s.add_dependency(%q<aruba>)
s.add_dependency(%q<cucumber>)
end
else
s.add_dependency(%q<rails>, ['>= 2'])
s.add_dependency(%q<heroku>, ['>= 2'])
s.add_dependency(%q<heroku-api>, ['>= 0.1.2'])
s.add_dependency(%q<aruba>)
s.add_dependency(%q<cucumber>)
end
Expand Down
45 changes: 32 additions & 13 deletions lib/heroku_san/stage.rb
@@ -1,6 +1,9 @@
require 'heroku'
require 'heroku/api'
require 'json'

MOCK = false unless defined?(MOCK)

module HerokuSan
class Stage
attr_reader :name
Expand All @@ -12,7 +15,7 @@ def initialize(stage, options = {})
end

def heroku
Heroku::Auth.client
@heroku ||= Heroku::API.new(:api_key => ENV['HEROKU_API_KEY'] || Heroku::Auth.api_key, :mock => MOCK)
end

def app
Expand All @@ -24,7 +27,7 @@ def repo
end

def stack
@options['stack'] ||= heroku.list_stacks(app).detect{|stack| stack['current']}['name']
@options['stack'] ||= heroku.get_stack(app).body.detect{|stack| stack['current']}['name']
end

def tag
Expand Down Expand Up @@ -60,24 +63,23 @@ def rake(*args)

def maintenance(action = nil)
if block_given?
heroku.maintenance(app, :on)
heroku.post_app_maintenance(app, '1')
begin
yield
ensure
heroku.maintenance(app, :off)
heroku.post_app_maintenance(app, '0')
end
else
raise ArgumentError, "Action #{action.inspect} must be one of (:on, :off)", caller if ![:on, :off].include?(action)
heroku.maintenance(app, action)
heroku.post_app_maintenance(app, {:on => '1', :off => '0'}[action])
end
end

def create # DEPREC?
if @options['stack']
heroku.create(@options['app'], {:stack => @options['stack']})
else
heroku.create(@options['app'])
end
params = @options.select{|k,v| %w[app stack].include? k}.stringify_keys
params['name'] = params.delete('app')
response = heroku.post_app(params)
response.body['name']
end

def sharing_add(email) # DEPREC?
Expand All @@ -89,15 +91,16 @@ def sharing_remove(email) # DEPREC?
end

def long_config
heroku.config_vars(app)
heroku.get_config_vars(app).body
end

def push_config(options = nil)
JSON.parse(heroku.add_config_vars(app, options || config))
params = (options || config).stringify_keys
heroku.put_config_vars(app, params).body
end

def restart
heroku.ps_restart(app)
"restarted" if heroku.post_ps_restart(app).body == 'ok'
end

def logs(tail = false)
Expand All @@ -114,4 +117,20 @@ def sh_heroku(command)
sh "heroku #{command} --app #{app}"
end
end
end

# from ActiveSupport
class Hash
# Return a new hash with all keys converted to strings.
def stringify_keys
dup.stringify_keys!
end

# Destructively convert all keys to strings.
def stringify_keys!
keys.each do |key|
self[key.to_s] = delete(key)
end
self
end
end
101 changes: 58 additions & 43 deletions spec/heroku_san/stage_spec.rb
Expand Up @@ -4,10 +4,9 @@
describe HerokuSan::Stage do
include Git
subject { HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "bamboo-ree-1.8.7"})}

STOCK_CONFIG = {"BUNDLE_WITHOUT"=>"development:test", "LANG"=>"en_US.UTF-8", "RACK_ENV"=>"production"}
before do
@heroku_client = mock(Heroku::Client)
Heroku::Auth.stub(:client).and_return(@heroku_client)
Heroku::Auth.stub(:api_key) { 'API_KEY' }
end

context "initializes" do
Expand Down Expand Up @@ -39,9 +38,9 @@
describe "#stack" do
it "returns the name of the stack from Heroku" do
subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp"})
@heroku_client.should_receive(:list_stacks).with('awesomeapp').
and_return { [{'name' => 'other'}, {'name' => 'the-one', 'current' => true}] }
subject.stack.should == 'the-one'
with_app(subject, 'name' => 'awesomeapp') do |app_data|
subject.stack.should == 'bamboo-mri-1.9.2'
end
end

it "returns the stack name from the config if it is set there" do
Expand Down Expand Up @@ -86,22 +85,24 @@

describe "#migrate" do
it "runs rake db:migrate" do
subject.should_receive(:rake).with('db:migrate').and_return 'output:'
# @heroku_client.should_receive(:rake).with('awesomeapp', 'db:migrate').and_return "output:"
@heroku_client.should_receive(:ps_restart).with('awesomeapp').and_return "restarted"
subject.migrate.should == "restarted"
with_app(subject, 'name' => subject.app) do |app_data|
subject.should_receive(:rake).with('db:migrate').and_return 'output:'
subject.migrate.should == "restarted"
end
end
end

describe "#maintenance" do
it ":on" do
@heroku_client.should_receive(:maintenance).with('awesomeapp', :on) {'on'}
subject.maintenance(:on).should == 'on'
with_app(subject, 'name' => subject.app )do |app_data|
subject.maintenance(:on).status.should == 200
end
end

it ":off" do
@heroku_client.should_receive(:maintenance).with('awesomeapp', :off) {'off'}
subject.maintenance(:off).should == 'off'
with_app(subject, 'name' => subject.app) do |app_data|
subject.maintenance(:off).status.should.should == 200
end
end

it "otherwise raises an ArgumentError" do
Expand All @@ -112,36 +113,45 @@

context "with a block" do
it "wraps it in a maitenance mode" do
@heroku_client.should_receive(:maintenance).with('awesomeapp', :on).ordered
reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).ordered
@heroku_client.should_receive(:maintenance).with('awesomeapp', :off).ordered
subject.maintenance do reactor.scram(:now) end
with_app(subject, 'name' => subject.app) do |app_data|
subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '1').ordered
reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).ordered
subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '0').ordered

subject.maintenance {reactor.scram(:now)}
end
end
it "ensures that maintenance mode is turned off" do
@heroku_client.should_receive(:maintenance).with('awesomeapp', :on).ordered
reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).and_raise(RuntimeError)
@heroku_client.should_receive(:maintenance).with('awesomeapp', :off).ordered
expect {
subject.maintenance do reactor.scram(:now) end
}.to raise_error
with_app(subject, 'name' => subject.app) do |app_data|
subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '1').ordered
reactor = mock("Reactor"); reactor.should_receive(:scram).and_raise(RuntimeError)
subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '0').ordered

expect do subject.maintenance {reactor.scram(:now)} end.to raise_error
end
end
end
end

describe "#create" do
after do
subject.heroku.delete_app(@app)
end
it "uses the provided name" do
(@app = subject.create).should == 'awesomeapp'
end
it "creates an app on heroku" do
@heroku_client.should_receive(:create).with('awesomeapp', {:stack => 'bamboo-ree-1.8.7'})
subject.create
subject = HerokuSan::Stage.new('production')
(@app = subject.create).should =~ /generated-name-\d+/
end
it "uses the default stack if none is given" do
subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp"})
@heroku_client.should_receive(:create).with('awesomeapp')
subject.create
end
it "sends a nil app name if none is given (Heroku will generate one)" do
subject = HerokuSan::Stage.new('production', {"app" => nil})
@heroku_client.should_receive(:create).with(nil).and_return('warm-ocean-9218')
subject.create.should == 'warm-ocean-9218'
subject = HerokuSan::Stage.new('production')
(@app = subject.create).should =~ /generated-name-\d+/
subject.heroku.get_stack(@app).body.detect{|stack| stack['current']}['name'].should == 'bamboo-mri-1.9.2'
end
it "uses the stack from the config" do
(@app = subject.create).should == 'awesomeapp'
subject.heroku.get_stack(@app).body.detect{|stack| stack['current']}['name'].should == 'bamboo-ree-1.8.7'
end
end

Expand All @@ -161,15 +171,17 @@

describe "#long_config" do
it "returns the remote config" do
@heroku_client.should_receive(:config_vars).with('awesomeapp') { {'A' => 'one', 'B' => 'two'} }
subject.long_config.should == { 'A' => 'one', 'B' => 'two' }
with_app(subject, 'name' => subject.app) do |app_data|
subject.long_config.should == STOCK_CONFIG
end
end
end

describe "#restart" do
it "restarts an app" do
@heroku_client.should_receive(:ps_restart).with('awesomeapp').and_return "restarted"
subject.restart.should == 'restarted'
with_app(subject, 'name' => subject.app) do |app_data|
subject.restart.should == 'restarted'
end
end
end

Expand All @@ -186,13 +198,16 @@

describe "#push_config" do
it "updates the configuration settings on Heroku" do
subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {:FOO => 'bar', :DOG => 'emu'}})
@heroku_client.should_receive(:add_config_vars).with('awesomeapp', {:FOO => 'bar', :DOG => 'emu'}).and_return("{}")
subject.push_config
subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {:FOO => 'bar', :DOG => 'emu'}})
with_app(subject, 'name' => subject.app) do |app_data|
subject.push_config.should == STOCK_CONFIG.merge('FOO' => 'bar', 'DOG' => 'emu')
end
end
it "pushes the options hash" do
@heroku_client.should_receive(:add_config_vars).with('awesomeapp', {:RACK_ENV => 'magic'}).and_return("{}")
subject.push_config(:RACK_ENV => 'magic')
subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {:FOO => 'bar', :DOG => 'emu'}})
with_app(subject, 'name' => subject.app) do |app_data|
subject.push_config(:RACK_ENV => 'magic').should == STOCK_CONFIG.merge('RACK_ENV' => 'magic')
end
end
end

Expand Down
2 changes: 2 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -3,6 +3,8 @@

SPEC_ROOT = File.dirname(__FILE__)

MOCK = ENV['MOCK'] != 'false'

# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[File.join(SPEC_ROOT, "support/**/*.rb")].each {|f| require f}
Expand Down
21 changes: 21 additions & 0 deletions spec/support/heroku.rb
@@ -0,0 +1,21 @@
def random_name
"heroku-rb-#{SecureRandom.hex(10)}"
end

def random_email_address
"email@#{random_name}.com"
end

def with_app(subject, params={}, &block)
begin
data = subject.heroku.post_app(params).body
@name = data['name']
ready = false
until ready
ready = subject.heroku.request(:method => :put, :path => "/apps/#{@name}/status").status == 201
end
yield(data)
ensure
subject.heroku.delete_app(@name) rescue nil
end
end

0 comments on commit 5471893

Please sign in to comment.