heroku_san 4.0.8 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,4 +1,13 @@
1
- # Change log (curated)
1
+ # Change Log (curated)
2
+
3
+ ## v4.2.0
4
+
5
+ * Wrap API calls so that errors are reported in a more human-fiendly way
6
+ * Closes #105
7
+
8
+ ## v4.1.0
9
+
10
+ * Extracted `Parser` and `Configuration` classes from `Project`. This *should* be a feature-neutral version change. There are a few API additions, but they should not be needed by the general `heroku_san` population.
2
11
 
3
12
  ## v4.0.8
4
13
 
@@ -62,7 +71,7 @@
62
71
 
63
72
  ## v2.1.4
64
73
 
65
- * Use heroku-api for client calls instead heroku gem
74
+ * Use heroku-api for client calls instead of heroku gem
66
75
 
67
76
  ## v2.1.3
68
77
 
@@ -0,0 +1,17 @@
1
+ module HerokuSan
2
+ class API
3
+ def initialize(options = {})
4
+ @heroku_api = Heroku::API.new(options)
5
+ end
6
+
7
+ def method_missing(name, *args)
8
+ @heroku_api.send(name, *args)
9
+ rescue Heroku::API::Errors::ErrorWithResponse => error
10
+ status = error.response.headers["Status"]
11
+ msg = JSON.parse(error.response.body)['error'] rescue '???'
12
+ error.set_backtrace([])
13
+ $stderr.puts "\nHeroku API ERROR: #{status} (#{msg})\n\n"
14
+ raise error
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,45 @@
1
+ module HerokuSan
2
+ class Configuration
3
+ attr_reader :config_file
4
+ attr_accessor :configuration
5
+ attr_reader :options
6
+
7
+ def initialize(configurable)
8
+ @config_file = configurable.config_file
9
+ default_options = {
10
+ 'deploy' => HerokuSan::Deploy::Rails
11
+ }
12
+ @options = default_options.merge(configurable.options || {})
13
+ end
14
+
15
+ def parse
16
+ HerokuSan::Parser.new.parse(self)
17
+ end
18
+
19
+ def stages
20
+ configured? or parse
21
+ configuration.inject({}) do |stages, (stage, settings)|
22
+ stages[stage] = HerokuSan::Stage.new(stage, settings.merge('deploy' => (options[:deploy]||options['deploy'])))
23
+ stages
24
+ end
25
+ end
26
+
27
+ def configured?
28
+ !!configuration
29
+ end
30
+
31
+ def template
32
+ File.expand_path(File.join(File.dirname(__FILE__), '../templates', 'heroku.example.yml'))
33
+ end
34
+
35
+ def generate_config
36
+ # TODO: Convert true/false returns to success/exception
37
+ if File.exists?(config_file)
38
+ false
39
+ else
40
+ FileUtils.cp(template, config_file)
41
+ true
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,57 @@
1
+ module HerokuSan
2
+ class Parser
3
+ include Git
4
+ attr_accessor :settings
5
+ def parse(parseable)
6
+ @settings = parse_yaml(parseable.config_file)
7
+ convert_from_heroku_san_format
8
+ each_setting_has_a_config_section
9
+ merge_external_config
10
+ parseable.configuration = @settings
11
+ end
12
+
13
+ def convert_from_heroku_san_format
14
+ (settings.delete('apps') || {}).each_pair do |stage, app_name|
15
+ settings[stage] = {'app' => app_name}
16
+ end
17
+ end
18
+
19
+ def each_setting_has_a_config_section
20
+ settings.keys.each do |name|
21
+ settings[name] ||= {}
22
+ settings[name]['config'] ||= {}
23
+ end
24
+ end
25
+
26
+ def merge_external_config
27
+ extra_config = parse_external_config(settings.delete('config_repo'))
28
+ return unless extra_config
29
+ settings.keys.each do |name|
30
+ settings[name]['config'].merge!(extra_config[name]) if extra_config[name]
31
+ end
32
+ end
33
+
34
+ def parse_external_config(config_repo)
35
+ return if config_repo.nil?
36
+ require 'tmpdir'
37
+ Dir.mktmpdir do |dir|
38
+ git_clone config_repo, dir
39
+ parse_yaml File.join(dir, 'config.yml')
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def parse_yaml(config_file)
46
+ if File.exists?(config_file)
47
+ if defined?(ERB)
48
+ YAML.load(ERB.new(File.read(config_file)).result)
49
+ else
50
+ YAML.load_file(config_file)
51
+ end
52
+ else
53
+ {}
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,37 +1,30 @@
1
1
  module HerokuSan
