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,214 @@
1
+ module Dpl
2
+ module Providers
3
+ class Releases < Provider
4
+ status :alpha
5
+
6
+ full_name 'GitHub Releases'
7
+
8
+ description sq(<<-str)
9
+ tbd
10
+ str
11
+
12
+ gem 'octokit', '~> 4.14.0'
13
+ gem 'mime-types', '~> 3.2.2'
14
+ gem 'public_suffix', '~> 3.0.3'
15
+
16
+ required :api_key, [:user, :password]
17
+
18
+ opt '--api_key TOKEN', 'GitHub oauth token (needs public_repo or repo permission)', secret: true
19
+ opt '--username LOGIN', 'GitHub login name', alias: :user
20
+ opt '--password PASS', 'GitHub password', secret: true
21
+ opt '--repo SLUG', 'GitHub repo slug', default: :repo_slug
22
+ opt '--file FILE', 'File to release to GitHub', required: true, type: :array
23
+ opt '--file_glob', 'Interpret files as globs'
24
+ opt '--overwrite', 'Overwrite files with the same name'
25
+ opt '--prerelease', 'Identify the release as a prerelease'
26
+ opt '--release_number NUM', 'Release number (overide automatic release detection)'
27
+ opt '--release_notes STR', 'Content for the release notes', alias: :body
28
+ opt '--release_notes_file PATH', 'Path to a file containing the release notes', note: 'will be ignored if --release_notes is given'
29
+ opt '--draft', 'Identify the release as a draft'
30
+ opt '--tag_name TAG', 'Git tag from which to create the release'
31
+ opt '--target_commitish STR', 'Commitish value that determines where the Git tag is created from'
32
+ opt '--name NAME', 'Name for the release'
33
+ # should this have --github_url, like Pages does?
34
+
35
+ needs :git
36
+
37
+ msgs deploy: 'Deploying to repo: %{slug}',
38
+ local_tag: 'Current tag is: %{local_tag}',
39
+ login: 'Authenticated as %s',
40
+ insufficient_scopes: 'Dpl does not have permission to upload assets. Make sure your token has the repo or public_repo scope.',
41
+ insufficient_perm: 'Release resource not found. Make sure your token belongs to an account which has push permission to this repo.',
42
+ overwrite_existing: 'File %s already exists, overwriting.',
43
+ skip_existing: 'File %s already exists, skipping.',
44
+ set_tag_name: 'Setting tag_name to %s',
45
+ set_target_commitish: 'Setting target_commitish to %s',
46
+ missing_file: 'File %s does not exist.',
47
+ not_a_file: '%s is not a file, skipping.'
48
+
49
+ cmds git_fetch_tags: 'git fetch --tags'
50
+
51
+ URL = 'https://api.github.com/repos/%s/releases/%s'
52
+
53
+ OCTOKIT_OPTS = %i(
54
+ repo
55
+ name
56
+ body
57
+ prerelease
58
+ release_number
59
+ tag_name
60
+ target_commitish
61
+ )
62
+
63
+ TIMEOUTS = {
64
+ timeout: 180,
65
+ open_timeout: 180
66
+ }
67
+
68
+ def validate
69
+ info :deploy
70
+ shell :git_fetch_tags if env_tag.nil?
71
+ info :local_tag
72
+ end
73
+
74
+ def login
75
+ user.login
76
+ info :login, user.login
77
+ error :insufficient_scopes unless sufficient_scopes?
78
+ end
79
+
80
+ def deploy
81
+ upload_files
82
+ api.update_release(url, octokit_opts)
83
+ end
84
+
85
+ def upload_files
86
+ files.each { |file| upload_file(file) }
87
+ end
88
+
89
+ def upload_file(file)
90
+ asset = asset(file)
91
+ return info :skip_existing, file if asset && !overwrite?
92
+ delete(asset, file) if asset
93
+ api.upload_asset(url, file, name: File.basename(file), content_type: content_type(file))
94
+ end
95
+
96
+ def delete(asset, file)
97
+ info :overwrite_existing, file
98
+ api.delete_release_asset(asset.url)
99
+ end
100
+
101
+ def octokit_opts
102
+ opts = with_tag(self.opts.dup)
103
+ opts = with_target_commitish(opts)
104
+ opts = opts.select { |key, _| OCTOKIT_OPTS.include?(key) }
105
+ compact(opts.merge(body: release_notes, draft: draft?))
106
+ end
107
+
108
+ def with_tag(opts)
109
+ return opts if tag_name? || draft?
110
+ info :set_tag_name, local_tag
111
+ opts.merge(tag_name: local_tag)
112
+ end
113
+
114
+ def with_target_commitish(opts)
115
+ return opts if target_commitish? || !same_repo?
116
+ info :set_target_commitish, git_sha
117
+ opts.merge(target_commitish: git_sha)
118
+ end
119
+
120
+ def content_type(file)
121
+ type = MIME::Types.type_for(file).first
122
+ type ||= 'application/octet-stream'
123
+ type.to_s
124
+ end
125
+
126
+ def url
127
+ if release_number?
128
+ URL % [slug, release_number]
129
+ elsif release
130
+ release.rels[:self].href
131
+ else
132
+ create_release.rels[:self].href
133
+ end
134
+ end
135
+
136
+ def release
137
+ releases.detect { |release| release.tag_name == local_tag }
138
+ end
139
+
140
+ def create_release
141
+ api.create_release(slug, local_tag, octokit_opts.merge(draft: true))
142
+ rescue Octokit::NotFound => nf
143
+ error :insufficient_perm
144
+ end
145
+
146
+ def local_tag
147
+ env_tag || git_tag
148
+ end
149
+
150
+ def env_tag
151
+ tag = ENV['TRAVIS_TAG']
152
+ tag unless tag.to_s.empty?
153
+ end
154
+
155
+ def sufficient_scopes?
156
+ api.scopes.include?('public_repo') || api.scopes.include?('repo')
157
+ end
158
+
159
+ def slug
160
+ repo || repo_slug
161
+ end
162
+
163
+ def same_repo?
164
+ slug == repo_slug
165
+ end
166
+
167
+ def asset(path)
168
+ api.release_assets(url).detect { |asset| asset.name == path }
169
+ end
170
+
171
+ def release_notes
172
+ super || release_notes_file || nil
173
+ end
174
+
175
+ def release_notes_file
176
+ release_notes_file? && exists?(super) && read(super)
177
+ end
178
+
179
+ def user
180
+ @user ||= api.user
181
+ end
182
+
183
+ def releases
184
+ @releases ||= api.releases(slug)
185
+ end
186
+
187
+ def api
188
+ @api ||= Octokit::Client.new(**creds, auto_paginate: true, connection_options: { request: TIMEOUTS })
189
+ end
190
+
191
+ def creds
192
+ username && password ? { login: username, password: password } : { access_token: api_key }
193
+ end
194
+
195
+ def files
196
+ files = file_glob? ? Dir.glob("{#{file.join(',')}}").uniq : file
197
+ files = files.select { |file| exists?(file) }
198
+ files.select { |file| file?(file) }
199
+ end
200
+
201
+ def exists?(file)
202
+ return true if File.exists?(file)
203
+ error :missing_file, file
204
+ false
205
+ end
206
+
207
+ def file?(file)
208
+ return true if File.file?(file)
209
+ warn :not_a_file, file
210
+ false
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,89 @@
1
+ module Dpl
2
+ module Providers
3
+ class Rubygems < Provider
4
+ status :alpha
5
+
6
+ description sq(<<-str)
7
+ tbd
8
+ str
9
+
10
+ gem 'gems', '~> 1.1.1'
11
+
12
+ required :api_key, [:user, :password]
13
+
14
+ opt '--api_key KEY', 'Rubygems api key', secret: true
15
+ opt '--username USER', 'Rubygems user name', alias: :user
16
+ opt '--password PASS', 'Rubygems password', secret: true
17
+ opt '--gem NAME', 'Name of the gem to release', default: :repo_name
18
+ opt '--gemspec FILE', 'Gemspec file to use to build the gem'
19
+ opt '--gemspec_glob GLOB', 'Glob pattern to search for gemspec files when multiple gems are generated in the repository (overrides the gemspec option)'
20
+ opt '--host URL'
21
+
22
+ msgs login_api_key: 'Authenticating with api key %{api_key}',
23
+ login_creds: 'Authenticating with username %{username} and password %{password}',
24
+ setup: 'Setting up host %{host}',
25
+ gem_lookup: 'Looking up gem %{gem} ... ',
26
+ gem_found: 'found.',
27
+ gem_not_found: 'no such gem.',
28
+ gem_push: 'Pushing gem %{gem}'
29
+
30
+ cmds gem_build: 'gem build %{gemspec}'
31
+
32
+ errs gem_build: 'Failed to build %{gemspec}'
33
+
34
+ def setup
35
+ return unless host?
36
+ info :setup
37
+ Gems.host = host
38
+ end
39
+
40
+ def login
41
+ api_key? ? login_api_key : login_creds
42
+ end
43
+
44
+ def validate
45
+ print :gem_lookup
46
+ name = Gems.info(gem)['name']
47
+ info name ? :gem_found : :gem_not_found
48
+ end
49
+
50
+ def deploy
51
+ build
52
+ push
53
+ end
54
+
55
+ private
56
+
57
+ def login_api_key
58
+ info :login_api_key
59
+ Gems.key = api_key
60
+ end
61
+
62
+ def login_creds
63
+ info :login_creds
64
+ Gems.username, Gems.password = username, password
65
+ end
66
+
67
+ def build
68
+ Dir[gemspec_glob].each do |gemspec|
69
+ shell :gem_build, gemspec: gemspec.untaint
70
+ end
71
+ end
72
+
73
+ def push
74
+ Dir["#{gem}-*.gem"].each do |file|
75
+ info :gem_push, gem: file.untaint
76
+ info Gems.push(File.new(file), *[host].compact)
77
+ end
78
+ end
79
+
80
+ def gemspec_glob
81
+ super || "#{gemspec || gem}.gemspec"
82
+ end
83
+
84
+ def gemspec
85
+ super.gsub('.gemspec', '') if gemspec?
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,243 @@
1
+ require 'uri'
2
+
3
+ # we want this, don't we?
4
+ Thread.abort_on_exception = true
5
+
6
+ module Dpl
7
+ module Providers
8
+ class S3 < Provider
9
+ status :alpha
10
+
11
+ full_name 'AWS S3'
12
+
13
+ description sq(<<-str)
14
+ tbd
15
+ str
16
+
17
+ gem 'aws-sdk-s3', '~> 1.0'
18
+ gem 'mime-types', '~> 3.2.2'
19
+
20
+ env :aws
21
+ config '~/.aws/credentials', '~/.aws/config', prefix: 'aws'
22
+
23
+ opt '--access_key_id ID', 'AWS access key id', required: true, secret: true
24
+ opt '--secret_access_key KEY', 'AWS secret key', required: true, secret: true
25
+ opt '--bucket BUCKET', 'S3 bucket', required: true
26
+ opt '--region REGION', 'S3 region', default: 'us-east-1'
27
+ opt '--endpoint URL', 'S3 endpoint'
28
+ opt '--upload_dir DIR', 'S3 directory to upload to'
29
+ opt '--storage_class CLASS', 'S3 storage class to upload as', default: 'STANDARD', enum: %w(STANDARD STANDARD_IA REDUCED_REDUNDANCY)
30
+ opt '--server_side_encryption', 'Use S3 Server Side Encryption (SSE-AES256)'
31
+ opt '--local_dir DIR', 'Local directory to upload from', default: '.', example: '~/travis/build (absolute path) or ./build (relative path)'
32
+ opt '--detect_encoding', 'HTTP header Content-Encoding for files compressed with gzip and compress utilities'
33
+ opt '--cache_control STR', 'HTTP header Cache-Control to suggest that the browser cache the file', type: :array, default: 'no-cache', enum: [/^no-cache.*/, /^no-store.*/, /^max-age=\d+.*/, /^s-maxage=\d+.*/, /^no-transform/, /^public/, /^private/], note: 'accepts mapping values to globs', eg: 'public: *.css,*.js'
34
+ opt '--expires DATE', 'Date and time that the cached object expires', type: :array, format: /^"?\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} .+"?.*$/, note: 'accepts mapping values to globs', eg: '2020-01-01 00:00:00 UTC: *.css,*.js'
35
+ opt '--acl ACL', 'Access control for the uploaded objects', default: 'private', enum: %w(private public_read public_read_write authenticated_read bucket_owner_read bucket_owner_full_control)
36
+ opt '--dot_match', 'Upload hidden files starting with a dot'
37
+ opt '--index_document_suffix SUFFIX', 'Index document suffix of a S3 website'
38
+ opt '--default_text_charset CHARSET', 'Default character set to append to the content-type of text files'
39
+ opt '--max_threads NUM', 'The number of threads to use for S3 file uploads', default: 5, max: 15, type: :integer
40
+ opt '--overwrite', 'Whether or not to overwrite existing files', default: true
41
+ opt '--force_path_style', 'Whether to force keeping the bucket name on the path'
42
+ opt '--verbose', 'Be verbose about uploading files'
43
+ # how come there is no glob or file option?
44
+
45
+ msgs login: 'Using Access Key: %{access_key_id}',
46
+ default_uri_schema: 'S3 endpoint does not specify a scheme; defaulting to https',
47
+ access_denied: 'It looks like you tried to write to a bucket that is not yours or does not exist. Please create the bucket before trying to write to it.',
48
+ checksum_error: 'AWS secret key does not match the access key id',
49
+ invalid_access_key_id: 'Invalid S3 access key id',
50
+ upload: 'Uploading %s files with up to %s threads ...',
51
+ upload_file: 'Uploading %s to %s with %s',
52
+ upload_skipped: 'Skipping %{file}, already exists',
53
+ upload_failed: 'Failed to upload %s',
54
+ index_document_suffix: 'Setting index document suffix to %s'
55
+
56
+ DEFAULT_CONTENT_TYPE = 'application/octet-stream'
57
+
58
+ def setup
59
+ @cwd = Dir.pwd
60
+ Dir.chdir(local_dir)
61
+ # Aws.eager_autoload!(services: ['S3'])
62
+ end
63
+
64
+ def login
65
+ info :login
66
+ end
67
+
68
+ def deploy
69
+ upload
70
+ index_document_suffix if index_document_suffix?
71
+ rescue Aws::S3::Errors::ServiceError => e
72
+ handle_error(e)
73
+ end
74
+
75
+ def finish
76
+ Dir.chdir(@cwd) if @cwd
77
+ end
78
+
79
+ private
80
+
81
+ def upload
82
+ info :upload, files.length, max_threads
83
+ threads = max_threads.times.map { |i| Thread.new(&method(:upload_files)) }
84
+ threads.each(&:join)
85
+ info "\n" unless verbose?
86
+ end
87
+
88
+ def upload_files
89
+ while file = files.pop
90
+ opts = upload_opts(file)
91
+ progress(file, opts)
92
+ upload_file(file, opts)
93
+ end
94
+ end
95
+
96
+ def progress(file, data)
97
+ if verbose?
98
+ info :upload_file, file, upload_dir || '/', to_pairs(data)
99
+ else
100
+ print '.'
101
+ end
102
+ end
103
+
104
+ def upload_file(file, opts)
105
+ object = bucket.object(upload_path(file))
106
+ return warn :upload_skipped, file: file if !overwrite && object.exists?
107
+ info :upload_file, file, upload_dir || '/', to_pairs(opts)
108
+ object.upload_file(file, opts) || warn(:upload_failed, file)
109
+ end
110
+
111
+ def index_document_suffix
112
+ info :index_document_suffix, super
113
+ body = { website_configuration: { index_document: { suffix: super } } }
114
+ bucket.website.put(body)
115
+ end
116
+
117
+ def upload_path(file)
118
+ [upload_dir, file].compact.join('/')
119
+ end
120
+
121
+ def upload_opts(file)
122
+ compact(
123
+ acl: acl,
124
+ content_type: content_type(file),
125
+ content_encoding: detect_encoding? ? encoding(file) : nil,
126
+ cache_control: match_opt(cache_control, file),
127
+ expires: match_opt(expires, file),
128
+ storage_class: storage_class,
129
+ server_side_encryption: server_side_encryption
130
+ )
131
+ end
132
+
133
+ def files
134
+ @files ||= Dir.glob(*glob).reject { |path| File.directory?(path) }
135
+ end
136
+
137
+ def glob
138
+ ['**/*', dot_match? ? File::FNM_DOTMATCH : nil].compact
139
+ end
140
+
141
+ def acl
142
+ super.gsub(/_/, '-') if acl?
143
+ end
144
+
145
+ def server_side_encryption
146
+ 'AES256' if server_side_encryption?
147
+ end
148
+
149
+ def content_type(file)
150
+ return DEFAULT_CONTENT_TYPE unless type = MIME::Types.type_for(file).first
151
+ type = "#{type}; charset=#{default_text_charset}" if encoding(file) == 'text' && default_text_charset?
152
+ type.to_s
153
+ end
154
+
155
+ def compact(hash)
156
+ hash.reject { |_, value| value.nil? }.to_h
157
+ end
158
+
159
+ def endpoint
160
+ @endpoint ||= normalize_endpoint(super) if endpoint?
161
+ end
162
+
163
+ def normalize_endpoint(url)
164
+ uri = URI.parse(url)
165
+ return uri if uri.scheme
166
+ info :default_uri_scheme
167
+ URI.parse("https://#{url}")
168
+ end
169
+
170
+ def handle_error(e)
171
+ case e
172
+ when Aws::S3::Errors::InvalidAccessKeyId
173
+ error :invalid_access_key_id
174
+ when Aws::S3::Errors::ChecksumError
175
+ error :checksum_error
176
+ when Aws::S3::Errors::AccessDenied
177
+ error :access_denied
178
+ else
179
+ error e.message
180
+ end
181
+ end
182
+
183
+ def bucket
184
+ @bucket ||= Aws::S3::Resource.new(client: client).bucket(super)
185
+ end
186
+
187
+ def client
188
+ Aws::S3::Client.new(s3_opts)
189
+ end
190
+
191
+ def s3_opts
192
+ compact(
193
+ region: region,
194
+ credentials: credentials,
195
+ endpoint: endpoint,
196
+ force_path_style: force_path_style?
197
+ )
198
+ end
199
+
200
+ def credentials
201
+ Aws::Credentials.new(access_key_id, secret_access_key)
202
+ end
203
+
204
+ def to_pairs(hash)
205
+ hash.map { |pair| pair.join('=') }.join(' ')
206
+ end
207
+
208
+ def match_opt(strs, file)
209
+ maps = Array(strs).map { |str| Mapping.new(str, file) }
210
+ maps.map(&:value).compact.first
211
+ end
212
+
213
+ class Mapping < Struct.new(:str, :file)
214
+ MATCH = File::FNM_DOTMATCH | File::FNM_EXTGLOB
215
+
216
+ def value
217
+ str, glob = parse
218
+ unquote(str) if match?(glob)
219
+ end
220
+
221
+ private
222
+
223
+ def unquote(str)
224
+ str =~ /^"(.*)"$/ && $1 || str
225
+ end
226
+
227
+ def match?(glob)
228
+ glob.nil? || File.fnmatch?(normalize(glob), file, MATCH)
229
+ end
230
+
231
+ def normalize(glob)
232
+ return glob if glob.include?('{')
233
+ "{#{glob.split(',').map(&:strip).join(',')}}"
234
+ end
235
+
236
+ def parse
237
+ parts = str.split(': ')
238
+ parts.size > 1 ? [parts[0..-2].join(': '), parts.last] : parts
239
+ end
240
+ end
241
+ end
242
+ end
243
+ end