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 +11 -2
- data/lib/heroku_san/api.rb +17 -0
- data/lib/heroku_san/configuration.rb +45 -0
- data/lib/heroku_san/parser.rb +57 -0
- data/lib/heroku_san/project.rb +17 -70
- data/lib/heroku_san/stage.rb +9 -7
- data/lib/heroku_san/version.rb +1 -1
- data/lib/heroku_san.rb +3 -0
- data/spec/fixtures/extended_config.yml +3 -1
- data/spec/git_spec.rb +10 -0
- data/spec/heroku_san/api_spec.rb +25 -0
- data/spec/heroku_san/configuration_spec.rb +41 -0
- data/spec/heroku_san/parser_spec.rb +88 -0
- data/spec/heroku_san/project_spec.rb +8 -51
- data/spec/heroku_san/stage_spec.rb +29 -19
- data/spec/spec_helper.rb +4 -0
- data/spec/support/mocks.rb +2 -0
- metadata +14 -7
- data/spec/fixtures/single_app.yml +0 -6
- data/spec/issues/issue_105_spec.rb +0 -13
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,13 @@
|
|
1
|
-
# Change
|
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
|
data/lib/heroku_san/project.rb
CHANGED
@@ -1,37 +1,30 @@
|
|
1
1
|
module HerokuSan
|
2
2
|
class Project
|
3
|
-
attr_reader :config_file
|
4
|
-
|
5
3
|
include Git
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
23
|
+
configuration.keys
|
31
24
|
end
|
32
25
|
|
33
26
|
def [](stage)
|
34
|
-
|
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")
|
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
|
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
|
data/lib/heroku_san/stage.rb
CHANGED
@@ -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 =
|
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 ||=
|
22
|
+
@heroku ||= HerokuSan::API.new(:api_key => auth_token, :mock => MOCK)
|
21
23
|
end
|
22
24
|
|
23
25
|
def app
|
data/lib/heroku_san/version.rb
CHANGED
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
|
|
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(:
|
6
|
-
let(:heroku_san) { HerokuSan::Project.new(heroku_config_file) }
|
5
|
+
let(:heroku_san) { HerokuSan::Project.new }
|
7
6
|
subject { heroku_san }
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
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
|
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
|
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
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.
|
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-
|
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:
|
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,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
|