Addons v2 #73

Closed
wants to merge 5 commits into
from
@@ -7,6 +7,11 @@ Feature: Works with Heroku
And I run `bundle install`
Scenario: Remote commands
+ When I run `rake demo heroku:addons`
+ Then the output should contain "* logging:basic\n"
+ And the output should contain "* deployhooks:campfire"
+ And the output should contain "Addon deployhooks:campfire needs to be configured at https://api.heroku.com/myapps/heroku-san-demo-demo/addons/deployhooks:campfire"
+
When I run `rake demo deploy`
Then the output should match /(http:.*-demo.heroku.com deployed to Heroku)|(Everything up-to-date)/
View
@@ -2,8 +2,9 @@
require 'git'
require 'heroku_san/stage'
require 'heroku_san/project'
+require 'heroku_san/addons'
module HerokuSan
class NoApps < StandardError; end
class Deprecated < StandardError; end
-end
+end
@@ -0,0 +1,50 @@
+module HerokuSan
+ class Addons
+ def initialize stage
+ @stage = stage
+ end
+
+ attr_reader :stage
+
+ # Addons that are in the heroku_san configuration, but not installed.
+ # => ['not:installed', 'also:not']
+ def needed
+ stage.addons - installed
+ end
+
+ # A list of addons that are installed, but not broken.
+ # => ['addon:name', 'another:addon', 'broken:name', 'another:broken']
+ def installed
+ all_installed.collect { |x| x.first }
+ end
+
+ # A list of addons that are not configured correctly, with a URL where the problem can be inspected and/or fixed.
+ # => [['broken:name', 'http://heroku.com/blah/blah'], ['another:broken', 'http://heroku.com/blah/blah/again']]
+ def broken
+ all_installed.select { |x| x.length == 2 }
+ end
+
+ def refresh
+ @caches = nil
+ end
+
+ private
+ def all_installed
+ caches[:all_installed] ||=
+ begin
+ `heroku addons --app #{stage.app}`.lines.collect do |line|
+ line = line.chomp
+ if line.empty? || line =~ /^-/
+ nil
+ else
+ line.split /\s+/
+ end
+ end.compact
+ end
+ end
+
+ def caches
+ @caches ||= {}
+ end
+ end
+end
@@ -26,6 +26,10 @@ def tag
def config
@options['config'] ||= {}
end
+
+ def addons
+ (@options['addons'] ||= []).flatten
+ end
def run(command, args = nil)
if stack =~ /cedar/
@@ -75,4 +79,4 @@ def sh_heroku command
sh "heroku #{command} --app #{app}"
end
end
-end
+end
@@ -1,3 +1,3 @@
module HerokuSan
- VERSION = "2.0.0.rc.1"
+ VERSION = "2.0.0"
end
View
@@ -106,6 +106,26 @@
end
end
+ desc 'Set up addons for the application'
+ task :addons do
+ each_heroku_app do |stage|
+ addons = HerokuSan::Addons.new stage
+ if addons.needed.any?
+ addons.needed.each do |addon|
+ sh "heroku addons:add --app #{stage.app} #{addon}" rescue nil
+ end
+ addons.refresh
+ end
+ puts "Installed addons:"
+ addons.installed.each do |name|
+ puts "* #{name}"
+ end
+ addons.broken.each do |name, url|
+ puts "Addon #{name} needs to be configured at #{url.sub('http://heroku', 'https://api.heroku')}"
+ end
+ end
+ end
+
desc 'Creates an example configuration file'
task :create_config do
filename = %Q{#{@heroku_san.config_file.to_s}}
@@ -281,4 +301,4 @@ def each_heroku_app(&block)
rake all heroku:share"
exit(1)
-end
+end
@@ -8,6 +8,19 @@
# repo: <git repository, optional>
# config:
# - <Heroku config:var name>: <Heroku config:var value>
+# addons:
+# - addon:name
+# - another:addon
+#
+# To share addons:
+#
+# env1:
+# addons: &addons
+# - an:addon
+# env2:
+# addons:
+# - *addons
+# - another:addon
#
production:
app: awesomeapp
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe HerokuSan::Addons do
+ subject { described_class.new stage }
+ let(:stage) { mock('stage', :app => 'awesomeapp') }
+
+ context '#needed' do
+ it 'should list configured addons that are not installed' do
+ subject.should_receive(:installed).and_return(['installed:addon'])
+ stage.should_receive(:addons).and_return(['installed:addon', 'other:addon'])
+ subject.needed.should == ['other:addon']
+ end
+ end
+
+ context do
+ before { subject.should_receive(:`).with("heroku addons --app awesomeapp").and_return(addons_response) }
+ let(:addons_response) { <<END_OUTPUT }
+one:addon
+
+--- not configured ---
+two:addon http://heroku.com/myapps/awesomeapp/addons/two:addon
+END_OUTPUT
+
+ it 'should list addons that are installed in the app' do
+ subject.installed.should == ['one:addon', 'two:addon']
+ end
+
+ it 'should list addons that are installed in the app and that need configuration' do
+ subject.broken.should == [ ['two:addon', 'http://heroku.com/myapps/awesomeapp/addons/two:addon'] ]
+ end
+ end
+end
@@ -8,15 +8,37 @@
{"stack" => "cedar",
"app" => "awesomeapp-demo",
"tag" => "demo/*",
- "config"=> {"BUNDLE_WITHOUT"=>"development:test"}
+ "config"=> {"BUNDLE_WITHOUT"=>"development:test"},
+ "addons"=> addons,
})}
+ let(:addons) { ['one:addon', 'two:addons'] }
its(:name) { should == 'production' }
its(:app) { should == 'awesomeapp-demo' }
its(:stack) { should == 'cedar' }
its(:tag) { should == "demo/*" }
its(:config) { should == {"BUNDLE_WITHOUT"=>"development:test"} }
its(:repo) { should == 'git@heroku.com:awesomeapp-demo.git' }
+ its(:addons) { should == ['one:addon', 'two:addons'] }
+
+ context 'addons' do
+ context 'default' do
+ let(:addons) { nil }
+ its(:addons) { should == [] }
+ end
+ context 'nested' do
+ # This is for when you do:
+ # default_addons: &default_addons
+ # - a
+ # - b
+ # env:
+ # addons:
+ # - *default_addons
+ # - other
+ let(:addons) { [ ['a', 'b'], 'other' ] }
+ its(:addons) { should == [ 'a', 'b', 'other' ] }
+ end
+ end
end
context "celadon cedar stack has a different API" do
@@ -135,4 +157,4 @@
end
end
-end
+end