dpl 1.10.17.travis.6637.6 → 2.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +74 -0
  3. data/CONTRIBUTING.md +392 -0
  4. data/Gemfile +17 -3
  5. data/Gemfile.lock +373 -0
  6. data/LICENSE +16 -19
  7. data/NOTES.md +275 -0
  8. data/README.md +1977 -707
  9. data/Rakefile +2 -2
  10. data/bin/dpl +7 -3
  11. data/lib/dpl.rb +20 -0
  12. data/lib/dpl/assets/atlas/install +19 -0
  13. data/lib/dpl/assets/dpl/README.erb.md +133 -0
  14. data/lib/dpl/assets/dpl/git_ssh +2 -0
  15. data/lib/dpl/assets/git/detect_private_key +8 -0
  16. data/lib/dpl/assets/hephy/filter_log +3 -0
  17. data/lib/dpl/assets/pypi/install +4 -0
  18. data/lib/dpl/assets/scalingo/install +6 -0
  19. data/lib/dpl/cli.rb +36 -48
  20. data/lib/dpl/ctx.rb +2 -0
  21. data/lib/dpl/ctx/bash.rb +543 -0
  22. data/lib/dpl/ctx/test.rb +242 -0
  23. data/lib/dpl/helper/assets.rb +36 -0
  24. data/lib/dpl/helper/cmd.rb +167 -0
  25. data/lib/dpl/helper/config_file.rb +47 -0
  26. data/lib/dpl/helper/env.rb +39 -0
  27. data/lib/dpl/helper/interpolate.rb +126 -0
  28. data/lib/dpl/helper/memoize.rb +20 -0
  29. data/lib/dpl/helper/squiggle.rb +22 -0
  30. data/lib/dpl/helper/zip.rb +69 -0
  31. data/lib/dpl/provider.rb +562 -234
  32. data/lib/dpl/provider/dsl.rb +369 -0
  33. data/lib/dpl/provider/examples.rb +128 -0
  34. data/lib/dpl/provider/status.rb +59 -0
  35. data/lib/dpl/providers.rb +40 -0
  36. data/lib/dpl/providers/anynines.rb +65 -0
  37. data/lib/dpl/providers/atlas.rb +49 -0
  38. data/lib/dpl/providers/azure_web_apps.rb +59 -0
  39. data/lib/dpl/providers/bintray.rb +313 -0
  40. data/lib/dpl/providers/bluemixcloudfoundry.rb +92 -0
  41. data/lib/dpl/providers/boxfuse.rb +48 -0
  42. data/lib/dpl/providers/cargo.rb +19 -0
  43. data/lib/dpl/providers/chef_supermarket.rb +128 -0
  44. data/lib/dpl/providers/cloud66.rb +40 -0
  45. data/lib/dpl/providers/cloudfiles.rb +56 -0
  46. data/lib/dpl/providers/cloudfoundry.rb +81 -0
  47. data/lib/dpl/providers/codedeploy.rb +179 -0
  48. data/lib/dpl/providers/datica.rb +60 -0
  49. data/lib/dpl/providers/elasticbeanstalk.rb +195 -0
  50. data/lib/dpl/providers/engineyard.rb +107 -0
  51. data/lib/dpl/providers/firebase.rb +41 -0
  52. data/lib/dpl/providers/gae.rb +74 -0
  53. data/lib/dpl/providers/gcs.rb +105 -0
  54. data/lib/dpl/providers/hackage.rb +47 -0
  55. data/lib/dpl/providers/hephy.rb +101 -0
  56. data/lib/dpl/providers/heroku.rb +111 -0
  57. data/lib/dpl/providers/heroku/api.rb +119 -0
  58. data/lib/dpl/providers/heroku/git.rb +50 -0
  59. data/lib/dpl/providers/lambda.rb +202 -0
  60. data/lib/dpl/providers/launchpad.rb +74 -0
  61. data/lib/dpl/providers/netlify.rb +30 -0
  62. data/lib/dpl/providers/npm.rb +88 -0
  63. data/lib/dpl/providers/openshift.rb +46 -0
  64. data/lib/dpl/providers/opsworks.rb +142 -0
  65. data/lib/dpl/providers/packagecloud.rb +190 -0
  66. data/lib/dpl/providers/pages.rb +17 -0
  67. data/lib/dpl/providers/pages/api.rb +102 -0
  68. data/lib/dpl/providers/pages/git.rb +251 -0
  69. data/lib/dpl/providers/puppetforge.rb +44 -0
  70. data/lib/dpl/providers/pypi.rb +120 -0
  71. data/lib/dpl/providers/releases.rb +214 -0
  72. data/lib/dpl/providers/rubygems.rb +89 -0
  73. data/lib/dpl/providers/s3.rb +243 -0
  74. data/lib/dpl/providers/scalingo.rb +63 -0
  75. data/lib/dpl/providers/script.rb +28 -0
  76. data/lib/dpl/providers/snap.rb +59 -0
  77. data/lib/dpl/providers/surge.rb +55 -0
  78. data/lib/dpl/providers/testfairy.rb +93 -0
  79. data/lib/dpl/providers/transifex.rb +66 -0
  80. data/lib/dpl/support/aws_sdk_patch.rb +23 -0
  81. data/lib/dpl/support/gems.rb +69 -0
  82. data/lib/dpl/support/gstore_patch.rb +6 -0
  83. data/lib/dpl/support/version.rb +83 -0
  84. data/lib/dpl/version.rb +2 -2
  85. metadata +98 -169
  86. data/.coveralls.yml +0 -1
  87. data/.github/CONTRIBUTING.md +0 -173
  88. data/.github/stale.yml +0 -53
  89. data/.gitignore +0 -13
  90. data/.rspec +0 -2
  91. data/.travis.yml +0 -56
  92. data/dpl-anynines.gemspec +0 -3
  93. data/dpl-atlas.gemspec +0 -3
  94. data/dpl-azure_webapps.gemspec +0 -3
  95. data/dpl-bintray.gemspec +0 -3
  96. data/dpl-bitballoon.gemspec +0 -3
  97. data/dpl-bluemix_cloud_foundry.gemspec +0 -3
  98. data/dpl-boxfuse.gemspec +0 -3
  99. data/dpl-cargo.gemspec +0 -3
  100. data/dpl-catalyze.gemspec +0 -3
  101. data/dpl-chef_supermarket.gemspec +0 -20
  102. data/dpl-cloud66.gemspec +0 -3
  103. data/dpl-cloud_files.gemspec +0 -3
  104. data/dpl-cloud_foundry.gemspec +0 -3
  105. data/dpl-code_deploy.gemspec +0 -3
  106. data/dpl-deis.gemspec +0 -3
  107. data/dpl-elastic_beanstalk.gemspec +0 -3
  108. data/dpl-engine_yard.gemspec +0 -3
  109. data/dpl-firebase.gemspec +0 -3
  110. data/dpl-gae.gemspec +0 -3
  111. data/dpl-gcs.gemspec +0 -3
  112. data/dpl-hackage.gemspec +0 -3
  113. data/dpl-hephy.gemspec +0 -3
  114. data/dpl-heroku.gemspec +0 -3
  115. data/dpl-lambda.gemspec +0 -3
  116. data/dpl-launchpad.gemspec +0 -3
  117. data/dpl-npm.gemspec +0 -3
  118. data/dpl-openshift.gemspec +0 -3
  119. data/dpl-ops_works.gemspec +0 -3
  120. data/dpl-packagecloud.gemspec +0 -3
  121. data/dpl-pages.gemspec +0 -3
  122. data/dpl-puppet_forge.gemspec +0 -3
  123. data/dpl-pypi.gemspec +0 -3
  124. data/dpl-releases.gemspec +0 -8
  125. data/dpl-rubygems.gemspec +0 -3
  126. data/dpl-s3.gemspec +0 -3
  127. data/dpl-scalingo.gemspec +0 -3
  128. data/dpl-script.gemspec +0 -3
  129. data/dpl-snap.gemspec +0 -3
  130. data/dpl-surge.gemspec +0 -3
  131. data/dpl-testfairy.gemspec +0 -3
  132. data/dpl-transifex.gemspec +0 -3
  133. data/dpl.gemspec +0 -3
  134. data/gemspec_helper.rb +0 -51
  135. data/lib/dpl/error.rb +0 -3
  136. data/notes/engine_yard.md +0 -1
  137. data/notes/heroku.md +0 -3
  138. data/spec/cli_spec.rb +0 -36
  139. data/spec/provider_spec.rb +0 -191
  140. data/spec/spec_helper.rb +0 -20
