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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +74 -0
- data/CONTRIBUTING.md +392 -0
- data/Gemfile +17 -3
- data/Gemfile.lock +373 -0
- data/LICENSE +16 -19
- data/NOTES.md +275 -0
- data/README.md +1977 -707
- data/Rakefile +2 -2
- data/bin/dpl +7 -3
- data/lib/dpl.rb +20 -0
- data/lib/dpl/assets/atlas/install +19 -0
- data/lib/dpl/assets/dpl/README.erb.md +133 -0
- data/lib/dpl/assets/dpl/git_ssh +2 -0
- data/lib/dpl/assets/git/detect_private_key +8 -0
- data/lib/dpl/assets/hephy/filter_log +3 -0
- data/lib/dpl/assets/pypi/install +4 -0
- data/lib/dpl/assets/scalingo/install +6 -0
- data/lib/dpl/cli.rb +36 -48
- data/lib/dpl/ctx.rb +2 -0
- data/lib/dpl/ctx/bash.rb +543 -0
- data/lib/dpl/ctx/test.rb +242 -0
- data/lib/dpl/helper/assets.rb +36 -0
- data/lib/dpl/helper/cmd.rb +167 -0
- data/lib/dpl/helper/config_file.rb +47 -0
- data/lib/dpl/helper/env.rb +39 -0
- data/lib/dpl/helper/interpolate.rb +126 -0
- data/lib/dpl/helper/memoize.rb +20 -0
- data/lib/dpl/helper/squiggle.rb +22 -0
- data/lib/dpl/helper/zip.rb +69 -0
- data/lib/dpl/provider.rb +562 -234
- data/lib/dpl/provider/dsl.rb +369 -0
- data/lib/dpl/provider/examples.rb +128 -0
- data/lib/dpl/provider/status.rb +59 -0
- data/lib/dpl/providers.rb +40 -0
- data/lib/dpl/providers/anynines.rb +65 -0
- data/lib/dpl/providers/atlas.rb +49 -0
- data/lib/dpl/providers/azure_web_apps.rb +59 -0
- data/lib/dpl/providers/bintray.rb +313 -0
- data/lib/dpl/providers/bluemixcloudfoundry.rb +92 -0
- data/lib/dpl/providers/boxfuse.rb +48 -0
- data/lib/dpl/providers/cargo.rb +19 -0
- data/lib/dpl/providers/chef_supermarket.rb +128 -0
- data/lib/dpl/providers/cloud66.rb +40 -0
- data/lib/dpl/providers/cloudfiles.rb +56 -0
- data/lib/dpl/providers/cloudfoundry.rb +81 -0
- data/lib/dpl/providers/codedeploy.rb +179 -0
- data/lib/dpl/providers/datica.rb +60 -0
- data/lib/dpl/providers/elasticbeanstalk.rb +195 -0
- data/lib/dpl/providers/engineyard.rb +107 -0
- data/lib/dpl/providers/firebase.rb +41 -0
- data/lib/dpl/providers/gae.rb +74 -0
- data/lib/dpl/providers/gcs.rb +105 -0
- data/lib/dpl/providers/hackage.rb +47 -0
- data/lib/dpl/providers/hephy.rb +101 -0
- data/lib/dpl/providers/heroku.rb +111 -0
- data/lib/dpl/providers/heroku/api.rb +119 -0
- data/lib/dpl/providers/heroku/git.rb +50 -0
- data/lib/dpl/providers/lambda.rb +202 -0
- data/lib/dpl/providers/launchpad.rb +74 -0
- data/lib/dpl/providers/netlify.rb +30 -0
- data/lib/dpl/providers/npm.rb +88 -0
- data/lib/dpl/providers/openshift.rb +46 -0
- data/lib/dpl/providers/opsworks.rb +142 -0
- data/lib/dpl/providers/packagecloud.rb +190 -0
- data/lib/dpl/providers/pages.rb +17 -0
- data/lib/dpl/providers/pages/api.rb +102 -0
- data/lib/dpl/providers/pages/git.rb +251 -0
- data/lib/dpl/providers/puppetforge.rb +44 -0
- data/lib/dpl/providers/pypi.rb +120 -0
- data/lib/dpl/providers/releases.rb +214 -0
- data/lib/dpl/providers/rubygems.rb +89 -0
- data/lib/dpl/providers/s3.rb +243 -0
- data/lib/dpl/providers/scalingo.rb +63 -0
- data/lib/dpl/providers/script.rb +28 -0
- data/lib/dpl/providers/snap.rb +59 -0
- data/lib/dpl/providers/surge.rb +55 -0
- data/lib/dpl/providers/testfairy.rb +93 -0
- data/lib/dpl/providers/transifex.rb +66 -0
- data/lib/dpl/support/aws_sdk_patch.rb +23 -0
- data/lib/dpl/support/gems.rb +69 -0
- data/lib/dpl/support/gstore_patch.rb +6 -0
- data/lib/dpl/support/version.rb +83 -0
- data/lib/dpl/version.rb +2 -2
- metadata +98 -169
- data/.coveralls.yml +0 -1
- data/.github/CONTRIBUTING.md +0 -173
- data/.github/stale.yml +0 -53
- data/.gitignore +0 -13
- data/.rspec +0 -2
- data/.travis.yml +0 -56
- data/dpl-anynines.gemspec +0 -3
- data/dpl-atlas.gemspec +0 -3
- data/dpl-azure_webapps.gemspec +0 -3
- data/dpl-bintray.gemspec +0 -3
- data/dpl-bitballoon.gemspec +0 -3
- data/dpl-bluemix_cloud_foundry.gemspec +0 -3
- data/dpl-boxfuse.gemspec +0 -3
- data/dpl-cargo.gemspec +0 -3
- data/dpl-catalyze.gemspec +0 -3
- data/dpl-chef_supermarket.gemspec +0 -20
- data/dpl-cloud66.gemspec +0 -3
- data/dpl-cloud_files.gemspec +0 -3
- data/dpl-cloud_foundry.gemspec +0 -3
- data/dpl-code_deploy.gemspec +0 -3
- data/dpl-deis.gemspec +0 -3
- data/dpl-elastic_beanstalk.gemspec +0 -3
- data/dpl-engine_yard.gemspec +0 -3
- data/dpl-firebase.gemspec +0 -3
- data/dpl-gae.gemspec +0 -3
- data/dpl-gcs.gemspec +0 -3
- data/dpl-hackage.gemspec +0 -3
- data/dpl-hephy.gemspec +0 -3
- data/dpl-heroku.gemspec +0 -3
- data/dpl-lambda.gemspec +0 -3
- data/dpl-launchpad.gemspec +0 -3
- data/dpl-npm.gemspec +0 -3
- data/dpl-openshift.gemspec +0 -3
- data/dpl-ops_works.gemspec +0 -3
- data/dpl-packagecloud.gemspec +0 -3
- data/dpl-pages.gemspec +0 -3
- data/dpl-puppet_forge.gemspec +0 -3
- data/dpl-pypi.gemspec +0 -3
- data/dpl-releases.gemspec +0 -8
- data/dpl-rubygems.gemspec +0 -3
- data/dpl-s3.gemspec +0 -3
- data/dpl-scalingo.gemspec +0 -3
- data/dpl-script.gemspec +0 -3
- data/dpl-snap.gemspec +0 -3
- data/dpl-surge.gemspec +0 -3
- data/dpl-testfairy.gemspec +0 -3
- data/dpl-transifex.gemspec +0 -3
- data/dpl.gemspec +0 -3
- data/gemspec_helper.rb +0 -51
- data/lib/dpl/error.rb +0 -3
- data/notes/engine_yard.md +0 -1
- data/notes/heroku.md +0 -3
- data/spec/cli_spec.rb +0 -36
- data/spec/provider_spec.rb +0 -191
- 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
|