dpl 1.8.33.travis.2049.5 → 1.8.34.travis.2089.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/lib/dpl/provider/heroku/api.rb +101 -29
- data/lib/dpl/version.rb +1 -1
- data/spec/provider/heroku_api_spec.rb +174 -43
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 344f328801c80df62034fbfcc73345c4f1777df5
|
4
|
+
data.tar.gz: 126e4bd8d202e46a3b2096fcd1a45b137742fb16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a57556f2852be0c4df1a23656529984bc66225f84b4519690114b9387824828b11fafb3e2ba06c70823125621878822386e775e9dbc04aea523899e71328041d
|
7
|
+
data.tar.gz: c2890e7cc455e59bfeb074fc9779754a12d1c8e8c9887ad07e724175434f8c6ce6afe41886c03189be5cd878a001ffeb80af40b7d43393e213fad8bb632f675d
|
data/Gemfile
CHANGED
@@ -1,11 +1,67 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'shellwords'
|
3
|
+
require 'logger'
|
3
4
|
|
4
5
|
module DPL
|
5
6
|
class Provider
|
6
7
|
module Heroku
|
7
8
|
class API < Generic
|
8
9
|
attr_reader :build_id
|
10
|
+
requires 'faraday'
|
11
|
+
requires 'rendezvous'
|
12
|
+
|
13
|
+
def check_auth
|
14
|
+
response = faraday.get('/account')
|
15
|
+
|
16
|
+
if response.success?
|
17
|
+
email = JSON.parse(response.body)["email"]
|
18
|
+
log "authenticated as #{email}"
|
19
|
+
else
|
20
|
+
handle_error_response(response)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def faraday
|
25
|
+
return @conn if @conn
|
26
|
+
headers = { "Accept" => "application/vnd.heroku+json; version=3" }
|
27
|
+
|
28
|
+
if options[:user] and options[:password]
|
29
|
+
# no-op
|
30
|
+
else
|
31
|
+
headers.merge!({ "Authorization" => "Bearer #{option(:api_key)}" })
|
32
|
+
end
|
33
|
+
|
34
|
+
@conn = Faraday.new( url: 'https://api.heroku.com', headers: headers ) do |faraday|
|
35
|
+
if options[:user] and options[:password]
|
36
|
+
faraday.basic_auth(options[:user], options[:password])
|
37
|
+
end
|
38
|
+
if log_level = options[:log_level]
|
39
|
+
logger = Logger.new($stderr)
|
40
|
+
logger.level = Logger.const_get(log_level.upcase)
|
41
|
+
|
42
|
+
faraday.response :logger, logger do | logger |
|
43
|
+
logger.filter(/(.*Authorization: ).*/,'\1[REDACTED]')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
faraday.adapter Faraday.default_adapter
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_app
|
51
|
+
log "checking for app #{option(:app)}"
|
52
|
+
response = faraday.get("/apps/#{option(:app)}")
|
53
|
+
if response.success?
|
54
|
+
name = JSON.parse(response.body)["name"]
|
55
|
+
log "found app #{name}"
|
56
|
+
else
|
57
|
+
handle_error_response(response)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_error_response(response)
|
62
|
+
error_response = JSON.parse(response.body)
|
63
|
+
error "API request failed.\nMessage: #{error_response["message"]}\nReference: #{error_response["url"]}"
|
64
|
+
end
|
9
65
|
|
10
66
|
def push_app
|
11
67
|
pack_archive
|
@@ -30,16 +86,29 @@ module DPL
|
|
30
86
|
|
31
87
|
def trigger_build
|
32
88
|
log "triggering new deployment"
|
33
|
-
response
|
34
|
-
|
35
|
-
|
36
|
-
|
89
|
+
response = faraday.post("/apps/#{option(:app)}/builds") do |req|
|
90
|
+
req.headers['Content-Type'] = 'application/json'
|
91
|
+
req.body = {
|
92
|
+
"source_blob" => {
|
93
|
+
"url" => get_url,
|
94
|
+
"version" => version
|
95
|
+
}
|
96
|
+
}.to_json
|
97
|
+
end
|
98
|
+
|
99
|
+
if response.success?
|
100
|
+
@build_id = JSON.parse(response.body)['id']
|
101
|
+
output_stream_url = JSON.parse(response.body)['output_stream_url']
|
102
|
+
context.shell "curl #{Shellwords.escape(output_stream_url)}"
|
103
|
+
else
|
104
|
+
handle_error_response(response)
|
105
|
+
end
|
37
106
|
end
|
38
107
|
|
39
108
|
def verify_build
|
40
109
|
loop do
|
41
|
-
response = get("builds/#{build_id}/result")
|
42
|
-
exit_code = response.
|
110
|
+
response = faraday.get("/apps/#{option(:app)}/builds/#{build_id}/result")
|
111
|
+
exit_code = JSON.parse(response.body)['exit_code']
|
43
112
|
if exit_code.nil?
|
44
113
|
log "heroku build still pending"
|
45
114
|
sleep 5
|
@@ -61,38 +130,41 @@ module DPL
|
|
61
130
|
end
|
62
131
|
|
63
132
|
def source_blob
|
64
|
-
@source_blob
|
133
|
+
return @source_blob if @source_blob
|
134
|
+
|
135
|
+
response = faraday.post('/sources')
|
136
|
+
|
137
|
+
if response.success?
|
138
|
+
@source_blob = JSON.parse(response.body)["source_blob"]
|
139
|
+
else
|
140
|
+
handle_error_response(response)
|
141
|
+
end
|
65
142
|
end
|
66
143
|
|
67
144
|
def version
|
68
145
|
@version ||= options[:version] || context.env['TRAVIS_COMMIT'] || `git rev-parse HEAD`.strip
|
69
146
|
end
|
70
147
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
api.request(options).body
|
148
|
+
def restart
|
149
|
+
response = faraday.delete "/apps/#{option(:app)}/dynos" do |req|
|
150
|
+
req.headers['Content-Type'] = 'application/json'
|
151
|
+
end
|
152
|
+
unless response.success?
|
153
|
+
handle_error_response(response)
|
154
|
+
end
|
80
155
|
end
|
81
156
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
options[:headers]['Content-Type'] = 'application/json'
|
157
|
+
def run(command)
|
158
|
+
response = faraday.post "/apps/#{option(:app)}/dynos" do |req|
|
159
|
+
req.headers['Content-Type'] = 'application/json'
|
160
|
+
req.body = {"command" => command, "attach" => true}.to_json
|
161
|
+
end
|
162
|
+
if response.success?
|
163
|
+
rendezvous_url = JSON.parse(response.body)["attach_url"]
|
164
|
+
Rendezvous.start(url: rendezvous_url)
|
165
|
+
else
|
166
|
+
handle_error_response(response)
|
93
167
|
end
|
94
|
-
|
95
|
-
api.request(options).body
|
96
168
|
end
|
97
169
|
end
|
98
170
|
end
|
data/lib/dpl/version.rb
CHANGED
@@ -1,16 +1,160 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
require 'heroku-api'
|
3
2
|
require 'dpl/provider/heroku'
|
3
|
+
require 'faraday'
|
4
4
|
|
5
5
|
describe DPL::Provider::Heroku do
|
6
|
+
let(:api_key) { 'foo' }
|
7
|
+
let(:stubs) { Faraday::Adapter::Test::Stubs.new }
|
8
|
+
let(:faraday) {
|
9
|
+
Faraday.new do |builder|
|
10
|
+
builder.adapter :test, stubs do |stub|
|
11
|
+
stub.get("/account") {|env| [200, response_headers, account_response_body]}
|
12
|
+
stub.post("/apps/example/builds") {|env| [201, response_headers, builds_response_body]}
|
13
|
+
stub.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body]}
|
14
|
+
stub.post("/sources") {|env| [201, response_headers, source_response_body] }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:response_headers) {
|
20
|
+
{'Content-Type' => 'application/json'}
|
21
|
+
}
|
22
|
+
|
23
|
+
let(:account_response_body) {
|
24
|
+
'{
|
25
|
+
"allow_tracking": true,
|
26
|
+
"beta": false,
|
27
|
+
"created_at": "2012-01-01T12:00:00Z",
|
28
|
+
"email": "username@example.com",
|
29
|
+
"federated": false,
|
30
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
31
|
+
"identity_provider": {
|
32
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
33
|
+
"organization": {
|
34
|
+
"name": "example"
|
35
|
+
}
|
36
|
+
},
|
37
|
+
"last_login": "2012-01-01T12:00:00Z",
|
38
|
+
"name": "Tina Edmonds",
|
39
|
+
"sms_number": "+1 ***-***-1234",
|
40
|
+
"suspended_at": "2012-01-01T12:00:00Z",
|
41
|
+
"delinquent_at": "2012-01-01T12:00:00Z",
|
42
|
+
"two_factor_authentication": false,
|
43
|
+
"updated_at": "2012-01-01T12:00:00Z",
|
44
|
+
"verified": false,
|
45
|
+
"default_organization": {
|
46
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
47
|
+
"name": "example"
|
48
|
+
}
|
49
|
+
}'
|
50
|
+
}
|
51
|
+
|
52
|
+
let(:builds_response_body) {
|
53
|
+
'{
|
54
|
+
"app": {
|
55
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef"
|
56
|
+
},
|
57
|
+
"buildpacks": [
|
58
|
+
{
|
59
|
+
"url": "https://github.com/heroku/heroku-buildpack-ruby"
|
60
|
+
}
|
61
|
+
],
|
62
|
+
"created_at": "2012-01-01T12:00:00Z",
|
63
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
64
|
+
"output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef",
|
65
|
+
"source_blob": {
|
66
|
+
"checksum": "SHA256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
|
67
|
+
"url": "https://example.com/source.tgz?token=xyz",
|
68
|
+
"version": "v1.3.0"
|
69
|
+
},
|
70
|
+
"release": {
|
71
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef"
|
72
|
+
},
|
73
|
+
"slug": {
|
74
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef"
|
75
|
+
},
|
76
|
+
"status": "succeeded",
|
77
|
+
"updated_at": "2012-01-01T12:00:00Z",
|
78
|
+
"user": {
|
79
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
80
|
+
"email": "username@example.com"
|
81
|
+
}
|
82
|
+
}'
|
83
|
+
}
|
84
|
+
|
85
|
+
let(:source_response_body) {
|
86
|
+
'{
|
87
|
+
"source_blob": {
|
88
|
+
"get_url": "https://api.heroku.com/sources/1234.tgz",
|
89
|
+
"put_url": "https://api.heroku.com/sources/1234.tgz"
|
90
|
+
}
|
91
|
+
}'
|
92
|
+
}
|
93
|
+
|
94
|
+
let(:build_result_response_body) {
|
95
|
+
'{
|
96
|
+
"build": {
|
97
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
98
|
+
"status": "succeeded",
|
99
|
+
"output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
|
100
|
+
},
|
101
|
+
"exit_code": 0,
|
102
|
+
"lines": [
|
103
|
+
{
|
104
|
+
"line": "-----> Ruby app detected\n",
|
105
|
+
"stream": "STDOUT"
|
106
|
+
}
|
107
|
+
]
|
108
|
+
}'
|
109
|
+
}
|
110
|
+
|
111
|
+
let(:build_result_response_body_failure) {
|
112
|
+
'{
|
113
|
+
"build": {
|
114
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
115
|
+
"status": "failed",
|
116
|
+
"output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
|
117
|
+
},
|
118
|
+
"exit_code": 1,
|
119
|
+
"lines": [
|
120
|
+
{
|
121
|
+
"line": "-----> Ruby app detected\n",
|
122
|
+
"stream": "STDOUT"
|
123
|
+
}
|
124
|
+
]
|
125
|
+
}'
|
126
|
+
}
|
127
|
+
|
128
|
+
let(:build_result_response_body_in_progress) {
|
129
|
+
'{
|
130
|
+
"build": {
|
131
|
+
"id": "01234567-89ab-cdef-0123-456789abcdef",
|
132
|
+
"status": "failed",
|
133
|
+
"output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
|
134
|
+
},
|
135
|
+
"lines": [
|
136
|
+
{
|
137
|
+
"line": "-----> Ruby app detected\n",
|
138
|
+
"stream": "STDOUT"
|
139
|
+
}
|
140
|
+
]
|
141
|
+
}'
|
142
|
+
}
|
143
|
+
|
6
144
|
subject(:provider) do
|
7
|
-
described_class.new(DummyContext.new,
|
145
|
+
described_class.new(DummyContext.new, provider_options.merge({ :api_key => api_key}))
|
8
146
|
end
|
9
147
|
|
148
|
+
let(:provider_options) {
|
149
|
+
{:app => 'example', :key_name => 'key', :strategy => "api"}
|
150
|
+
}
|
151
|
+
|
10
152
|
let(:expected_headers) do
|
11
|
-
{ "
|
153
|
+
{ "Authorization" => "Bearer #{api_key}", "Accept" => "application/vnd.heroku+json; version=3" }
|
12
154
|
end
|
13
155
|
|
156
|
+
let(:api_url) { 'https://api.heroku.com' }
|
157
|
+
|
14
158
|
describe "#ssh" do
|
15
159
|
it "doesn't require an ssh key" do
|
16
160
|
expect(provider.needs_key?).to eq(false)
|
@@ -19,74 +163,61 @@ describe DPL::Provider::Heroku do
|
|
19
163
|
|
20
164
|
describe "#api" do
|
21
165
|
it 'accepts an api key' do
|
22
|
-
|
23
|
-
|
24
|
-
expect(provider.api).to eq(api)
|
166
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
167
|
+
provider.check_auth
|
25
168
|
end
|
26
169
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
170
|
+
context "when api_key is not given" do
|
171
|
+
let(:provider) { described_class.new(DummyContext.new, provider_options) }
|
172
|
+
it 'raises DPL::Error' do
|
173
|
+
provider.options.update(:user => "foo", :password => "bar")
|
174
|
+
expect(::Heroku::API).not_to receive(:new)
|
175
|
+
expect { provider.check_auth }.to raise_error(DPL::Error)
|
176
|
+
end
|
32
177
|
end
|
33
178
|
end
|
34
179
|
|
35
180
|
describe "#trigger_build" do
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
"user" => { "id" => "01234567-89ab-cdef-0123-456789abcdef", "email" => "username@example.com" }
|
43
|
-
} }
|
181
|
+
it "does not initiate legacy API object" do
|
182
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
183
|
+
expect(::Heroku::API).not_to receive(:new)
|
184
|
+
provider.trigger_build
|
185
|
+
end
|
186
|
+
|
44
187
|
example do
|
45
188
|
expect(provider).to receive(:log).with('triggering new deployment')
|
189
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
46
190
|
expect(provider).to receive(:get_url).and_return 'http://example.com/source.tgz'
|
47
|
-
expect(provider).to receive(:version).and_return '
|
48
|
-
expect(provider).to receive(:
|
49
|
-
:builds, source_blob: {url: 'http://example.com/source.tgz', version: 'sha'}
|
50
|
-
).and_return(response_body)
|
51
|
-
expect(provider.context).to receive(:shell).with('curl http://example.com/stream')
|
191
|
+
expect(provider).to receive(:version).and_return 'v1.3.0'
|
192
|
+
expect(provider.context).to receive(:shell).with('curl https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef')
|
52
193
|
provider.trigger_build
|
53
|
-
expect(provider.build_id).to eq('
|
194
|
+
expect(provider.build_id).to eq('01234567-89ab-cdef-0123-456789abcdef')
|
54
195
|
end
|
55
196
|
end
|
56
197
|
|
57
198
|
describe "#verify_build" do
|
58
|
-
def response_body(status, exit_code)
|
59
|
-
{
|
60
|
-
"build" => {
|
61
|
-
"id" => "01234567-89ab-cdef-0123-456789abcdef",
|
62
|
-
"status" => status
|
63
|
-
},
|
64
|
-
"exit_code" => exit_code
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
|
-
before do
|
69
|
-
allow(provider).to receive(:build_id).and_return('abc')
|
70
|
-
end
|
71
|
-
|
72
199
|
context 'when build succeeds' do
|
73
200
|
example do
|
74
|
-
expect(provider).to receive(:
|
201
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
202
|
+
expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
|
75
203
|
expect{ provider.verify_build }.not_to raise_error
|
76
204
|
end
|
77
205
|
end
|
78
206
|
|
79
207
|
context 'when build fails' do
|
80
208
|
example do
|
81
|
-
expect(provider).to receive(:
|
209
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
210
|
+
expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
|
211
|
+
stubs.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body_failure]}
|
82
212
|
expect{ provider.verify_build }.to raise_error("deploy failed, build exited with code 1")
|
83
213
|
end
|
84
214
|
end
|
85
215
|
|
86
216
|
context 'when build is pending, then succeeds' do
|
87
217
|
example do
|
88
|
-
expect(provider).to receive(:
|
89
|
-
expect(provider).to receive(:
|
218
|
+
expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
|
219
|
+
expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
|
220
|
+
stubs.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body_in_progress]}
|
90
221
|
expect(provider).to receive(:sleep).with(5) # stub sleep
|
91
222
|
expect{ provider.verify_build }.not_to raise_error
|
92
223
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dpl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.8.
|
4
|
+
version: 1.8.34.travis.2089.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstantin Haase
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|