2
2
  class Project
3
- attr_reader :config_file
4
-
5
3
  include Git
6
-
7
- def initialize(config_file, options = {})
8
- @apps = []
4
+ attr_reader :config_file
5
+ attr_reader :options
6
+ attr_writer :configuration
7
+
8
+ def initialize(config_file = nil, options = {})
9
9
  @config_file = config_file
10
- @app_settings = {}
11
- config = parse(@config_file)
12
- config.each do |stage, settings|
13
- # TODO: Push this eval later (j.i.t.)
14
- @app_settings[stage] = HerokuSan::Stage.new(stage, settings.merge('deploy' => (options[:deploy]||options['deploy'])))
15
- end
10
+ @options = options
11
+ @apps = []
12
+ end
13
+
14
+ def configuration
15
+ @configuration ||= HerokuSan::Configuration.new(self).stages
16
16
  end
17
17
 
18
18
  def create_config
19
- # TODO: Convert true/false returns to success/exception
20
- template = File.expand_path(File.join(File.dirname(__FILE__), '../templates', 'heroku.example.yml'))
21
- if File.exists?(@config_file)
22
- false
23
- else
24
- FileUtils.cp(template, @config_file)
25
- true
26
- end
19
+ HerokuSan::Configuration.new(self).generate_config
27
20
  end
28
-
21
+
29
22
  def all
30
- @app_settings.keys
23
+ configuration.keys
31
24
  end
32
25
 
33
26
  def [](stage)
34
- @app_settings[stage]
27
+ configuration[stage]
35
28
  end
36
29
 
37
30
  def <<(*app)
@@ -51,7 +44,7 @@ module HerokuSan
51
44
  else
52
45
  active_branch = self.git_active_branch
53
46
  all.select do |app|
54
- app == active_branch and ($stdout.puts("Defaulting to '#{app}' as it matches the current branch") || true)
47
+ app == active_branch and ($stdout.puts("Defaulting to '#{app}' as it matches the current branch"); true)
55
48
  end
56
49
  end
57
50
  end
@@ -60,54 +53,8 @@ module HerokuSan
60
53
  def each_app
61
54
  raise NoApps if apps.empty?
62
55
  apps.each do |stage|
63
- yield(self[stage])
64
- end
65
- end
66
-
67
- private
68
-
69
- def parse_yaml(config_file)
70
- if File.exists?(config_file)
71
- if defined?(ERB)
72
- YAML.load(ERB.new(File.read(config_file)).result)
73
- else
74
- YAML.load_file(config_file)
75
- end
76
- else
77
- {}
78
- end
79
- end
80
-
81
- def parse(config_file)
82
- app_settings = parse_yaml(config_file)
83
-
84
- # support heroku_san format
85
- if app_settings.has_key? 'apps'
86
- app_settings = app_settings['apps']
87
- app_settings.each_pair do |stage, app_name|
88
- app_settings[stage] = {'app' => app_name}
89
- end
90
- end
91
-
92
- # load external config
93
- if (config_repo = app_settings.delete('config_repo'))
94
- require 'tmpdir'
95
- tmp_config_dir = Dir.mktmpdir
96
- tmp_config_file = File.join tmp_config_dir, 'config.yml'
97
- git_clone(config_repo, tmp_config_dir)
98
- extra_config = parse_yaml(tmp_config_file)
99
- else
100
- extra_config = {}
56
+ yield self[stage]
101
57
  end
102
-
103
- # make sure each app has a 'config' section & merge w/extra
104
- app_settings.keys.each do |name|
105
- app_settings[name] ||= {}
106
- app_settings[name]['config'] ||= {}
107
- app_settings[name]['config'].merge!(extra_config[name]) if extra_config[name]
108
- end
109
-
110
- app_settings
111
58
  end
112
59
  end
113
60
  end
@@ -5,19 +5,21 @@ MOCK = false unless defined?(MOCK)
5
5
 
6
6
  module HerokuSan
7
7
  class Stage
8
- attr_reader :name
9
8
  include Git
10
-
9
+ attr_reader :name
10
+ attr_reader :options
11
+
11
12
  def initialize(stage, options = {})
12
- default_options = {
13
- 'deploy' => HerokuSan::Deploy::Rails
14
- }
15
13
  @name = stage
16
- @options = default_options.merge(options)
14
+ @options = options
15
+ end
16
+
17
+ def ==(other)
18
+ other.name == name && other.options == options
17
19
  end
18
20
 
19
21
  def heroku
