dpl-heroku 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 460cf4528d1f5fc25f763b67596cf5e50eb327a3
4
+ data.tar.gz: 3e841cf48c4c1feff85e14b7794f2b8d5844f78f
5
+ SHA512:
6
+ metadata.gz: 62f0c7002af8f2107245a7b05948c7c9375995b5f2092dc92d1d951a6df0c3a2ed19dc51ac4d1dffe365c416259215960407fd616a7c0ab575d4ed116ea98a2c
7
+ data.tar.gz: 04d0ba08a02ce4696e52dcb80b8de0ecd9aad6ea54cc1e03ef93b1bef901232ff871bff2bcad2f22f76e1d766e97e7d5863b3dc5fb286dfe8eedd8501198790e
@@ -0,0 +1,3 @@
1
+ require './gemspec_helper'
2
+
3
+ gemspec_for 'heroku', [['faraday'], ['rendezvous'], ['netrc']]
@@ -0,0 +1,22 @@
1
+ require 'faraday'
2
+ require 'rendezvous'
3
+ require 'netrc'
4
+
5
+ module DPL
6
+ class Provider
7
+ module Heroku
8
+ autoload :API, 'dpl/provider/heroku/api'
9
+ autoload :Generic, 'dpl/provider/heroku/generic'
10
+ autoload :Git, 'dpl/provider/heroku/git'
11
+
12
+ extend self
13
+
14
+ def new(context, options)
15
+ strategy = options[:strategy] || 'api'
16
+ constant = constants.detect { |c| c.to_s.downcase == strategy.downcase.gsub(/\W/, '') }
17
+ raise Error, 'unknown strategy %p' % strategy unless constant and constant != Generic
18
+ const_get(constant).new(context, options)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,96 @@
1
+ require 'json'
2
+ require 'shellwords'
3
+ require 'logger'
4
+
5
+ module DPL
6
+ class Provider
7
+ module Heroku
8
+ class API < Generic
9
+ attr_reader :build_id
10
+
11
+ def push_app
12
+ pack_archive
13
+ upload_archive
14
+ trigger_build
15
+ verify_build
16
+ end
17
+
18
+ def archive_file
19
+ Shellwords.escape("#{context.env['HOME']}/.dpl.#{option(:app)}.tgz")
20
+ end
21
+
22
+ def pack_archive
23
+ log "creating application archive"
24
+ context.shell "tar -zcf #{archive_file} --exclude .git ."
25
+ end
26
+
27
+ def upload_archive
28
+ log "uploading application archive"
29
+ context.shell "curl #{Shellwords.escape(put_url)} -X PUT -H 'Content-Type:' -H 'Accept: application/vnd.heroku+json; version=3' --data-binary @#{archive_file}"
30
+ end
31
+
32
+ def trigger_build
33
+ log "triggering new deployment"
34
+ response = faraday.post("/apps/#{option(:app)}/builds") do |req|
35
+ req.headers['Content-Type'] = 'application/json'
36
+ req.body = {
37
+ "source_blob" => {
38
+ "url" => get_url,
39
+ "version" => version
40
+ }
41
+ }.to_json
42
+ end
43
+
44
+ if response.success?
45
+ @build_id = JSON.parse(response.body)['id']
46
+ output_stream_url = JSON.parse(response.body)['output_stream_url']
47
+ context.shell "curl #{Shellwords.escape(output_stream_url)} -H 'Accept: application/vnd.heroku+json; version=3'"
48
+ else
49
+ handle_error_response(response)
50
+ end
51
+ end
52
+
53
+ def verify_build
54
+ loop do
55
+ response = faraday.get("/apps/#{option(:app)}/builds/#{build_id}/result")
56
+ exit_code = JSON.parse(response.body)['exit_code']
57
+ if exit_code.nil?
58
+ log "heroku build still pending"
59
+ sleep 5
60
+ next
61
+ elsif exit_code == 0
62
+ break
63
+ else
64
+ error "deploy failed, build exited with code #{exit_code}"
65
+ end
66
+ end
67
+ end
68
+
69
+ def get_url
70
+ source_blob.fetch("get_url")
71
+ end
72
+
73
+ def put_url
74
+ source_blob.fetch("put_url")
75
+ end
76
+
77
+ def source_blob
78
+ return @source_blob if @source_blob
79
+
80
+ response = faraday.post('/sources')
81
+
82
+ if response.success?
83
+ @source_blob = JSON.parse(response.body)["source_blob"]
84
+ else
85
+ handle_error_response(response)
86
+ end
87
+ end
88
+
89
+ def version
90
+ @version ||= options[:version] || context.env['TRAVIS_COMMIT'] || `git rev-parse HEAD`.strip
91
+ end
92
+
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,91 @@
1
+ require 'json'
2
+
3
+ module DPL
4
+ class Provider
5
+ module Heroku
6
+ class Generic < Provider
7
+ attr_reader :app, :user
8
+
9
+ def needs_key?
10
+ false
11
+ end
12
+
13
+ def faraday
14
+ return @conn if @conn
15
+ headers = { "Accept" => "application/vnd.heroku+json; version=3" }
16
+
17
+ if options[:user] and options[:password]
18
+ # no-op
19
+ else
20
+ headers.merge!({ "Authorization" => "Bearer #{option(:api_key)}" })
21
+ end
22
+
23
+ @conn = Faraday.new( url: 'https://api.heroku.com', headers: headers ) do |faraday|
24
+ if options[:user] and options[:password]
25
+ faraday.basic_auth(options[:user], options[:password])
26
+ end
27
+ if log_level = options[:log_level]
28
+ logger = Logger.new($stderr)
29
+ logger.level = Logger.const_get(log_level.upcase)
30
+
31
+ faraday.response :logger, logger do | logger |
32
+ logger.filter(/(.*Authorization: ).*/,'\1[REDACTED]')
33
+ end
34
+ end
35
+ faraday.adapter Faraday.default_adapter
36
+ end
37
+ end
38
+
39
+ def check_auth
40
+ response = faraday.get('/account')
41
+
42
+ if response.success?
43
+ email = JSON.parse(response.body)["email"]
44
+ @user = email
45
+ log "authentication succeeded"
46
+ else
47
+ handle_error_response(response)
48
+ end
49
+ end
50
+
51
+ def handle_error_response(response)
52
+ error_response = JSON.parse(response.body)
53
+ error "API request failed.\nMessage: #{error_response["message"]}\nReference: #{error_response["url"]}"
54
+ end
55
+
56
+ def check_app
57
+ log "checking for app #{option(:app)}"
58
+ response = faraday.get("/apps/#{option(:app)}")
59
+ if response.success?
60
+ @app = JSON.parse(response.body)
61
+ log "found app #{@app["name"]}"
62
+ else
63
+ handle_error_response(response)
64
+ end
65
+ end
66
+
67
+ def restart
68
+ response = faraday.delete "/apps/#{option(:app)}/dynos" do |req|
69
+ req.headers['Content-Type'] = 'application/json'
70
+ end
71
+ unless response.success?
72
+ handle_error_response(response)
73
+ end
74
+ end
75
+
76
+ def run(command)
77
+ response = faraday.post "/apps/#{option(:app)}/dynos" do |req|
78
+ req.headers['Content-Type'] = 'application/json'
79
+ req.body = {"command" => command, "attach" => true}.to_json
80
+ end
81
+ if response.success?
82
+ rendezvous_url = JSON.parse(response.body)["attach_url"]
83
+ Rendezvous.start(url: rendezvous_url)
84
+ else
85
+ handle_error_response(response)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,26 @@
1
+ module DPL
2
+ class Provider
3
+ module Heroku
4
+ class Git < Generic
5
+ def git_url
6
+ "https://git.heroku.com/#{option(:app)}.git"
7
+ end
8
+
9
+ def push_app
10
+ git_remote = options[:git] || git_url
11
+ write_netrc if git_remote.start_with?("https://")
12
+ log "$ git fetch origin $TRAVIS_BRANCH --unshallow"
13
+ context.shell "git fetch origin $TRAVIS_BRANCH --unshallow"
14
+ log "$ git push #{git_remote} HEAD:refs/heads/master -f"
15
+ context.shell "git push #{git_remote} HEAD:refs/heads/master -f"
16
+ end
17
+
18
+ def write_netrc
19
+ n = Netrc.read
20
+ n['git.heroku.com'] = [user, option(:api_key)]
21
+ n.save
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
data/notes/heroku.md ADDED
@@ -0,0 +1,3 @@
1
+ Heroku might send out emails for new deploy keys (doesn't do it for me, but for some others).
2
+
3
+ Alternative is Anvil, but it's not perfect, as it duplicates a lot of Heroku logic internally (and failed for me in one case).
@@ -0,0 +1,357 @@
1
+ require 'spec_helper'
2
+ require 'dpl/provider/heroku'
3
+ require 'faraday'
4
+
5
+ RSpec.shared_context 'with faraday' 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.get("/apps/example") {|env| [200, response_headers, app_response_body]}
13
+ stub.post("/apps/example/builds") {|env| [201, response_headers, builds_response_body]}
14
+ stub.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body]}
15
+ stub.post("/sources") {|env| [201, response_headers, source_response_body] }
16
+ stub.post("/apps/example/dynos") {|env| [201, response_headers, dynos_create_response_body]}
17
+ stub.delete("/apps/example/dynos") {|env| [202, response_headers, '{}'] }
18
+ end
19
+ end
20
+ }
21
+
22
+ let(:response_headers) {
23
+ {'Content-Type' => 'application/json'}
24
+ }
25
+
26
+ let(:app_response_body) {
27
+ '{
28
+ "acm": false,
29
+ "archived_at": "2012-01-01T12:00:00Z",
30
+ "buildpack_provided_description": "Ruby/Rack",
31
+ "build_stack": {
32
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
33
+ "name": "cedar-14"
34
+ },
35
+ "created_at": "2012-01-01T12:00:00Z",
36
+ "git_url": "https://git.heroku.com/example.git",
37
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
38
+ "maintenance": false,
39
+ "name": "example",
40
+ "owner": {
41
+ "email": "username@example.com",
42
+ "id": "01234567-89ab-cdef-0123-456789abcdef"
43
+ },
44
+ "organization": {
45
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
46
+ "name": "example"
47
+ },
48
+ "team": {
49
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
50
+ "name": "example"
51
+ },
52
+ "region": {
53
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
54
+ "name": "us"
55
+ },
56
+ "released_at": "2012-01-01T12:00:00Z",
57
+ "repo_size": 0,
58
+ "slug_size": 0,
59
+ "space": {
60
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
61
+ "name": "nasa",
62
+ "shield": true
63
+ },
64
+ "stack": {
65
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
66
+ "name": "cedar-14"
67
+ },
68
+ "updated_at": "2012-01-01T12:00:00Z",
69
+ "web_url": "https://example.herokuapp.com/"
70
+ }'
71
+ }
72
+
73
+ let(:account_response_body) {
74
+ '{
75
+ "allow_tracking": true,
76
+ "beta": false,
77
+ "created_at": "2012-01-01T12:00:00Z",
78
+ "email": "username@example.com",
79
+ "federated": false,
80
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
81
+ "identity_provider": {
82
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
83
+ "organization": {
84
+ "name": "example"
85
+ }
86
+ },
87
+ "last_login": "2012-01-01T12:00:00Z",
88
+ "name": "Tina Edmonds",
89
+ "sms_number": "+1 ***-***-1234",
90
+ "suspended_at": "2012-01-01T12:00:00Z",
91
+ "delinquent_at": "2012-01-01T12:00:00Z",
92
+ "two_factor_authentication": false,
93
+ "updated_at": "2012-01-01T12:00:00Z",
94
+ "verified": false,
95
+ "default_organization": {
96
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
97
+ "name": "example"
98
+ }
99
+ }'
100
+ }
101
+
102
+ let(:builds_response_body) {
103
+ '{
104
+ "app": {
105
+ "id": "01234567-89ab-cdef-0123-456789abcdef"
106
+ },
107
+ "buildpacks": [
108
+ {
109
+ "url": "https://github.com/heroku/heroku-buildpack-ruby"
110
+ }
111
+ ],
112
+ "created_at": "2012-01-01T12:00:00Z",
113
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
114
+ "output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef",
115
+ "source_blob": {
116
+ "checksum": "SHA256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
117
+ "url": "https://example.com/source.tgz?token=xyz",
118
+ "version": "v1.3.0"
119
+ },
120
+ "release": {
121
+ "id": "01234567-89ab-cdef-0123-456789abcdef"
122
+ },
123
+ "slug": {
124
+ "id": "01234567-89ab-cdef-0123-456789abcdef"
125
+ },
126
+ "status": "succeeded",
127
+ "updated_at": "2012-01-01T12:00:00Z",
128
+ "user": {
129
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
130
+ "email": "username@example.com"
131
+ }
132
+ }'
133
+ }
134
+
135
+ let(:source_response_body) {
136
+ '{
137
+ "source_blob": {
138
+ "get_url": "https://api.heroku.com/sources/1234.tgz",
139
+ "put_url": "https://api.heroku.com/sources/1234.tgz"
140
+ }
141
+ }'
142
+ }
143
+
144
+ let(:dynos_create_response_body) {
145
+ '{
146
+ "attach_url": "rendezvous://rendezvous.runtime.heroku.com:5000/rendezvous",
147
+ "command": "bash",
148
+ "created_at": "2012-01-01T12:00:00Z",
149
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
150
+ "name": "run.1",
151
+ "release": {
152
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
153
+ "version": 11
154
+ },
155
+ "app": {
156
+ "name": "example",
157
+ "id": "01234567-89ab-cdef-0123-456789abcdef"
158
+ },
159
+ "size": "standard-1X",
160
+ "state": "up",
161
+ "type": "run",
162
+ "updated_at": "2012-01-01T12:00:00Z"
163
+ }'
164
+ }
165
+
166
+ let(:build_result_response_body) {
167
+ '{
168
+ "build": {
169
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
170
+ "status": "succeeded",
171
+ "output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
172
+ },
173
+ "exit_code": 0,
174
+ "lines": [
175
+ {
176
+ "line": "-----> Ruby app detected\n",
177
+ "stream": "STDOUT"
178
+ }
179
+ ]
180
+ }'
181
+ }
182
+
183
+ let(:build_result_response_body_failure) {
184
+ '{
185
+ "build": {
186
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
187
+ "status": "failed",
188
+ "output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
189
+ },
190
+ "exit_code": 1,
191
+ "lines": [
192
+ {
193
+ "line": "-----> Ruby app detected\n",
194
+ "stream": "STDOUT"
195
+ }
196
+ ]
197
+ }'
198
+ }
199
+
200
+ let(:build_result_response_body_in_progress) {
201
+ '{
202
+ "build": {
203
+ "id": "01234567-89ab-cdef-0123-456789abcdef",
204
+ "status": "failed",
205
+ "output_stream_url": "https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef"
206
+ },
207
+ "lines": [
208
+ {
209
+ "line": "-----> Ruby app detected\n",
210
+ "stream": "STDOUT"
211
+ }
212
+ ]
213
+ }'
214
+ }
215
+ end
216
+
217
+ describe DPL::Provider::Heroku, :api do
218
+ include_context 'with faraday'
219
+
220
+ subject(:provider) do
221
+ described_class.new(DummyContext.new, provider_options.merge({ :api_key => api_key}))
222
+ end
223
+
224
+ let(:provider_options) {
225
+ {:app => 'example', :key_name => 'key', :strategy => "api"}
226
+ }
227
+
228
+ describe "#ssh" do
229
+ it "doesn't require an ssh key" do
230
+ expect(provider.needs_key?).to eq(false)
231
+ end
232
+ end
233
+
234
+ describe "#api" do
235
+ it 'accepts an api key' do
236
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
237
+ provider.check_auth
238
+ end
239
+
240
+ context "when api_key is not given" do
241
+ let(:provider) { described_class.new(DummyContext.new, provider_options) }
242
+ it 'raises DPL::Error' do
243
+ provider.options.update(:user => "foo", :password => "bar")
244
+ expect { provider.check_auth }.to raise_error(DPL::Error)
245
+ end
246
+ end
247
+ end
248
+
249
+ describe "#trigger_build" do
250
+ it "does not initiate legacy API object" do
251
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
252
+ provider.trigger_build
253
+ end
254
+
255
+ example do
256
+ expect(provider).to receive(:log).with('triggering new deployment')
257
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
258
+ expect(provider).to receive(:get_url).and_return 'http://example.com/source.tgz'
259
+ expect(provider).to receive(:version).and_return 'v1.3.0'
260
+ expect(provider.context).to receive(:shell).with("curl https://build-output.heroku.com/streams/01234567-89ab-cdef-0123-456789abcdef -H 'Accept: application/vnd.heroku+json; version=3'")
261
+ provider.trigger_build
262
+ expect(provider.build_id).to eq('01234567-89ab-cdef-0123-456789abcdef')
263
+ end
264
+ end
265
+
266
+ describe "#verify_build" do
267
+ context 'when build succeeds' do
268
+ example do
269
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
270
+ expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
271
+ expect{ provider.verify_build }.not_to raise_error
272
+ end
273
+ end
274
+
275
+ context 'when build fails' do
276
+ example do
277
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
278
+ expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
279
+ stubs.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body_failure]}
280
+ expect{ provider.verify_build }.to raise_error("deploy failed, build exited with code 1")
281
+ end
282
+ end
283
+
284
+ context 'when build is pending, then succeeds' do
285
+ example do
286
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
287
+ expect(provider).to receive(:build_id).at_least(:once).and_return('01234567-89ab-cdef-0123-456789abcdef')
288
+ stubs.get("/apps/example/builds/01234567-89ab-cdef-0123-456789abcdef/result") {|env| [200, response_headers, build_result_response_body_in_progress]}
289
+ expect(provider).to receive(:sleep).with(5).and_return(true)
290
+ expect{ provider.verify_build }.not_to raise_error
291
+ end
292
+ end
293
+
294
+ end
295
+
296
+ end
297
+
298
+
299
+ describe DPL::Provider::Heroku, :git do
300
+ include_context "with faraday"
301
+
302
+ subject :provider do
303
+ described_class.new(DummyContext.new, :app => 'example', :key_name => 'key', :api_key => api_key, :strategy => "git")
304
+ end
305
+
306
+ describe "#api" do
307
+ it 'accepts an api key' do
308
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
309
+ provider.check_auth
310
+ end
311
+ end
312
+
313
+ context "with faraday" do
314
+ before :each do
315
+ expect(provider).to receive(:faraday).at_least(:once).and_return(faraday)
316
+ end
317
+
318
+ describe "#check_auth" do
319
+ example do
320
+ expect(provider).to receive(:log).with("authentication succeeded")
321
+ provider.check_auth
322
+ end
323
+ end
324
+
325
+ describe "#check_app" do
326
+ example do
327
+ expect(provider).to receive(:log).at_least(1).times.with(/example/)
328
+ provider.check_app
329
+ end
330
+ end
331
+
332
+ describe "#run" do
333
+ example do
334
+ expect(Rendezvous).to receive(:start).with(:url => "rendezvous://rendezvous.runtime.heroku.com:5000/rendezvous")
335
+ provider.run("that command")
336
+ end
337
+ end
338
+
339
+ describe "#restart" do
340
+ example do
341
+ provider.restart
342
+ end
343
+ end
344
+ end
345
+
346
+ context "without faraday" do
347
+ describe "#push_app" do
348
+ example do
349
+ provider.options[:git] = "git://something"
350
+ expect(provider.context).to receive(:shell).with("git fetch origin $TRAVIS_BRANCH --unshallow")
351
+ expect(provider.context).to receive(:shell).with("git push git://something HEAD:refs/heads/master -f")
352
+ provider.push_app
353
+ expect(provider.context.env['GIT_HTTP_USER_AGENT']).to include("dpl/#{DPL::VERSION}")
354
+ end
355
+ end
356
+ end
357
+ end
metadata ADDED
@@ -0,0 +1,205 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dpl-heroku
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Konstantin Haase
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dpl
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rendezvous
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: netrc
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: json_pure
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: tins
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: coveralls
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: highline
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: deploy tool abstraction for clients
168
+ email: konstantin.mailinglists@googlemail.com
169
+ executables: []
170
+ extensions: []
171
+ extra_rdoc_files: []
172
+ files:
173
+ - dpl-heroku.gemspec
174
+ - lib/dpl/provider/heroku.rb
175
+ - lib/dpl/provider/heroku/api.rb
176
+ - lib/dpl/provider/heroku/generic.rb
177
+ - lib/dpl/provider/heroku/git.rb
178
+ - notes/heroku.md
179
+ - spec/provider/heroku_spec.rb
180
+ homepage: https://github.com/travis-ci/dpl
181
+ licenses:
182
+ - MIT
183
+ metadata: {}
184
+ post_install_message:
185
+ rdoc_options: []
186
+ require_paths:
187
+ - lib
188
+ required_ruby_version: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - ">="
191
+ - !ruby/object:Gem::Version
192
+ version: '2.2'
193
+ required_rubygems_version: !ruby/object:Gem::Requirement
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ version: '0'
198
+ requirements: []
199
+ rubyforge_project:
200
+ rubygems_version: 2.6.13
201
+ signing_key:
202
+ specification_version: 4
203
+ summary: deploy tool
204
+ test_files:
205
+ - spec/provider/heroku_spec.rb