travis_dpl_test 2.0.3.beta.4.ror

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +392 -0
  5. data/Gemfile +32 -0
  6. data/Gemfile.lock +611 -0
  7. data/LICENSE +19 -0
  8. data/README.md +2744 -0
  9. data/Rakefile +210 -0
  10. data/bin/dpl +11 -0
  11. data/config/transliterate.yml +733 -0
  12. data/dpl.gemspec +23 -0
  13. data/lib/dpl/assets/atlas/install +19 -0
  14. data/lib/dpl/assets/convox/install +11 -0
  15. data/lib/dpl/assets/dpl/README.erb.md +138 -0
  16. data/lib/dpl/assets/dpl/git_ssh +8 -0
  17. data/lib/dpl/assets/git/detect_private_key +8 -0
  18. data/lib/dpl/assets/hephy/filter_log +3 -0
  19. data/lib/dpl/assets/pypi/install +4 -0
  20. data/lib/dpl/assets/scalingo/install +6 -0
  21. data/lib/dpl/cli.rb +100 -0
  22. data/lib/dpl/ctx/bash.rb +549 -0
  23. data/lib/dpl/ctx/test.rb +255 -0
  24. data/lib/dpl/ctx.rb +4 -0
  25. data/lib/dpl/helper/assets.rb +38 -0
  26. data/lib/dpl/helper/cmd.rb +169 -0
  27. data/lib/dpl/helper/config_file.rb +49 -0
  28. data/lib/dpl/helper/cookbook_site_streaming_uploader.rb +249 -0
  29. data/lib/dpl/helper/env.rb +92 -0
  30. data/lib/dpl/helper/github.rb +22 -0
  31. data/lib/dpl/helper/interpolate.rb +160 -0
  32. data/lib/dpl/helper/memoize.rb +23 -0
  33. data/lib/dpl/helper/squiggle.rb +24 -0
  34. data/lib/dpl/helper/transliterate.rb +13 -0
  35. data/lib/dpl/helper/wrap.rb +11 -0
  36. data/lib/dpl/helper/zip.rb +71 -0
  37. data/lib/dpl/provider/dsl.rb +410 -0
  38. data/lib/dpl/provider/examples.rb +132 -0
  39. data/lib/dpl/provider/status.rb +61 -0
  40. data/lib/dpl/provider.rb +651 -0
  41. data/lib/dpl/providers/anynines.rb +71 -0
  42. data/lib/dpl/providers/azure_web_apps.rb +63 -0
  43. data/lib/dpl/providers/bintray.rb +324 -0
  44. data/lib/dpl/providers/bluemixcloudfoundry.rb +98 -0
  45. data/lib/dpl/providers/boxfuse.rb +52 -0
  46. data/lib/dpl/providers/cargo.rb +32 -0
  47. data/lib/dpl/providers/chef_supermarket.rb +132 -0
  48. data/lib/dpl/providers/cloud66.rb +46 -0
  49. data/lib/dpl/providers/cloudfiles.rb +62 -0
  50. data/lib/dpl/providers/cloudformation.rb +281 -0
  51. data/lib/dpl/providers/cloudfoundry.rb +89 -0
  52. data/lib/dpl/providers/codedeploy.rb +190 -0
  53. data/lib/dpl/providers/convox.rb +130 -0
  54. data/lib/dpl/providers/datica.rb +64 -0
  55. data/lib/dpl/providers/ecr.rb +129 -0
  56. data/lib/dpl/providers/elasticbeanstalk.rb +207 -0
  57. data/lib/dpl/providers/engineyard.rb +113 -0
  58. data/lib/dpl/providers/firebase.rb +45 -0
  59. data/lib/dpl/providers/flynn.rb +35 -0
  60. data/lib/dpl/providers/gae.rb +78 -0
  61. data/lib/dpl/providers/gcs.rb +132 -0
  62. data/lib/dpl/providers/git_push.rb +273 -0
  63. data/lib/dpl/providers/gleis.rb +74 -0
  64. data/lib/dpl/providers/hackage.rb +53 -0
  65. data/lib/dpl/providers/hephy.rb +107 -0
  66. data/lib/dpl/providers/heroku/api.rb +123 -0
  67. data/lib/dpl/providers/heroku/git.rb +54 -0
  68. data/lib/dpl/providers/heroku.rb +111 -0
  69. data/lib/dpl/providers/lambda.rb +211 -0
  70. data/lib/dpl/providers/launchpad.rb +80 -0
  71. data/lib/dpl/providers/netlify.rb +38 -0
  72. data/lib/dpl/providers/npm.rb +130 -0
  73. data/lib/dpl/providers/nuget.rb +41 -0
  74. data/lib/dpl/providers/openshift.rb +52 -0
  75. data/lib/dpl/providers/opsworks.rb +146 -0
  76. data/lib/dpl/providers/packagecloud.rb_ +194 -0
  77. data/lib/dpl/providers/pages/api.rb +106 -0
  78. data/lib/dpl/providers/pages/git.rb +262 -0
  79. data/lib/dpl/providers/pages.rb +18 -0
  80. data/lib/dpl/providers/puppetforge.rb +50 -0
  81. data/lib/dpl/providers/pypi.rb +125 -0
  82. data/lib/dpl/providers/releases.rb +234 -0
  83. data/lib/dpl/providers/rubygems.rb +97 -0
  84. data/lib/dpl/providers/s3.rb +251 -0
  85. data/lib/dpl/providers/scalingo.rb +69 -0
  86. data/lib/dpl/providers/script.rb +32 -0
  87. data/lib/dpl/providers/snap.rb +68 -0
  88. data/lib/dpl/providers/surge.rb +59 -0
  89. data/lib/dpl/providers/testfairy.rb +101 -0
  90. data/lib/dpl/providers/transifex.rb +72 -0
  91. data/lib/dpl/providers.rb +48 -0
  92. data/lib/dpl/string_ext.rb +23 -0
  93. data/lib/dpl/support/aws_sdk_patch.rb +26 -0
  94. data/lib/dpl/support/gems.rb +73 -0
  95. data/lib/dpl/support/gstore_patch.rb +8 -0
  96. data/lib/dpl/support/version.rb +84 -0
  97. data/lib/dpl/version.rb +5 -0
  98. data/lib/dpl.rb +23 -0
  99. data/status.json +237 -0
  100. metadata +161 -0
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+
5
+ # we want this, don't we?
6
+ Thread.abort_on_exception = true
7
+
8
+ module Dpl
9
+ module Providers
10
+ class S3 < Provider
11
+ register :s3
12
+
13
+ status :stable
14
+
15
+ full_name 'AWS S3'
16
+
17
+ description sq(<<-STR)
18
+ tbd
19
+ STR
20
+
21
+ gem 'aws-sdk-s3', '~> 1'
22
+ gem 'mime-types', '~> 3.4.1'
23
+
24
+ env :aws, :s3
25
+ config '~/.aws/credentials', '~/.aws/config', prefix: 'aws'
26
+
27
+ opt '--access_key_id ID', 'AWS access key id', required: true, secret: true
28
+ opt '--secret_access_key KEY', 'AWS secret key', required: true, secret: true
29
+ opt '--bucket BUCKET', 'S3 bucket', required: true
30
+ opt '--region REGION', 'S3 region', default: 'us-east-1'
31
+ opt '--endpoint URL', 'S3 endpoint'
32
+ opt '--upload_dir DIR', 'S3 directory to upload to'
33
+ opt '--local_dir DIR', 'Local directory to upload from', default: '.', example: '~/travis/build (absolute path) or ./build (relative path)'
34
+ opt '--glob GLOB', 'Files to upload', default: '**/*'
35
+ opt '--dot_match', 'Upload hidden files starting with a dot'
36
+ 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]
37
+ opt '--detect_encoding', 'HTTP header Content-Encoding for files compressed with gzip and compress utilities'
38
+ 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'
39
+ 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'
40
+ opt '--default_text_charset CHARSET', 'Default character set to append to the content-type of text files'
41
+ opt '--storage_class CLASS', 'S3 storage class to upload as', default: 'STANDARD', enum: %w[STANDARD STANDARD_IA REDUCED_REDUNDANCY]
42
+ opt '--server_side_encryption', 'Use S3 Server Side Encryption (SSE-AES256)'
43
+ opt '--index_document_suffix SUFFIX', 'Index document suffix of a S3 website'
44
+ opt '--overwrite', 'Whether or not to overwrite existing files', default: true
45
+ opt '--force_path_style', 'Whether to force keeping the bucket name on the path'
46
+ opt '--max_threads NUM', 'The number of threads to use for S3 file uploads', default: 5, max: 15, type: :integer
47
+ opt '--verbose', 'Be verbose about uploading files'
48
+
49
+ msgs login: 'Using Access Key: %{access_key_id}',
50
+ default_uri_schema: 'S3 endpoint does not specify a scheme; defaulting to https',
51
+ 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.',
52
+ checksum_error: 'AWS secret key does not match the access key id',
53
+ invalid_access_key_id: 'Invalid S3 access key id',
54
+ upload: 'Uploading %s files with up to %s threads ...',
55
+ upload_file: 'Uploading %s to %s with %s',
56
+ upload_skipped: 'Skipping %{file}, already exists',
57
+ upload_failed: 'Failed to upload %s',
58
+ index_document_suffix: 'Setting index document suffix to %s'
59
+
60
+ DEFAULT_CONTENT_TYPE = 'application/octet-stream'
61
+
62
+ def setup
63
+ @cwd = Dir.pwd
64
+ Dir.chdir(local_dir)
65
+ # Aws.eager_autoload!(services: ['S3'])
66
+ end
67
+
68
+ def login
69
+ info :login
70
+ end
71
+
72
+ def deploy
73
+ upload
74
+ index_document_suffix if index_document_suffix?
75
+ rescue Aws::S3::Errors::ServiceError => e
76
+ handle_error(e)
77
+ end
78
+
79
+ def finish
80
+ Dir.chdir(@cwd) if @cwd
81
+ end
82
+
83
+ private
84
+
85
+ def upload
86
+ info :upload, files.length, max_threads
87
+ threads = max_threads.times.map { |_i| Thread.new(&method(:upload_files)) }
88
+ threads.each(&:join)
89
+ info "\n" unless verbose?
90
+ end
91
+
92
+ def upload_files
93
+ while file = files.pop
94
+ opts = upload_opts(file)
95
+ progress(file, opts)
96
+ upload_file(file, opts)
97
+ end
98
+ end
99
+
100
+ def progress(file, data)
101
+ if verbose?
102
+ info :upload_file, file, upload_dir || '/', to_pairs(data)
103
+ else
104
+ print '.'
105
+ end
106
+ end
107
+
108
+ def upload_file(file, opts)
109
+ object = bucket.object(upload_path(file))
110
+ return warn :upload_skipped, file: file if !overwrite && object.exists?
111
+
112
+ info :upload_file, file, upload_dir || '/', to_pairs(opts)
113
+ object.upload_file(file, opts) || warn(:upload_failed, file)
114
+ end
115
+
116
+ def index_document_suffix
117
+ info :index_document_suffix, super
118
+ body = { website_configuration: { index_document: { suffix: super } } }
119
+ bucket.website.put(body)
120
+ end
121
+
122
+ def upload_path(file)
123
+ [upload_dir, file].compact.join('/')
124
+ end
125
+
126
+ def upload_opts(file)
127
+ compact(
128
+ acl:,
129
+ content_type: content_type(file),
130
+ content_encoding: detect_encoding? ? encoding(file) : nil,
131
+ cache_control: match_opt(cache_control, file),
132
+ expires: match_opt(expires, file),
133
+ storage_class:,
134
+ server_side_encryption:
135
+ )
136
+ end
137
+
138
+ def files
139
+ @files ||= Dir.glob(*glob).reject { |path| File.directory?(path) }
140
+ end
141
+
142
+ def glob
143
+ [super, dot_match? ? File::FNM_DOTMATCH : nil].compact
144
+ end
145
+
146
+ def acl
147
+ super.gsub(/_/, '-') if acl?
148
+ end
149
+
150
+ def server_side_encryption
151
+ 'AES256' if server_side_encryption?
152
+ end
153
+
154
+ def content_type(file)
155
+ return DEFAULT_CONTENT_TYPE unless type = MIME::Types.type_for(file).first
156
+
157
+ type = "#{type}; charset=#{default_text_charset}" if encoding(file) == 'text' && default_text_charset?
158
+ type.to_s
159
+ end
160
+
161
+ def compact(hash)
162
+ hash.reject { |_, value| value.nil? }.to_h
163
+ end
164
+
165
+ def endpoint
166
+ @endpoint ||= normalize_endpoint(super) if endpoint?
167
+ end
168
+
169
+ def normalize_endpoint(url)
170
+ uri = URI.parse(url)
171
+ return uri if uri.scheme
172
+
173
+ info :default_uri_scheme
174
+ URI.parse("https://#{url}")
175
+ end
176
+
177
+ def handle_error(err)
178
+ case err
179
+ when Aws::S3::Errors::InvalidAccessKeyId
180
+ error :invalid_access_key_id
181
+ when Aws::S3::Errors::ChecksumError
182
+ error :checksum_error
183
+ when Aws::S3::Errors::AccessDenied
184
+ error :access_denied
185
+ else
186
+ error err.message
187
+ end
188
+ end
189
+
190
+ def bucket
191
+ @bucket ||= Aws::S3::Resource.new(client:).bucket(super)
192
+ end
193
+
194
+ def client
195
+ Aws::S3::Client.new(s3_opts)
196
+ end
197
+
198
+ def s3_opts
199
+ compact(
200
+ region:,
201
+ credentials:,
202
+ endpoint:,
203
+ force_path_style: force_path_style?
204
+ )
205
+ end
206
+
207
+ def credentials
208
+ Aws::Credentials.new(access_key_id, secret_access_key)
209
+ end
210
+
211
+ def to_pairs(hash)
212
+ hash.map { |pair| pair.join('=') }.join(' ')
213
+ end
214
+
215
+ def match_opt(strs, file)
216
+ maps = Array(strs).map { |str| Mapping.new(str, file) }
217
+ maps.map(&:value).compact.first
218
+ end
219
+
220
+ class Mapping < Struct.new(:str, :file)
221
+ MATCH = File::FNM_DOTMATCH | File::FNM_EXTGLOB
222
+
223
+ def value
224
+ str, glob = parse
225
+ unquote(str) if match?(glob)
226
+ end
227
+
228
+ private
229
+
230
+ def unquote(str)
231
+ str =~ /^"(.*)"$/ && ::Regexp.last_match(1) || str
232
+ end
233
+
234
+ def match?(glob)
235
+ glob.nil? || File.fnmatch?(normalize(glob), file, MATCH)
236
+ end
237
+
238
+ def normalize(glob)
239
+ return glob if glob.include?('{')
240
+
241
+ "{#{glob.split(',').map(&:strip).join(',')}}"
242
+ end
243
+
244
+ def parse
245
+ parts = str.split(': ')
246
+ parts.size > 1 ? [parts[0..-2].join(': '), parts.last] : parts
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dpl
4
+ module Providers
5
+ class Scalingo < Provider
6
+ register :scalingo
7
+
8
+ status :alpha
9
+
10
+ description sq(<<-STR)
11
+ tbd
12
+ STR
13
+
14
+ env :scalingo
15
+
16
+ required :api_token, %i[username password]
17
+
18
+ opt '--app APP', default: :repo_name
19
+ opt '--api_token TOKEN', 'Scalingo API token', alias: :api_key, deprecated: :api_key
20
+ opt '--username NAME', 'Scalingo username'
21
+ opt '--password PASS', 'Scalingo password', secret: true
22
+ opt '--region REGION', 'Scalingo region', default: 'agora-fr1', enum: %w[agora-fr1 osc-fr1]
23
+ opt '--remote REMOTE', 'Git remote name', default: 'scalingo-dpl'
24
+ opt '--branch BRANCH', 'Git branch', default: 'master'
25
+ opt '--timeout SEC', 'Timeout for Scalingo CLI commands', default: 60, type: :integer
26
+
27
+ needs :git, :ssh_key
28
+
29
+ cmds login_key: 'timeout %{timeout} ./scalingo login --api-token %{api_token} > /dev/null',
30
+ login_creds: 'echo -e \"%{username}\n%{password}\" | timeout %{timeout} ./scalingo login > /dev/null',
31
+ add_key: 'timeout %{timeout} ./scalingo keys-add dpl_tmp_key %{key}',
32
+ remove_key: 'timeout %{timeout} ./scalingo keys-remove dpl_tmp_key',
33
+ git_setup: './scalingo --app %{app} git-setup --remote %{remote}',
34
+ push: 'git push %{remote} HEAD:%{branch} -f'
35
+
36
+ errs install: 'Failed to install the Scalingo CLI.',
37
+ login: 'Failed to authenticate with the Scalingo API.',
38
+ add_key: 'Failed to add the ssh key.',
39
+ remove_key: 'Failed to remove the ssh key.',
40
+ git_setup: 'Failed to add the git remote.',
41
+ push: 'Failed to push the app.'
42
+
43
+ def install
44
+ script :install
45
+ ENV['SCALINGO_REGION'] = region if region?
46
+ end
47
+
48
+ def login
49
+ shell api_token ? :login_key : :login_creds, assert: err(:login)
50
+ end
51
+
52
+ def add_key(key, _type = nil)
53
+ shell :add_key, key:
54
+ end
55
+
56
+ def setup
57
+ shell :git_setup
58
+ end
59
+
60
+ def deploy
61
+ shell :push
62
+ end
63
+
64
+ def remove_key
65
+ shell :remove_key
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dpl
4
+ module Providers
5
+ class Script < Provider
6
+ register :script
7
+
8
+ status :stable
9
+
10
+ summary 'Minimal provider that executes a custom command'
11
+
12
+ description sq(<<-STR)
13
+ This deployment provider executes a single, custom command. This is
14
+ usually a script that is contained in your repository, but it can be
15
+ any command executable in the build environment.
16
+
17
+ It is possible to pass arguments to a script deployment like so:
18
+
19
+ dpl script -s './scripts/deploy.sh production --verbose'
20
+
21
+ Deployment will be marked a failure if the script exits with nonzero
22
+ status.
23
+ STR
24
+
25
+ opt '-s', '--script SCRIPT', 'The script to execute', required: true
26
+
27
+ def deploy
28
+ shell script, assert: 'Script failed with status %{status}'
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dpl
4
+ module Providers
5
+ class Snap < Provider
6
+ register :snap
7
+
8
+ status :stable
9
+
10
+ description sq(<<-STR)
11
+ tbd
12
+ STR
13
+
14
+ env :snap
15
+
16
+ opt '--token TOKEN', 'Snap API token', required: true, secret: true
17
+ opt '--snap STR', 'Path to the snap to be pushed (can be a glob)', default: '**/*.snap'
18
+ opt '--channel CHAN', 'Channel into which the snap will be released', default: 'edge'
19
+
20
+ apt 'snapd', 'snap'
21
+
22
+ cmds apt_get_update: 'sudo apt-get update -qq',
23
+ update_snapd: 'sudo apt-get install snapd',
24
+ install: 'sudo snap install snapcraft --classic',
25
+ login: 'echo "%{token}" | snapcraft login --with -',
26
+ deploy: 'snapcraft push %{snap_path} --release=%{channel}'
27
+
28
+ msgs login: 'Attemping to login ...',
29
+ no_snaps: 'No snap found matching %{snap}',
30
+ multiple_snaps: 'Multiple snaps found matching %{snap}: %{snap_paths}',
31
+ deploy: 'Pushing snap %{snap_path}'
32
+
33
+ def install
34
+ return if which 'snapcraft'
35
+
36
+ shell :apt_get_update
37
+ shell :update_snapd
38
+ shell :install
39
+ ENV['PATH'] += ':/snap/bin'
40
+ end
41
+
42
+ def login
43
+ shell :login, assert: 'Failed to authenticate: %{err}', success: '%{out}', capture: true
44
+ end
45
+
46
+ def validate
47
+ error :no_snaps if snaps.empty?
48
+ error :multiple_snaps if snaps.size > 1
49
+ end
50
+
51
+ def deploy
52
+ shell :deploy
53
+ end
54
+
55
+ def snap_path
56
+ snaps.first
57
+ end
58
+
59
+ def snap_paths
60
+ snaps.join(', ')
61
+ end
62
+
63
+ def snaps
64
+ @snaps ||= Dir[snap].sort
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open-uri'
4
+
5
+ module Dpl
6
+ module Providers
7
+ class Surge < Provider
8
+ register :surge
9
+
10
+ status :stable
11
+
12
+ description sq(<<-STR)
13
+ tbd
14
+ STR
15
+
16
+ node_js '>= 8.8.1'
17
+
18
+ gem 'json'
19
+ npm :surge
20
+ env :surge
21
+
22
+ opt '--login EMAIL', 'Surge login (the email address you use with Surge)', required: true
23
+ opt '--token TOKEN', 'Surge login token (can be retrieved with `surge token`)', required: true, secret: true
24
+ opt '--domain NAME', 'Domain to publish to. Not required if the domain is set in the CNAME file in the project folder.'
25
+ opt '--project PATH', 'Path to project directory relative to repo root', default: '.'
26
+
27
+ cmds deploy: 'surge %{project} %{domain}'
28
+
29
+ msgs invalid_project: '%{project} is not a directory',
30
+ missing_domain: 'Please set the domain in .travis.yml or in a CNAME file in the project directory'
31
+
32
+ def login
33
+ ENV['SURGE_LOGIN'] ||= opts[:login]
34
+ ENV['SURGE_TOKEN'] ||= opts[:token]
35
+ end
36
+
37
+ def validate
38
+ error :invalid_project if invalid_project?
39
+ error :missing_domain if missing_domain?
40
+ end
41
+
42
+ def deploy
43
+ shell :deploy
44
+ end
45
+
46
+ def invalid_project?
47
+ !File.directory?(project)
48
+ end
49
+
50
+ def missing_domain?
51
+ !domain && !File.exist?("#{project}/CNAME")
52
+ end
53
+
54
+ def project
55
+ expand(super, build_dir)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dpl/version'
4
+ require 'net/http'
5
+ require 'securerandom'
6
+
7
+ module Dpl
8
+ module Providers
9
+ class Testfairy < Provider
10
+ register :testfairy
11
+
12
+ status :alpha
13
+
14
+ full_name 'TestFairy'
15
+
16
+ description sq(<<-STR)
17
+ tbd
18
+ STR
19
+
20
+ gem 'json'
21
+ gem 'multipart-post', '~> 2.0.0', require: 'net/http/post/multipart'
22
+
23
+ env :testfairy
24
+
25
+ opt '--api_key KEY', 'TestFairy API key', required: true, secret: true
26
+ opt '--app_file FILE', 'Path to the app file that will be generated after the build (APK/IPA)', required: true
27
+ opt '--symbols_file FILE', 'Path to the symbols file'
28
+ opt '--testers_groups GROUPS', 'Tester groups to be notified about this build', example: 'e.g. group1,group1'
29
+ opt '--notify', 'Send an email with a changelog to your users'
30
+ opt '--auto_update', 'Automaticall upgrade all the previous installations of this app this version'
31
+ opt '--advanced_options OPTS', 'Comma_separated list of advanced options', example: 'option1,option2'
32
+
33
+ URL = 'https://upload.testfairy.com/api/upload'
34
+ UA = "Travis CI dpl version=#{Dpl::VERSION}".freeze
35
+
36
+ msgs deploy: 'Uploading to TestFairy: %s',
37
+ done: 'Done. Check your build at %s'
38
+
39
+ def deploy
40
+ info :deploy, pretty_print(params)
41
+ body = JSON.parse(http.request(request).body)
42
+ error body['message'] if body['status'] == 'fail'
43
+ info :done, body['build_url']
44
+ end
45
+
46
+ private
47
+
48
+ def params
49
+ @params ||= compact(
50
+ 'api_key': api_key,
51
+ 'apk_file': file(app_file),
52
+ 'symbols_file': file(symbols_file),
53
+ 'testers-groups': testers_groups,
54
+ 'notify': bool(notify),
55
+ 'auto-update': bool(auto_update),
56
+ 'advanced-options': advanced_options,
57
+ 'changelog': changelog
58
+ )
59
+ end
60
+
61
+ def changelog
62
+ git_log "--pretty=oneline --abbrev-commit #{commits}" if commits
63
+ end
64
+
65
+ def commits
66
+ ENV['TRAVIS_COMMIT_RANGE']
67
+ end
68
+
69
+ def request
70
+ Net::HTTP::Post::Multipart.new(uri.path, params, 'User-Agent' => UA)
71
+ end
72
+
73
+ def http
74
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true)
75
+ end
76
+
77
+ def uri
78
+ @uri ||= URI.parse(URL)
79
+ end
80
+
81
+ def file(path)
82
+ UploadIO.new(path, '', File.basename(path)) if path
83
+ end
84
+
85
+ def bool(obj)
86
+ unless obj.nil?
87
+ obj ? 'on' : 'off'
88
+ end
89
+ end
90
+
91
+ def pretty_print(params)
92
+ params = params.map do |key, value|
93
+ value = obfuscate(value) if key == :api_key
94
+ value = value.path if value.respond_to?(:path)
95
+ [key, value]
96
+ end
97
+ JSON.pretty_generate(params.to_h)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dpl
4
+ module Providers
5
+ class Transifex < Provider
6
+ register :transifex
7
+
8
+ status :alpha
9
+
10
+ description sq(<<-STR)
11
+ tbd
12
+ STR
13
+
14
+ python '>= 2.7', '!= 3.0', '!= 3.1', '!= 3.2', '!= 3.3', '< 3.8'
15
+
16
+ required :api_token, %i[username password]
17
+
18
+ env :transifex
19
+
20
+ opt '--api_token TOKEN', 'Transifex API token', secret: true
21
+ opt '--username NAME', 'Transifex username'
22
+ opt '--password PASS', 'Transifex password', secret: true
23
+ opt '--hostname NAME', 'Transifex hostname', default: 'www.transifex.com'
24
+ opt '--cli_version VER', 'CLI version to install', default: '>=0.11'
25
+
26
+ cmds status: 'tx status',
27
+ push: 'tx push --source --no-interactive'
28
+
29
+ msgs login: 'Writing ~/.transifexrc (user: %{username}, password: %{password})'
30
+ errs push: 'Failure pushing to Transifex'
31
+
32
+ RC = sq(<<-RC)
33
+ [%{url}]
34
+ hostname = %{url}
35
+ username = %{username}
36
+ password = %{password}
37
+ RC
38
+
39
+ def install
40
+ pip_install 'transifex-client', 'tx', cli_version
41
+ end
42
+
43
+ def login
44
+ info :login
45
+ write_rc
46
+ shell :status
47
+ end
48
+
49
+ def deploy
50
+ shell :push, retry: true
51
+ end
52
+
53
+ private
54
+
55
+ def write_rc
56
+ write_file '~/.transifexrc', interpolate(RC, opts, secure: true)
57
+ end
58
+
59
+ def username
60
+ super || 'api'
61
+ end
62
+
63
+ def password
64
+ super || api_token
65
+ end
66
+
67
+ def url
68
+ hostname.start_with?('https://') ? hostname : "https://#{hostname}"
69
+ end
70
+ end
71
+ end
72
+ end