20
- @heroku ||= Heroku::API.new(:api_key => auth_token, :mock => MOCK)
22
+ @heroku ||= HerokuSan::API.new(:api_key => auth_token, :mock => MOCK)
21
23
  end
22
24
 
23
25
  def app
@@ -1,3 +1,3 @@
1
1
  module HerokuSan
2
- VERSION = "4.0.8"
2
+ VERSION = "4.2.1"
3
3
  end
data/lib/heroku_san.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  require File.join(File.dirname(__FILE__), 'railtie.rb') if defined?(Rails) && Rails::VERSION::MAJOR >= 3
2
2
  require 'git'
3
+ require 'heroku_san/api'
3
4
  require 'heroku_san/stage'
5
+ require 'heroku_san/parser'
4
6
  require 'heroku_san/project'
7
+ require 'heroku_san/configuration'
5
8
  require 'heroku_san/deploy/rails'
6
9
  require 'heroku_san/deploy/sinatra'
7
10
 
@@ -3,5 +3,7 @@ production:
3
3
  app: awesomeapp
4
4
  staging:
5
5
  app: awesomeapp-staging
6
- demo:
6
+ config:
7
+ EXTRA: 'bar'
8
+ demo:
7
9
  app: awesomeapp-demo
data/spec/git_spec.rb CHANGED
@@ -25,6 +25,13 @@ describe GitTest do
25
25
  subject.should_receive(:sh).with("git update-ref -d refs/heroku_san/deploy")
26
26
  subject.git_push(nil, 'git@heroku.com:awesomeapp.git', %w[--force -v])
27
27
  end
28
+
29
+ it "propagates any errors, but still cleans up" do
30
+ subject.should_receive(:sh).with("git update-ref refs/heroku_san/deploy HEAD^{commit}")
31
+ subject.should_receive(:sh).with("git push git@heroku.com:awesomeapp.git refs/heroku_san/deploy:refs/heads/master").and_raise
32
+ subject.should_receive(:sh).with("git update-ref -d refs/heroku_san/deploy")
33
+ expect { subject.git_push(nil, 'git@heroku.com:awesomeapp.git') }.to raise_error
34
+ end
28
35
  end
29
36
 
30
37
  describe "#git_tag" do
@@ -32,12 +39,14 @@ describe GitTest do
32
39
  subject.should_receive("`").with("git tag -l 'pattern*'") { "x\n\y\n\z\n" }
33
40
  subject.git_tag('pattern*').should == "z"
34
41
  end
42
+
35
43
  it "raises exception if no tags match the pattern" do
36
44
  subject.should_receive("`").with("git tag -l 'pattern*'") { "\n" }
37
45
  expect {
38
46
  subject.git_tag('pattern*')
39
47
  }.to raise_error(Git::NoTagFoundError)
40
48
  end
49
+
41
50
  it "returns nil for a nil glob" do
42
51
  subject.should_not_receive("`").with("git tag -l ''") { "\n" }
43
52
  subject.git_tag(nil).should == nil
@@ -49,6 +58,7 @@ describe GitTest do
49
58
  subject.should_receive("`").with("git rev-parse prod/1234567890") { "sha\n" }
50
59
  subject.git_rev_parse('prod/1234567890').should == "sha"
51
60
  end
61
+
52
62
  it "returns nil for a blank tag" do
53
63
  subject.should_not_receive("`").with("git rev-parse ") { "\n" }