@@ -0,0 +1,41 @@
1
+ module Dpl
2
+ module Providers
3
+ class Firebase < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ node_js '>= 8.0.0'
11
+
12
+ npm 'firebase-tools@^6.3', 'firebase'
13
+
14
+ path 'node_modules/.bin'
15
+
16
+ env :firebase
17
+
18
+ opt '--token TOKEN', 'Firebase CI access token (generate with firebase login:ci)', required: true, secret: true
19
+ opt '--project NAME', 'Firebase project to deploy to (defaults to the one specified in your firebase.json)'
20
+ opt '--message MSG', 'Message describing this deployment.'
21
+ opt '--only SERVICES', 'Firebase services to deploy', note: 'can be a comma-separated list'
22
+ opt '--force', 'Whether or not to delete Cloud Functions missing from the current working directory'
23
+
24
+ cmds deploy: 'firebase deploy --non-interactive %{deploy_opts}'
25
+ errs deploy: 'Firebase deployment failed'
26
+ msgs missing_config: 'Missing firebase.json'
27
+
28
+ def validate
29
+ error :missing_config unless File.exists?('firebase.json')
30
+ end
31
+
32
+ def deploy
33
+ shell :deploy
34
+ end
35
+
36
+ def deploy_opts
37
+ opts_for(%i(project message token only force))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,74 @@
1
+ module Dpl
2
+ module Providers
3
+ class Gae < Provider
4
+ status :alpha
5
+
6
+ full_name 'Google App Engine'
7
+
8
+ description sq(<<-str)
9
+ tbd
10
+ str
11
+
12
+ python '>= 2.7.9'
13
+
14
+ env :googlecloud, :cloudsdk_core, allow_skip_underscore: true
15
+
16
+ opt '--project ID', 'Project ID used to identify the project on Google Cloud', required: true
17
+ opt '--keyfile FILE', 'Path to the JSON file containing your Service Account credentials in JSON Web Token format. To be obtained via the Google Developers Console. Should be handled with care as it contains authorization keys.', default: 'service-account.json'
18
+ opt '--config FILE', 'Path to your service configuration file', type: :array, default: 'app.yaml'
19
+ opt '--version VER', 'The version of the app that will be created or replaced by this deployment. If you do not specify a version, one will be generated for you'
20
+ opt '--verbosity LEVEL', 'Adjust the log verbosity', default: 'warning'
21
+ opt '--promote', 'Do not promote the deployed version', default: true
22
+ opt '--stop_previous_version', 'Prevent your deployment from stopping the previously promoted version. This is from the future, so might not work (yet).', default: true
23
+ opt '--install_sdk', 'Do not install the Google Cloud SDK', default: true
24
+
25
+ cmds install: 'curl -L https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz | gzip -d | tar -x -C ~',
26
+ bootstrap: '~/google-cloud-sdk/bin/bootstrapping/install.py --usage-reporting=false --command-completion=false --path-update=false',
27
+ login: 'gcloud -q auth activate-service-account --key-file %{keyfile}',
28
+ deploy: 'gcloud -q app deploy %{config} %{deploy_opts}',
29
+ cat_logs: 'find $HOME/.config/gcloud/logs -type f -print -exec cat {} \;'
30
+
31
+ errs install: 'Failed to download Google Cloud SDK.',
32
+ login: 'Failed to authenticate.',
33
+ bootstrap: 'Failed bootstrap Google Cloud SDK.'
34
+
35
+ msgs failed: 'Deployment failed.'
36
+
37
+ path '~/google-cloud-sdk/bin'
38
+
39
+ def install
40
+ return unless install_sdk?
41
+ shell :install
42
+ shell :bootstrap
43
+ end
44
+
45
+ def login
46
+ shell :login
47
+ end
48
+
49
+ def deploy
50
+ shell :deploy
51
+ failed unless success?
52
+ end
53
+
54
+ private
55
+
56
+ def deploy_opts
57
+ opts = [*opts_for(%i(project verbosity version))]
58
+ opts << '--no-promote' unless promote?
59
+ opts << '--no-stop-previous-version' unless stop_previous_version?
60
+ opts.join(' ')
61
+ end
62
+
63
+ def failed
64
+ warn :failed
65
+ shell :cat_logs
66
+ error ''
67
+ end
68
+
69
+ def project
70
+ super || File.dirname(build_dir)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,105 @@
1
+ require 'kconv'
2
+
3
+ module Dpl
4
+ module Providers
5
+ class Gcs < Provider
6
+ status :alpha
7
+
8
+ full_name 'Google Cloud Store'
9
+
10
+ description sq(<<-str)
11
+ tbd
12
+ str
13
+
14
+ gem 'mime-types', '~> 3.2.2'
15
+
16
+ python '>= 2.7.9'
17
+
18
+ opt '--access_key_id ID', 'GCS Interoperable Access Key ID', required: true, secret: true
19
+ opt '--secret_access_key KEY', 'GCS Interoperable Access Secret', required: true, secret: true
20
+ opt '--bucket BUCKET', 'GCS Bucket', required: true
21
+ opt '--local_dir DIR', 'Local directory to upload from', default: '.'
22
+ opt '--upload_dir DIR', 'GCS directory to upload to'
23
+ opt '--dot_match', 'Upload hidden files starting with a dot'
24
+ opt '--acl ACL', 'Access control to set for uploaded objects'
25
+ opt '--detect_encoding', 'HTTP header Content-Encoding to set for files compressed with gzip and compress utilities.'
26
+ opt '--cache_control HEADER', 'HTTP header Cache-Control to suggest that the browser cache the file.'
27
+
28
+ cmds install: 'curl -L %{URL} | tar xz -C ~ && ~/google-cloud-sdk/install.sh --path-update false --usage-reporting false --command-completion false',
29
+ copy: 'gsutil %{gs_opts}cp %{copy_opts}-r %{source} %{target}'
30
+
31
+ msgs login: 'Authenticating with access key: %{access_key_id}'
32
+
33
+ errs copy: 'Failed uploading files.'
34
+
35
+ URL = 'https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-252.0.0-linux-x86_64.tar.gz'
36
+
37
+ BOTO = sq(<<-str)
38
+ [Credentials]
39
+ gs_access_key_id = %{access_key_id}
40
+ gs_secret_access_key = %{secret_access_key}
41
+ str
42
+
43
+ path '~/google-cloud-sdk'
44
+ move '/etc/boto.cfg'
45
+
46
+ def install
47
+ shell :install
48
+ end
49
+
50
+ def login
51
+ info :login
52
+ write_boto
53
+ end
54
+
55
+ def deploy
56
+ Dir.chdir(local_dir) do
57
+ source_files.each { |file| copy(file) }
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def write_boto
64
+ write_file '~/.boto', interpolate(BOTO, opts, secure: true), 0600
65
+ end
66
+
67
+ def source_files
68
+ Dir.glob(*glob).select { |path| File.file?(path) }
69
+ end
70
+
71
+ def copy(source)
72
+ shell :copy, gs_opts: gs_opts(source), source: source
73
+ end
74
+
75
+ def gs_opts(path)
76
+ opts = []
77
+ opts << %(-h "Cache-Control:#{cache_control}") if cache_control?
78
+ opts << %(-h "Content-Encoding:#{encoding(path)}") if detect_encoding?
79
+ opts << %(-h "Content-type:#{mime_type(path)}") if mime_type(path)
80
+ opts.join(' ') + ' ' if opts.any?
81
+ end
82
+
83
+ def copy_opts
84
+ opts = []
85
+ opts << %(-a "#{acl}") if acl?
86
+ opts.join(' ') + ' ' if opts.any?
87
+ end
88
+
89
+ def target
90
+ "gs://#{bucket}/#{upload_dir}"
91
+ end
92
+
93
+ def mime_type(path)
94
+ type = MIME::Types.type_for(path).first
95
+ type.to_s if type
96
+ end
97
+
98
+ def glob
99
+ glob = ['**/*']
100
+ glob << File::FNM_DOTMATCH if dot_match?
101
+ glob
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,47 @@
1
+ module Dpl
2
+ module Providers
3
+ class Hackage < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ opt '--username USER', 'Hackage username', required: true
11
+ opt '--password USER', 'Hackage password', required: true, secret: true
12
+ opt '--publish', 'Whether or not to publish the package'
13
+
14
+ cmds check: 'cabal check',
15
+ sdist: 'cabal sdist',
16
+ upload: 'cabal upload %{upload_opts} %{path}'
17
+
18
+ errs check: 'cabal check failed',
19
+ sdist: 'cabal sdist failed',
20
+ upload: 'cabal upload failed'
21
+
22
+ def validate
23
+ shell :check
24
+ end
25
+
26
+ def prepare
27
+ shell :sdist
28
+ end
29
+
30
+ def deploy
31
+ tar_files.each do |path|
32
+ shell :upload, path: path
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def upload_opts
39
+ opts_for(%i(publish username password))
40
+ end
41
+
42
+ def tar_files
43
+ Dir.glob('dist/*.tar.gz').sort
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ module Dpl
2
+ module Providers
3
+ class Hephy < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ opt '--controller NAME', 'Hephy controller', required: true, example: 'hephy.hephyapps.com'
11
+ opt '--username USER', 'Hephy username', required: true
12
+ opt '--password PASS', 'Hephy password', required: true, secret: true
13
+ opt '--app APP', 'Deis app', required: true
14
+ opt '--cli_version VER', 'Install a specific hephy cli version', default: 'stable'
15
+ opt '--verbose', 'Verbose log output'
16
+
17
+ needs :git, :ssh_key
18
+ path '~/.dpl'
19
+
20
+ INSTALL = 'https://raw.githubusercontent.com/teamhephy/workflow-cli/master/install-v2.sh'
21
+
22
+ cmds install: 'curl -sSL %{INSTALL} | bash -x -s %{cli_version} && mv deis ~/.dpl',
23
+ login: 'deis login %{controller} --username=%{username} --password=%{password}',
24
+ add_key: 'deis keys:add %{key}',
25
+ validate: 'deis apps:info --app=%{app}',
26
+ deploy: 'filter_log git push %{verbose} %{url} HEAD:refs/heads/master -f',
27
+ run: 'deis run -a %{app} -- %{cmd}',
28
+ remove_key: 'deis keys:remove %{key_name}'
29
+
30
+ errs login: 'Login failed.',
31
+ add_key: 'Adding keys failed.',
32
+ validate: 'Application could not be verified.',
33
+ deploy: 'Deploying application failed.',
34
+ run: 'Running command failed.',
35
+ remove_key: 'Removing keys failed.'
36
+
37
+ def install
38
+ shell :install
39
+ end
40
+
41
+ def setup
42
+ install_hephy_log_filter
43
+ end
44
+
45
+ def login
46
+ shell :login
47
+ end
48
+
49
+ def add_key(key)
50
+ shell :add_key, key: key
51
+ wait_for_ssh_access(host, port)
52
+ end
53
+
54
+ def validate
55
+ shell :validate
56
+ end
57
+
58
+ def deploy
59
+ shell :deploy
60
+ end
61
+
62
+ def run_cmd(cmd)
63
+ shell :run, app: app, cmd: cmd
64
+ end
65
+
66
+ def remove_key
67
+ shell :remove_key
68
+ end
69
+
70
+ def verbose
71
+ verbose? ? '-v' : ''
72
+ end
73
+
74
+ def host
75
+ url.host
76
+ end
77
+
78
+ def port
79
+ url.port
80
+ end
81
+
82
+ def url
83
+ @url ||= URI.parse("ssh://git@#{builder}:2222/#{app}.git")
84
+ end
85
+
86
+ def builder
87
+ parts = host.split('.')
88
+ parts[0] = [parts[0], 'builder'].join('-')
89
+ parts.join('.')
90
+ end
91
+
92
+ def host
93
+ controller.gsub(/https?:\/\//, '').split(':')[0]
94
+ end
95
+
96
+ def install_hephy_log_filter
97
+ asset(:filter_log).copy('~/.dpl/')
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,111 @@
1
+ module Dpl
2
+ module Providers
3
+ class Heroku < Provider
4
+ def self.new(ctx, args)
5
+ # can this be a generic dispatch feature in Cl?
6
+ return super unless registry_key.to_sym == :heroku
7
+ arg = args.detect { |arg| arg.include?('--strategy') }
8
+ strategy = arg ? arg.split('=').last : 'api'
9
+ Provider[:"heroku:#{strategy}"].new(ctx, args)
10
+ end
11
+
12
+ gem 'faraday', '~> 0.9.2'
13
+ gem 'json'
14
+ gem 'netrc', '~> 0.11.0'
15
+ gem 'rendezvous', '~> 0.1.3'
16
+
17
+ opt '--strategy NAME', 'Heroku deployment strategy', default: 'api', enum: %w(api git), internal: true
18
+ opt '--app APP', 'Heroku app name', default: :repo_name
19
+ opt '--log_level LEVEL', internal: true
20
+
21
+ msgs login: 'Authenticating ... ',
22
+ restart: 'Restarting dynos ... ',
23
+ validate: 'Checking for app %{app} ... ',
24
+ run_cmd: 'Running command %s ... ',
25
+ success: 'success.',
26
+ api_error: 'API request failed: %s (see %s)'
27
+
28
+ URL = 'https://api.heroku.com'
29
+
30
+ HEADERS = {
31
+ 'Accept': 'application/vnd.heroku+json; version=3',
32
+ 'User-Agent': user_agent,
33
+ }
34
+
35
+ attr_reader :email
36
+
37
+ def login
38
+ print :login
39
+ res = http.get('/account')
40
+ handle_error(res) unless res.success?
41
+ @email = JSON.parse(res.body)["email"]
42
+ info :success
43
+ end
44
+
45
+ def validate
46
+ print :validate
47
+ res = http.get("/apps/#{app}")
48
+ handle_error(res) unless res.success?
49
+ info :success
50
+ end
51
+
52
+ def restart
53
+ print :restart
54
+ res = http.delete "/apps/#{app}/dynos" do |req|
55
+ req.headers['Content-Type'] = 'application/json'
56
+ end
57
+ handle_error(res) unless res.success?
58
+ info :success
59
+ end
60
+
61
+ def run_cmd(cmd)
62
+ print :run_cmd, cmd
63
+ res = http.post "/apps/#{app}/dynos" do |req|
64
+ req.headers['Content-Type'] = 'application/json'
65
+ req.body = { command: cmd, attach: true}.to_json
66
+ end
67
+ handle_error(res) unless res.success?
68
+ rendezvous(JSON.parse(res.body)['attach_url'])
69
+ end
70
+
71
+ private
72
+
73
+ def http
74
+ @http ||= Faraday.new(url: URL, headers: headers) do |http|
75
+ http.basic_auth(username, password) if username && password
76
+ http.response :logger, logger, &method(:filter) if log_level?
77
+ http.adapter Faraday.default_adapter
78
+ end
79
+ end
80
+
81
+ def headers
82
+ return HEADERS.dup if username && password
83
+ HEADERS.merge('Authorization': "Bearer #{api_key}")
84
+ end
85
+
86
+ def filter(logger)
87
+ logger.filter(/(.*Authorization: ).*/,'\1[REDACTED]')
88
+ end
89
+
90
+ def logger
91
+ super(log_level)
92
+ end
93
+
94
+ def handle_error(response)
95
+ body = JSON.parse(response.body)
96
+ error :api_error, body['message'], body['url']
97
+ end
98
+
99
+ def rendezvous(url)
100
+ Rendezvous.start(url: url)
101
+ end
102
+
103
+ # overwritten in Git, meaningless in Api
104
+ def username; end
105
+ def password; end
106
+ end
107
+ end
108
+ end
109
+
110
+ require 'dpl/providers/heroku/api'
111
+ require 'dpl/providers/heroku/git'