heroku_san 4.0.8 → 4.2.1

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.
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