54
64
  subject.git_rev_parse(nil).should == nil
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ # https://github.com/fastestforward/heroku_san/issues/105
4
+ describe HerokuSan::API do
5
+ subject(:api) { HerokuSan::API.new(:api_key => 'key', :mock => true)}
6
+ it "is a proxy to the Heroku::API" do
7
+ Heroku::API.any_instance.should_receive(:api_method).with(1, 2, {:arg => 3}) {true}
8
+ api.api_method(1, 2, {:arg => 3}).should be_true
9
+ end
10
+
11
+ it "reports Excon errors in a more human readable format" do
12
+ error_message = 'Name is already taken'
13
+ status_message = '000 Status'
14
+ response = mock("Response", :body => %Q[{"error":"#{error_message}"}], :headers => {'Status' => status_message})
15
+ Heroku::API.any_instance.should_receive(:api_method).and_raise(Heroku::API::Errors::ErrorWithResponse.new("excon message", response))
16
+
17
+ $stderr.should_receive(:puts).with("\nHeroku API ERROR: #{status_message} (#{error_message})\n\n")
18
+
19
+ expect {
20
+ api.api_method
21
+ }.to raise_error(Heroku::API::Errors::ErrorWithResponse, "excon message") {|error|
22
+ error.backtrace.should == []
23
+ }
24
+ end
25
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe HerokuSan::Configuration do
4
+ let(:configurable) { Configurable.new }
5
+ let(:configuration) { HerokuSan::Configuration.new(configurable) }
6
+
7
+ describe "#stages" do
8
+ it "creates a configuration hash" do
9
+ configuration.configuration = {'production' => {}}
10
+ configuration.stages.should == {
11
+ 'production' => HerokuSan::Stage.new('production', 'deploy' => HerokuSan::Deploy::Rails)
12
+ }
13
+ end
14
+
15
+ it "configures the deploy strategy" do
16
+ configurable.options = {'deploy' => HerokuSan::Deploy::Base}
17
+ configuration.configuration = {'production' => {}}
18
+ configuration.stages.should == {
19
+ 'production' => HerokuSan::Stage.new('production', 'deploy' => HerokuSan::Deploy::Base)
20
+ }
21
+ end
22
+ end
23
+
24
+ describe "#generate_config" do
25
+ context "unknown project" do
26
+ it "creates a new file using the example file" do
27
+ Dir.mktmpdir do |dir|
28
+ configurable.config_file = tmp_config_file = File.join(dir, 'config.yml')
29
+ FileUtils.should_receive(:cp).with(configuration.template, tmp_config_file)
30
+ configuration.generate_config.should be_true
31
+ end
32
+ end
33
+
34
+ it "does not overwrite an existing file" do
35
+ FileUtils.should_not_receive(:cp)
36
+ configurable.config_file = fixture("example.yml")
37
+ configuration.generate_config.should be_false
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe HerokuSan::Parser do
4
+ let(:parser) { subject }
5
+
6
+ describe '#parse' do
7
+ context 'using the new format' do
8
+ let(:parseable) { Parseable.new(fixture("example.yml")) }
9
+
10
+ it "returns a list of apps" do
11
+ parser.parse(parseable)
12
+
13
+ parseable.configuration.keys.should =~ %w[production staging demo]
14
+ parseable.configuration['production'].should == {'app' => 'awesomeapp', 'tag' => 'production/*', 'config' => {'BUNDLE_WITHOUT' => 'development:test', 'GOOGLE_ANALYTICS' => 'UA-12345678-1'}}
15
+ parseable.configuration['staging'].should == {'app' => 'awesomeapp-staging', 'stack' => 'bamboo-ree-1.8.7', 'config' => {'BUNDLE_WITHOUT' => 'development:test'}}
16
+ parseable.configuration['demo'].should == {'app' => 'awesomeapp-demo', 'stack' => 'cedar', 'config' => {'BUNDLE_WITHOUT' => 'development:test'}}
17
+ end
18
+ end
19
+
20
+ context "using the old heroku_san format" do
21
+ let(:parseable) { Parseable.new(fixture("old_format.yml")) }
22
+
23
+ it "returns a list of apps" do
24
+ parser.parse(parseable)
25
+
26
+ parseable.configuration.keys.should =~ %w[production staging demo]
27
+ parseable.configuration.should == {
28
+ 'production' => {'app' => 'awesomeapp', 'config' => {}},
29
+ 'staging' => {'app' => 'awesomeapp-staging', 'config' => {}},
30
+ 'demo' => {'app' => 'awesomeapp-demo', 'config' => {}}
31
+ }
32
+ end
33
+ end
34
+ end
35
+
36
+ describe "#convert_from_heroku_san_format" do
37
+ let(:old_format) { {'apps' => {'production' => 'awesomeapp', 'staging' => 'awesomeapp-staging', 'demo' => 'awesomeapp-demo'}} }
38
+ let(:new_format) { {'production' => {'app' => 'awesomeapp'}, 'staging' => {'app' => 'awesomeapp-staging'}, 'demo' => {'app' => 'awesomeapp-demo'}} }
39
+
40
+ it "converts to the new hash" do
41
+ parser.settings = old_format
42
+ expect {
43
+ parser.convert_from_heroku_san_format
44
+ }.to change(parser, :settings).to(new_format)
45
+ end
46
+
47
+ it "doesn't change new format" do
48
+ parser.settings = new_format
49
+ expect {
50
+ parser.convert_from_heroku_san_format
51
+ }.not_to change(parser, :settings)
52
+ end
53
+ end
54
+
55
+ describe "#merge_external_config" do
56
+ let(:extras) { {'production' => {'EXTRA' => 'bar'}, 'staging' => {'EXTRA' => 'foo'}} }
57
+
58
+ context "with no extras" do
59
+ it "doesn't change settings" do
60
+ parser.settings = {'production' => {'config' => {}}}
61
+ expect {
62
+ parser.merge_external_config
63
+ }.not_to change(parser, :settings)
64
+ end
65
+ end
66
+
67
+ context "with extra" do
68
+ before(:each) do
69
+ parser.should_receive(:git_clone).with('config_repos', anything)
70
+ parser.should_receive(:parse_yaml).and_return(extras)
71
+ end
72
+
73
+ it "merges extra configuration bits" do
74
+ parser.settings = {'config_repo' => 'config_repos', 'production' => {'config' => {}}}
75
+ expect {
76
+ parser.merge_external_config
77
+ }.to change(parser, :settings).to({"production" => {"config" => {"EXTRA" => "bar"}}})
78
+ end
79
+
80
+ it "overrides the main configuration" do
81
+ parser.settings = {'config_repo' => 'config_repos', 'staging' => {'config' => {'EXTRA' => 'bar'}}}
82
+ expect {
83
+ parser.merge_external_config
84
+ }.to change(parser, :settings).to({"staging" => {"config" => {"EXTRA" => "foo"}}})
85
+ end
86
+ end
87
+ end
88
+ end
@@ -2,26 +2,15 @@ require 'spec_helper'
2
2
  require 'tmpdir'
