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,59 @@
1
+ module Dpl
2
+ class Provider < Cl::Cmd
3
+ class Status < Struct.new(:provider, :status, :info)
4
+ STATUS = %i(dev alpha beta stable deprecated)
5
+
6
+ MSG = {
7
+ dev: 'Support for deployments to %s is in development',
8
+ alpha: 'Support for deployments to %s is in alpha',
9
+ beta: 'Support for deployments to %s is in beta',
10
+ deprecated: 'Support for deployments to %s is deprecated',
11
+ pre_stable: 'Please see here: %s'
12
+ }
13
+
14
+ URL = 'https://github.com/travis-ci/dpl/#maturity-levels'
15
+
16
+ def initialize(provider, status, info)
17
+ unknown!(status) unless known?(status)
18
+ super
19
+ end
20
+
21
+ def announce?
22
+ !stable?
23
+ end
24
+
25
+ def msg
26
+ msg = "#{MSG[status] % name}"
27
+ msg << "(#{info})" if info
28
+ msg << ". #{MSG[:pre_stable] % URL}" if pre_stable?
29
+ "\n#{msg}\n"
30
+ end
31
+
32
+ private
33
+
34
+ def name
35
+ provider.full_name
36
+ end
37
+
38
+ def pre_stable?
39
+ STATUS.index(status) < STATUS.index(:stable)
40
+ end
41
+
42
+ def stable?
43
+ status == :stable
44
+ end
45
+
46
+ def deprecated?
47
+ status == :deprecated
48
+ end
49
+
50
+ def known?(status)
51
+ STATUS.include?(status)
52
+ end
53
+
54
+ def unknown!(status)
55
+ raise "Unknown status: #{status.inspect}. Known statuses are: #{STATUS.map(&:inspect).join(', ')}"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,40 @@
1
+ require 'dpl/providers/anynines'
2
+ require 'dpl/providers/atlas'
3
+ require 'dpl/providers/azure_web_apps'
4
+ require 'dpl/providers/bintray'
5
+ require 'dpl/providers/boxfuse'
6
+ require 'dpl/providers/bluemixcloudfoundry'
7
+ require 'dpl/providers/cargo'
8
+ require 'dpl/providers/datica'
9
+ require 'dpl/providers/chef_supermarket'
10
+ require 'dpl/providers/cloud66'
11
+ require 'dpl/providers/cloudfiles'
12
+ require 'dpl/providers/cloudfoundry'
13
+ require 'dpl/providers/codedeploy'
14
+ require 'dpl/providers/elasticbeanstalk'
15
+ require 'dpl/providers/engineyard'
16
+ require 'dpl/providers/firebase'
17
+ require 'dpl/providers/gae'
18
+ require 'dpl/providers/gcs'
19
+ require 'dpl/providers/hackage'
20
+ require 'dpl/providers/hephy'
21
+ require 'dpl/providers/heroku'
22
+ require 'dpl/providers/lambda'
23
+ require 'dpl/providers/launchpad'
24
+ require 'dpl/providers/netlify'
25
+ require 'dpl/providers/npm'
26
+ require 'dpl/providers/openshift'
27
+ require 'dpl/providers/opsworks'
28
+ require 'dpl/providers/packagecloud'
29
+ require 'dpl/providers/pages'
30
+ require 'dpl/providers/puppetforge'
31
+ require 'dpl/providers/pypi'
32
+ require 'dpl/providers/releases'
33
+ require 'dpl/providers/rubygems'
34
+ require 'dpl/providers/s3'
35
+ require 'dpl/providers/scalingo'
36
+ require 'dpl/providers/script'
37
+ require 'dpl/providers/snap'
38
+ require 'dpl/providers/surge'
39
+ require 'dpl/providers/testfairy'
40
+ require 'dpl/providers/transifex'
@@ -0,0 +1,65 @@
1
+ module Dpl
2
+ module Providers
3
+ class Anynines < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ opt '--username USER', 'anynines username', required: true
11
+ opt '--password PASS', 'anynines password', required: true, secret: true
12
+ opt '--organization ORG', 'anynines target organization', required: true
13
+ opt '--space SPACE', 'anynines target space', required: true
14
+ opt '--app_name APP', 'Application name'
15
+ opt '--buildpack PACK', 'Custom buildpack name or Git URL'
16
+ opt '--manifest FILE', 'Path to the manifest'
17
+ opt '--logout', default: true, internal: true
18
+
19
+ API = 'https://api.de.a9s.eu'
20
+
21
+ cmds install: 'test $(uname) = "Linux" && rel="linux64-binary" || rel="macosx64"; wget "https://cli.run.pivotal.io/stable?release=${rel}&source=github" -qO cf.tgz && tar -zxvf cf.tgz && rm cf.tgz',
22
+ api: './cf api %{url}',
23
+ login: './cf login -u %{username} -p %{password} -o %{organization} -s %{space}',
24
+ push: './cf push %{args}',
25
+ logout: './cf logout'
26
+
27
+ errs install: 'Failed to install CLI tools',
28
+ api: 'Failed to set api',
29
+ login: 'Failed to login',
30
+ target: 'Failed to target organization %{organization}, space %{space}',
31
+ push: 'Failed to push app',
32
+ logout: 'Failed to logout'
33
+
34
+ def install
35
+ shell :install
36
+ end
37
+
38
+ def login
39
+ shell :api
40
+ shell :login
41
+ end
42
+
43
+ def deploy
44
+ shell :push
45
+ end
46
+
47
+ def finish
48
+ shell :logout if logout?
49
+ end
50
+
51
+ private
52
+
53
+ def url
54
+ API
55
+ end
56
+
57
+ def args
58
+ args = []
59
+ args << quote(app_name) if app_name?
60
+ args << "-f #{manifest}" if manifest?
61
+ args.join(' ')
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,49 @@
1
+ module Dpl
2
+ module Providers
3
+ class Atlas < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ opt '--app APP', 'The Atlas application to upload to', required: true
11
+ opt '--token TOKEN', 'The Atlas API token', required: true, secret: true
12
+ opt '--paths PATH', 'Files or directories to upload', type: :array, default: ['.']
13
+ opt '--address ADDR', 'The address of the Atlas server'
14
+ opt '--include GLOB', 'Glob pattern of files or directories to include', type: :array
15
+ opt '--exclude GLOB', 'Glob pattern of files or directories to exclude', type: :array
16
+ opt '--metadata DATA', 'Arbitrary key=value (string) metadata to be sent with the upload', type: :array
17
+ opt '--vcs', 'Get lists of files to exclude and include from a VCS (Git, Mercurial or SVN)'
18
+ opt '--args ARGS', 'Args to pass to the atlas-upload CLI'
19
+ opt '--debug', 'Turn on debug output'
20
+
21
+ cmds upload: 'atlas-upload %{args} %{app} %{path}'
22
+
23
+ def setup
24
+ ENV['ATLAS_TOKEN'] = token
25
+ end
26
+
27
+ def install
28
+ script :install
29
+ end
30
+
31
+ def deploy
32
+ paths.each { |path| upload(path) }
33
+ end
34
+
35
+ private
36
+
37
+ def upload(path)
38
+ shell :upload, path: path
39
+ # shell ['atlas-upload', args, app, path].compact.join(' ')
40
+ end
41
+
42
+ ARGS = %i(address exclude include metadata vcs debug)
43
+
44
+ def args
45
+ super || opts_for(ARGS, prefix: '-')
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,59 @@
1
+ module Dpl
2
+ module Providers
3
+ class AzureWebApps < Provider
4
+ status :alpha
5
+
6
+ full_name 'Azure Web Apps'
7
+
8
+ description sq(<<-str)
9
+ tbd
10
+ str
11
+
12
+ env :AZURE_WA
13
+
14
+ opt '--site SITE', 'Web App name (e.g. myapp in myapp.azurewebsites.net)', required: true
15
+ opt '--username NAME', 'Web App Deployment Username', required: true
16
+ opt '--password PASS', 'Web App Deployment Password', required: true, secret: true
17
+ opt '--slot SLOT', 'Slot name (if your app uses staging deployment)'
18
+ opt '--verbose', 'Print deployment output from Azure. Warning: If authentication fails, Git prints credentials in clear text. Correct credentials remain hidden.'
19
+
20
+ needs :git
21
+
22
+ cmds checkout: 'git checkout HEAD',
23
+ add: 'git add . --all --force',
24
+ commit: 'git commit -m "Cleanup commit"',
25
+ deploy: 'git push --force --quiet %{url} HEAD:refs/heads/master'
26
+
27
+ msgs commit: 'Committing changes to git',
28
+ deploy: 'Deploying to Azure Web App: %{site}'
29
+
30
+ errs push: 'Failed pushing to Azure Web Apps'
31
+
32
+ URL = 'https://%s:%s@%s.scm.azurewebsites.net:443/%s.git'
33
+
34
+ def setup
35
+ commit if git_dirty? && !cleanup?
36
+ end
37
+
38
+ def deploy
39
+ shell :deploy, silence: !verbose?
40
+ end
41
+
42
+ private
43
+
44
+ def url
45
+ URL % [username, password, target, site]
46
+ end
47
+
48
+ def target
49
+ slot || site
50
+ end
51
+
52
+ def commit
53
+ shell :checkout
54
+ shell :add
55
+ shell :commit
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,313 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'find'
4
+
5
+ module Dpl
6
+ module Providers
7
+ class Bintray < Provider
8
+ status :alpha
9
+
10
+ description sq(<<-str)
11
+ tbd
12
+ str
13
+
14
+ gem 'json'
15
+
16
+ opt '--user USER', 'Bintray user', required: true
17
+ opt '--key KEY', 'Bintray API key', required: true, secret: true
18
+ opt '--file FILE', 'Path to a descriptor file for the Bintray upload', required: true
19
+ opt '--passphrase PHRASE', 'Passphrase as configured on Bintray (if GPG signing is used)'
20
+ opt '--url URL', default: 'https://api.bintray.com', internal: true
21
+
22
+ msgs missing_file: 'Missing descriptor file: %{file}',
23
+ invalid_file: 'Failed to parse descriptor file %{file}',
24
+ create_package: 'Creating package %{package_name}',
25
+ package_attrs: 'Adding attributes for package %{package_name}',
26
+ create_version: 'Creating version %{version_name}',
27
+ version_attrs: 'Adding attributes for version %{version_name}',
28
+ upload_file: 'Uploading file %{source} to %{target}',
29
+ sign_version: 'Signing version %s passphrase',
30
+ publish_version: 'Publishing version %{version_name} of package %{package_name}',
31
+ missing_path: 'Path: %{path} does not exist.',
32
+ list_download: 'Listing %{path} in downloads',
33
+ retrying: '%{code} response from Bintray. It may take some time for a version to be published, retrying in %{pause} sec ... (%{count}/%{max})',
34
+ giveup_retries: 'Too many retries failed, giving up, something went wrong.',
35
+ unexpected_code: 'Unexpected HTTP response code %s while checking if the %s exists' ,
36
+ request_failed: '%s %s returned unexpected HTTP response code %s',
37
+ request_success: 'Bintray response: %s %s. %s'
38
+
39
+ PATHS = {
40
+ packages: '/packages/%{subject}/%{repo}',
41
+ package: '/packages/%{subject}/%{repo}/%{package_name}',
42
+ package_attrs: '/packages/%{subject}/%{repo}/%{package_name}/attributes',
43
+ versions: '/packages/%{subject}/%{repo}/%{package_name}/versions',
44
+ version: '/packages/%{subject}/%{repo}/%{package_name}/versions/%{version_name}',
45
+ version_attrs: '/packages/%{subject}/%{repo}/%{package_name}/versions/%{version_name}/attributes',
46
+ version_sign: '/gpg/%{subject}/%{repo}/%{package_name}/versions/%{version_name}',
47
+ version_publish: '/content/%{subject}/%{repo}/%{package_name}/%{version_name}/publish',
48
+ version_file: '/content/%{subject}/%{repo}/%{package_name}/%{version_name}/%{target}',
49
+ file_metadata: '/file_metadata/%{subject}/%{repo}/%{target}'
50
+ }
51
+
52
+ MAP = {
53
+ package: %i(name desc licenses labels vcs_url website_url
54
+ issue_tracker_url public_download_numbers public_stats),
55
+ version: %i(name desc released vcs_tag github_release_notes_file
56
+ github_use_tag_release_notes attributes)
57
+ }
58
+
59
+ def install
60
+ require 'json'
61
+ end
62
+
63
+ def validate
64
+ error :missing_file unless File.exist?(file)
65
+ # validate that the repo exists, and we have access
66
+ end
67
+
68
+ def deploy
69
+ create_package unless package_exists?
70
+ create_version unless version_exists?
71
+ upload_files
72
+ sign_version if sign_version?
73
+ publish_version && update_files if publish_version?
74
+ end
75
+
76
+ def package_exists?
77
+ exists?(:package)
78
+ end
79
+
80
+ def create_package
81
+ info :create_package
82
+ post(path(:packages), compact(only(package, *MAP[:package])))
83
+ return unless package_attrs
84
+ info :package_attrs
85
+ post(path(:package_attrs), package_attrs)
86
+ end
87
+
88
+ def version_exists?
89
+ exists?(:version)
90
+ end
91
+
92
+ def create_version
93
+ info :create_version
94
+ post(path(:versions), compact(only(version, *MAP[:version])))
95
+ return unless version_attrs
96
+ info :version_attrs
97
+ post(path(:version_attrs), version_attrs)
98
+ end
99
+
100
+ def upload_files
101
+ files.each do |file|
102
+ info :upload_file, source: file.source, target: file.target
103
+ put(path(:version_file, target: file.target), file.read, file.params)
104
+ end
105
+ end
106
+
107
+ def sign_version
108
+ body = compact(passphrase: passphrase)
109
+ info :sign_version, (passphrase? ? 'with' : 'without')
110
+ post(path(:version_sign), body)
111
+ end
112
+
113
+ def publish_version
114
+ info :publish_version
115
+ post(path(:version_publish))
116
+ end
117
+
118
+ def update_files
119
+ files.select(&:download).each do |file|
120
+ info :list_download, path: file.target
121
+ update_file(file)
122
+ end
123
+ end
124
+
125
+ def update_file(file)
126
+ retrying(max: 10, pause: 5) do
127
+ body = { list_in_downloads: file.download }.to_json
128
+ headers = { 'Content-Type': 'application/json' }
129
+ put(path(:file_metadata, target: file.target), body, {}, headers)
130
+ end
131
+ end
132
+
133
+ def retrying(opts, &block)
134
+ 1.upto(opts[:max]) do |count|
135
+ code = yield
136
+ return if code < 400
137
+ info :retrying, opts.merge(count: count, code: code)
138
+ sleep opts[:pause]
139
+ end
140
+ error :giveup_retries
141
+ end
142
+
143
+ def files
144
+ return {} unless files = descriptor[:files]
145
+ return @files if @files
146
+ keys = %i(path includePattern excludePattern uploadPattern matrixParams listInDownloads)
147
+ files = files.map { |file| file if file[:path] = path_for(file[:includePattern]) }
148
+ @files = files.compact.map { |file| find(*file.values_at(*keys)) }.flatten
149
+ end
150
+
151
+ def find(path, includes, excludes, uploads, params, download)
152
+ paths = Find.find(path).select { |path| File.file?(path) }
153
+ paths = paths.reject { |path| excluded?(path, excludes) }
154
+ paths = paths.map { |path| [path, path.match(/#{includes}/)] }
155
+ paths = paths.select(&:last)
156
+ paths.map { |path, match| Upload.new(path, fmt(uploads, match.captures), params, download) }
157
+ end
158
+
159
+ def fmt(pattern, captures)
160
+ captures.each.with_index.inject(pattern) do |pattern, (capture, ix)|
161
+ pattern.gsub("$#{ix + 1}", capture)
162
+ end
163
+ end
164
+
165
+ def excluded?(path, pattern)
166
+ !pattern.to_s.empty? && path.match(/#{pattern}/)
167
+ end
168
+
169
+ def path_for(str)
170
+ ix = str.index('(')
171
+ path = ix.to_i == 0 ? str : str[0, ix]
172
+ return path if File.exist?(path)
173
+ warn :missing_path, path: path
174
+ nil
175
+ end
176
+
177
+ def exists?(type)
178
+ case code = head(path(type), raise: false, silent: true)
179
+ when 200, 201 then true
180
+ when 404 then false
181
+ else error :unexpected_code, code, type
182
+ end
183
+ end
184
+
185
+ def head(path, opts = {})
186
+ req = Net::HTTP::Head.new(path)
187
+ req.basic_auth(user, key)
188
+ request(req, opts)
189
+ end
190
+
191
+ def post(path, body = nil)
192
+ req = Net::HTTP::Post.new(path)
193
+ req.add_field('Content-Type', 'application/json')
194
+ req.basic_auth(user, key)
195
+ req.body = JSON.dump(body) if body
196
+ request(req)
197
+ end
198
+
199
+ def put(path, body, params = {}, headers = {})
200
+ req = Net::HTTP::Put.new(append_params(path, params))
201
+ headers.each { |key, value| req.add_field(key.to_s, value) }
202
+ req.basic_auth(user, key)
203
+ req.body = body
204
+ request(req)
205
+ end
206
+
207
+ def request(req, opts = {})
208
+ res = http.request(req)
209
+ handle(req, res, opts)
210
+ res.code.to_i
211
+ end
212
+
213
+ def http
214
+ http = Net::HTTP.new(url.host, url.port)
215
+ http.use_ssl = true
216
+ http
217
+ end
218
+
219
+ def append_params(path, params)
220
+ [path, *Array(params).map { |pair| pair.join('=') }].join(';')
221
+ end
222
+
223
+ def handle(req, res, opts = { raise: true })
224
+ error :request_failed, req.method, req.uri, res.code if opts[:raise] && !success?(res.code)
225
+ info :request_success, res.code, res.message, parse(res)['message'] unless opts[:silent]
226
+ res.code.to_i
227
+ end
228
+
229
+ def success?(code)
230
+ code.to_s[0].to_i == 2
231
+ end
232
+
233
+ def descriptor
234
+ @descriptor ||= symbolize(JSON.parse(File.read(file)))
235
+ rescue => e
236
+ error :invalid_file
237
+ end
238
+
239
+ def url
240
+ @url ||= URI.parse(super || URL)
241
+ end
242
+
243
+ def package
244
+ descriptor[:package]
245
+ end
246
+
247
+ def package_name
248
+ package[:name]
249
+ end
250
+
251
+ def package_attrs
252
+ package[:attributes]
253
+ end
254
+
255
+ def subject
256
+ package[:subject]
257
+ end
258
+
259
+ def repo
260
+ package[:repo]
261
+ end
262
+
263
+ def version
264
+ descriptor[:version]
265
+ end
266
+
267
+ def version_name
268
+ version[:name]
269
+ end
270
+
271
+ def version_attrs
272
+ version[:attributes]
273
+ end
274
+
275
+ def sign_version?
276
+ version[:gpgSign]
277
+ end
278
+
279
+ def publish_version?
280
+ descriptor[:publish]
281
+ end
282
+
283
+ def path(resource, args = {})
284
+ interpolate(PATHS[resource], args, secure: true)
285
+ end
286
+
287
+ def parse(json)
288
+ hash = JSON.parse(json)
289
+ hash.is_a?(Hash) ? hash : {}
290
+ rescue
291
+ {}
292
+ end
293
+
294
+ def compact(hash)
295
+ hash.reject { |_, value| value.nil? }
296
+ end
297
+
298
+ def only(hash, *keys)
299
+ hash.select { |key, _| keys.include?(key) }
300
+ end
301
+
302
+ class Upload < Struct.new(:source, :target, :params, :download)
303
+ def read
304
+ IO.read(source)
305
+ end
306
+
307
+ def eql?(other)
308
+ source == other.source
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end