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