3
3
 
4
4
  describe HerokuSan::Project do
5
- let(:heroku_config_file) { File.join(SPEC_ROOT, "fixtures", "example.yml") }
6
- let(:heroku_san) { HerokuSan::Project.new(heroku_config_file) }
5
+ let(:heroku_san) { HerokuSan::Project.new }
7
6
  subject { heroku_san }
8
-
9
- describe ".new" do
10
- its(:all) { should =~ %w[production staging demo] }
11
- specify "with a missing config file has no stages" do
12
- heroku_san = HerokuSan::Project.new("/u/should/never/get/here")
13
- heroku_san.all.should == []
14
- end
15
-
16
- specify "with a deploy option configures each stage with the strategy" do
17
- heroku_san = HerokuSan::Project.new(heroku_config_file, :deploy => HerokuSan::Deploy::Base)
18
- heroku_san << heroku_san.all
19
- heroku_san.each_app do |stage|
20
- stage.instance_variable_get('@options')['deploy'].should == HerokuSan::Deploy::Base
21
- end
7
+ before do
8
+ HerokuSan::Configuration.new(Configurable.new).tap do |config|
9
+ config.configuration = {'production' => {}, 'staging' => {}, 'demo' => {}}
10
+ heroku_san.configuration = config.stages
22
11
  end
23
12
  end
24
-
13
+
25
14
  describe "#apps constructs the deploy list" do
26
15
  it "appends known shorthands to apps" do
27
16
  heroku_san.apps.should == []
@@ -53,8 +42,8 @@ describe HerokuSan::Project do
53
42
  end
54
43
 
55
44
  context "with only a single configured app" do
56
- let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "single_app.yml")) }
57
45
  it "returns the app" do
46
+ heroku_san.configuration= {'production' => {}}
58
47
  $stdout.should_receive(:puts).with('Defaulting to "production" since only one app is defined')
59
48
  expect {
60
49
  heroku_san.apps.should == %w[production]
@@ -66,7 +55,7 @@ describe HerokuSan::Project do
66
55
 
67
56
  describe "#each_app" do
68
57
  it "raises an error is no apps were specified" do
69
- expect { heroku_san.each_app do true; end }.to raise_error HerokuSan::NoApps
58
+ expect { heroku_san.each_app do true end }.to raise_error HerokuSan::NoApps
70
59
  end
71
60
 
72
61
  it "yields to a block with args" do
@@ -86,36 +75,4 @@ describe HerokuSan::Project do
86
75
  end
87
76
  end
88
77
  end
89
-
90
- context "using the heroku_san format" do
91
- let(:heroku_san) { HerokuSan::Project.new(File.join(SPEC_ROOT, "fixtures", "old_format.yml")) }
92
-
93
- it "returns a list of apps" do
94
- heroku_san.all.should =~ %w[production staging demo]
95
- end
96
- end
97
-
98
- describe "#create_config" do
99
- context "unknown project" do
100
- let(:template_config_file) do
101
- path = File.join(SPEC_ROOT, "..", "lib/templates", "heroku.example.yml")
102
- (File.respond_to? :realpath) ? File.realpath(path) : path
103
- end
104
-
105
- it "creates a new file using the example file" do
106
- Dir.mktmpdir do |dir|
107
- tmp_config_file = File.join dir, 'config.yml'
108
- heroku_san = HerokuSan::Project.new(tmp_config_file)
109
- FileUtils.should_receive(:cp).with(File.expand_path(template_config_file), tmp_config_file)
110
- heroku_san.create_config.should be_true
111
- end
112
- end
113
-
114
- it "does not overwrite an existing file" do
115
- FileUtils.should_not_receive(:cp)
116
- heroku_san.create_config.should be_false
117
- end
118
- end
119
- end
120
-
121
78
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe HerokuSan::Stage do
4
4
  include Git
5
- subject { HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "cedar"})}
5
+ subject { HerokuSan::Stage.new('production', {"deploy" => HerokuSan::Deploy::Rails, "app" => "awesomeapp", "stack" => "cedar"})}
6
6
  STOCK_CONFIG = {"BUNDLE_WITHOUT"=>"development:test", "LANG"=>"en_US.UTF-8", "RACK_ENV"=>"production"}
7
7
  before do
8
8
  HerokuSan::Stage.any_instance.stub(:preflight_check_for_cli)
@@ -44,7 +44,7 @@ describe HerokuSan::Stage do
44
44
  end
45
45
  end
46
46
 
47
- it "returns the stack name from the config if it is set there" do
47
+ it "returns the stack name from the config when it is set there" do
48
48
  subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "cedar"})
49
49
  subject.stack.should == 'cedar'
50
50
  end
@@ -117,6 +117,7 @@ describe HerokuSan::Stage do
117
117
  subject.deploy
118
118
  end
119
119
  end
120
+
120
121
  context "using a custom strategy" do
121
122
  class TestDeployStrategy < HerokuSan::Deploy::Base
122
123
  def deploy; end
@@ -149,7 +150,7 @@ describe HerokuSan::Stage do
149
150
  end
150
151
 
151
152
  context "with a block" do
152
- it "wraps it in a maitenance mode" do
153
+ it "wraps it in a maintenance mode" do
153
154
  with_app(subject, 'name' => subject.app) do |app_data|
154
155
  subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '1').ordered
155
156
  reactor = mock("Reactor"); reactor.should_receive(:scram).with(:now).ordered
@@ -158,6 +159,7 @@ describe HerokuSan::Stage do
158
159
  subject.maintenance {reactor.scram(:now)}
159
160
  end
160
161
  end
162
+
161
163
  it "ensures that maintenance mode is turned off" do
162
164
  with_app(subject, 'name' => subject.app) do |app_data|
163
165
  subject.heroku.should_receive(:post_app_maintenance).with(subject.app, '1').ordered
@@ -174,18 +176,22 @@ describe HerokuSan::Stage do
174
176
  after do
175
177
  subject.heroku.delete_app(@app)
176
178
  end
179
+
177
180
  it "uses the provided name" do
178
181
  (@app = subject.create).should == 'awesomeapp'
179
182
  end
183
+
180
184
  it "creates an app on heroku" do
181
185
  subject = HerokuSan::Stage.new('production')
182
186
  (@app = subject.create).should =~ /generated-name-\d+/
183
187
  end
188
+
184
189
  it "uses the default stack if none is given" do
185
190
  subject = HerokuSan::Stage.new('production')
186
191
  (@app = subject.create).should =~ /generated-name-\d+/
187
192
  subject.heroku.get_stack(@app).body.detect{|stack| stack['current']}['name'].should == 'bamboo-mri-1.9.2'
188
- end
193
+ end
194
+
189
195
  it "uses the stack from the config" do
190
196
  (@app = subject.create).should == 'awesomeapp'
191
197
  subject.heroku.get_stack(@app).body.detect{|stack| stack['current']}['name'].should == 'cedar'
@@ -200,6 +206,22 @@ describe HerokuSan::Stage do
200
206
  end
201
207
  end
202
208
 
209
+ describe "#push_config" do
210
+ it "updates the configuration settings on Heroku" do
211
+ subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {'FOO' => 'bar', 'DOG' => 'emu'}})
212
+ with_app(subject, 'name' => subject.app) do |app_data|
213
+ subject.push_config.should == STOCK_CONFIG.merge('FOO' => 'bar', 'DOG' => 'emu')
214
+ end
215
+ end
216
+
217
+ it "pushes the options hash" do
218
+ subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {'FOO' => 'bar', 'DOG' => 'emu'}})
219
+ with_app(subject, 'name' => subject.app) do |app_data|
220
+ subject.push_config('RACK_ENV' => 'magic').should == STOCK_CONFIG.merge('RACK_ENV' => 'magic')
221
+ end
222
+ end
223
+ end
224
+
203
225
  describe "#restart" do
204
226
  it "restarts an app" do
205
227
  with_app(subject, 'name' => subject.app) do |app_data|
@@ -213,33 +235,20 @@ describe HerokuSan::Stage do
213
235
  subject.should_receive(:system).with("heroku", "logs", "--app", "awesomeapp") { true }
214
236
  subject.logs
215
237
  end
238
+
216
239
  it "tails log files" do
217
240
  subject.should_receive(:system).with("heroku", "logs", "--tail", "--app", "awesomeapp") { true }
218
241
  subject.logs(:tail)
219
242
  end
220
243
  end
221
244
 
222
- describe "#push_config" do
223
- it "updates the configuration settings on Heroku" do
224
- subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {'FOO' => 'bar', 'DOG' => 'emu'}})
225
- with_app(subject, 'name' => subject.app) do |app_data|
226
- subject.push_config.should == STOCK_CONFIG.merge('FOO' => 'bar', 'DOG' => 'emu')
227
- end
228
- end
229
- it "pushes the options hash" do
230
- subject = HerokuSan::Stage.new('test', {"app" => "awesomeapp", "config" => {'FOO' => 'bar', 'DOG' => 'emu'}})
231
- with_app(subject, 'name' => subject.app) do |app_data|
232
- subject.push_config('RACK_ENV' => 'magic').should == STOCK_CONFIG.merge('RACK_ENV' => 'magic')
233
- end
234
- end
235
- end
236
-
237
245
  describe "#revision" do
238
246
  it "returns the named remote revision for the stage" do
239
247
  subject.should_receive(:git_revision).with(subject.repo) {"sha"}
240
248
  subject.should_receive(:git_named_rev).with('sha') {"sha production/123456"}
241
249
  subject.revision.should == 'sha production/123456'
242
250
  end
251
+
243
252
  it "returns nil if the stage has never been deployed" do
244
253
  subject.should_receive(:git_revision).with(subject.repo) {nil}
245
254
  subject.should_receive(:git_named_rev).with(nil) {''}
@@ -264,6 +273,7 @@ describe HerokuSan::Stage do
264
273
  subject.installed_addons.map{|a|a['name']}.should =~ subject.install_addons.map{|a| a['name']}
265
274
  end
266
275
  end
276
+
267
277
  it "only installs missing addons" do
268
278
  subject = HerokuSan::Stage.new('production', {"app" => "awesomeapp", "stack" => "bamboo-ree-1.8.7", "addons" => %w[shared-database:5mb custom_domains:basic ssl:piggyback]})
269
279
  with_app(subject, 'name' => subject.app) do |app_data|
data/spec/spec_helper.rb CHANGED
@@ -20,4 +20,8 @@ RSpec.configure do |config|
20
20
  config.mock_with :rspec
21
21
  end
22
22
 
23
+ def fixture(filename)
24
+ File.join(SPEC_ROOT, "fixtures", filename)
25
+ end
26
+
23
27
  require File.join(SPEC_ROOT, '/../lib/heroku_san')
@@ -0,0 +1,2 @@
1
+ Configurable = Struct.new(:config_file, :configuration, :options)
2
+ Parseable = Struct.new(:config_file, :configuration)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heroku_san
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.8
4
+ version: 4.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-01-22 00:00:00.000000000 Z
15
+ date: 2013-02-03 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: heroku-api
@@ -202,10 +202,13 @@ files:
202
202
  - lib/generators/heroku_san_generator.rb
203
203
  - lib/git.rb
204
204
  - lib/heroku_san.rb
205
+ - lib/heroku_san/api.rb
206
+ - lib/heroku_san/configuration.rb
205
207
  - lib/heroku_san/deploy/base.rb
206
208
  - lib/heroku_san/deploy/noop.rb
207
209
  - lib/heroku_san/deploy/rails.rb
208
210
  - lib/heroku_san/deploy/sinatra.rb
211
+ - lib/heroku_san/parser.rb
209
212
  - lib/heroku_san/project.rb
210
213
  - lib/heroku_san/stage.rb
211
214
  - lib/heroku_san/tasks.rb
@@ -216,15 +219,17 @@ files:
216
219
  - spec/fixtures/example.yml
217
220
  - spec/fixtures/extended_config.yml
218
221
  - spec/fixtures/old_format.yml
219
- - spec/fixtures/single_app.yml
220
222
  - spec/git_spec.rb
223
+ - spec/heroku_san/api_spec.rb
224
+ - spec/heroku_san/configuration_spec.rb
221
225
  - spec/heroku_san/deploy/base_spec.rb
222
226
  - spec/heroku_san/deploy/rails_spec.rb
227
+ - spec/heroku_san/parser_spec.rb
223
228
  - spec/heroku_san/project_spec.rb
224
229
  - spec/heroku_san/stage_spec.rb
225
- - spec/issues/issue_105_spec.rb
226
230
  - spec/spec_helper.rb
227
231
  - spec/support/heroku.rb
232
+ - spec/support/mocks.rb
228
233
  homepage: http://github.com/fastestforward/heroku_san
229
234
  licenses:
230
235
  - MIT
@@ -240,7 +245,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
240
245
  version: '0'
241
246
  segments:
242
247
  - 0
243
- hash: 3786976856768330652
248
+ hash: -2348527230697112228
244
249
  required_rubygems_version: !ruby/object:Gem::Requirement
245
250
  none: false
246
251
  requirements:
@@ -264,12 +269,14 @@ test_files:
264
269
  - spec/fixtures/example.yml
265
270
  - spec/fixtures/extended_config.yml
266
271
  - spec/fixtures/old_format.yml
267
- - spec/fixtures/single_app.yml
268
272
  - spec/git_spec.rb
273
+ - spec/heroku_san/api_spec.rb
274
+ - spec/heroku_san/configuration_spec.rb
269
275
  - spec/heroku_san/deploy/base_spec.rb
270
276
  - spec/heroku_san/deploy/rails_spec.rb
277
+ - spec/heroku_san/parser_spec.rb
271
278
  - spec/heroku_san/project_spec.rb
272
279
  - spec/heroku_san/stage_spec.rb
273
- - spec/issues/issue_105_spec.rb
274
280
  - spec/spec_helper.rb
275
281
  - spec/support/heroku.rb
282
+ - spec/support/mocks.rb
@@ -1,6 +0,0 @@
1
- production:
2
- app: awesomeapp
3
- tag: production/*
4
- config:
5
- BUNDLE_WITHOUT: "development:test"
6
- GOOGLE_ANALYTICS: "UA-12345678-1"
@@ -1,13 +0,0 @@
1
- require 'spec_helper'
2
-
3
- # https://github.com/fastestforward/heroku_san/issues/105
4
-
5
- =begin
6
- Expected(202) <=> Actual(422 Unprocessable Entity)
7
- request => {:connect_timeout=>60, :headers=>{"Accept"=>"application/json", "Accept-Encoding"=>"gzip", "Authorization"=>"Basic OjUwZTc4NjgxZDhmYzcxZDI3Yzc5ZTM3MTZhNDk3Nzk1MzU2Mzk4ZTM=", "User-Agent"=>"heroku-rb/0.1.8", "X-Heroku-API-Version"=>"3", "X-Ruby-Version"=>"1.9.2", "X-Ruby-Platform"=>"x86_64-darwin10.8.0", "Host"=>"api.heroku.com:443", "Content-Length"=>0}, :instrumentor_name=>"excon", :mock=>false, :read_timeout=>60, :retry_limit=>4, :ssl_ca_file=>"/Users/kmayer/.rvm/gems/ruby-1.9.2-p320/gems/excon-0.13.4/data/cacert.pem", :ssl_verify_peer=>true, :write_timeout=>60, :host=>"api.heroku.com", :path=>"/apps", :port=>"443", :query=>{"app[name]"=>"stark-window-5734"}, :scheme=>"https", :expects=>202, :method=>:post}
8
- response => #<Excon::Response:0x00000100e07288 @body="{\"error\":\"Name is already taken\"}", @headers={"Cache-Control"=>"no-cache", "Content-Type"=>"application/json; charset=utf-8", "Date"=>"Sun, 29 Jul 2012 19:24:33 GMT", "Server"=>"nginx/1.0.14", "Status"=>"422 Unprocessable Entity", "Strict-Transport-Security"=>"max-age=500", "X-RateLimit-Limit"=>"486", "X-RateLimit-Remaining"=>"485", "X-RateLimit-Reset"=>"1343589933", "X-Runtime"=>"426", "Content-Length"=>"33", "Connection"=>"keep-alive"}, @status=422>
9
- =end
10
-
11
- describe "Handle Excon exceptions" do
12
- it "should report the error in a more readable format"
13
- end