fastlane-plugin-polidea 3.0.0 → 4.0.0.pre.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47c06814e51a640fb88049a4cfc3f5ab117b6a7e4a7998fad230acf2cb4e36c2
4
- data.tar.gz: 295b9efce8dda96c0a86366052b96934303fe3aadebda7e7aef28aec880fd170
3
+ metadata.gz: e96d20dc3d82e5d9eb608192437fe2f96a8b6e5d56d66064b11bc06be1f3bdff
4
+ data.tar.gz: 550ae76e8bd178dad28d8305a16d5870dffdb5872c8a986d7dcd15f8acadcc91
5
5
  SHA512:
6
- metadata.gz: 12749757a7e39a57565db274c5c98145beec26cccc97d1fbde01c29bb76e2089a71392580e87d6863baf3b1e243fbd7ab092dc57813f36d0d5169d156c05bca6
7
- data.tar.gz: 96257d4d5193d777c73622d576012e016e9d7b4192a87bcbba4f1c166feea9f4db32d8bf5817121996855fc4ba1151ff3593bf7684207f7182ef70701fde7f36
6
+ metadata.gz: 2dbb128516aac0a303cac31eeb583244f163de0aefe511465dc6908fbff12d1b25aaf97e74a0d5a6ba8ef2a6a1bdf316b6777277686c5e0107856ceb8999e67c
7
+ data.tar.gz: aa072fe85f8c5df7c18f706c18868b87243cc6da3457908625af9c263818e20346d4318906c393ae1330c5b848e2962fab753e20b3140566abdd31d1c416be8e
data/README.md CHANGED
@@ -17,8 +17,6 @@ fastlane add_plugin polidea
17
17
  Plugin contains following actions:
18
18
  - `extract_app_info`: Extracts application name, icon, version, binary size from .apk/.ipa
19
19
  - `release_notes`: Extracts release notes from git tag message
20
- - `fota_s3`: Custom version of s3 action with Polidea's installation page
21
- - `fota_mail`: Custom version of mailgun action with Polidea's mail template
22
20
  - `shuttle`: Upload new app to Shuttle
23
21
 
24
22
  See more details [here](./docs/Actions.md).
@@ -33,10 +31,7 @@ fastlane actions
33
31
  lane :deploy do
34
32
  (some actions that produces .ipa/.apk)
35
33
  extract_app_info
36
- fota_s3
37
- fota_mail(
38
- to: "hey@polidea.com"
39
- )
34
+ shuttle
40
35
  end
41
36
  ```
42
37
 
@@ -10,9 +10,11 @@ module Fastlane
10
10
  platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME].to_sym
11
11
  options = {
12
12
  api_token: params[:api_token],
13
- plist_url: params[:plist_url],
13
+ ipa: params[:ipa],
14
+ apk: params[:apk],
15
+ dsym: params[:dsym],
16
+ mapping: params[:mapping],
14
17
  prefix_schema: params[:prefix_schema],
15
- apk_url: params[:apk_url],
16
18
  app_identifier: params[:app_identifier],
17
19
  app_version: params[:app_version],
18
20
  build_number: params[:build_number],
@@ -23,7 +25,9 @@ module Fastlane
23
25
  }
24
26
  validate(platform, options)
25
27
  config = get_config(platform, options)
26
- notify(platform, config)
28
+ client = Shuttle::Client.new(base_url(params[:environment]), params[:api_token])
29
+ notify(client, platform, config)
30
+ upload(client, platform, config)
27
31
 
28
32
  UI.success("Successfully uploaded to #{params[:app_identifier]} #{params[:app_version]}.#{params[:build_number]} to Shuttle")
29
33
 
@@ -54,21 +58,31 @@ module Fastlane
54
58
  verify_block: proc do |api_token|
55
59
  UI.user_error!("No API token for Shuttle given, pass using `api_token: 'token'`") unless api_token and !api_token.empty?
56
60
  end),
57
- FastlaneCore::ConfigItem.new(key: :plist_url,
58
- env_name: "SHUTTLE_PLIST_URL",
59
- description: "Url to uploaded plist",
60
- default_value: Actions.lane_context[SharedValues::S3_PLIST_OUTPUT_PATH],
61
- optional: true),
61
+ FastlaneCore::ConfigItem.new(key: :ipa,
62
+ env_name: "",
63
+ description: ".ipa file for the build",
64
+ optional: true,
65
+ default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH]),
66
+ FastlaneCore::ConfigItem.new(key: :dsym,
67
+ env_name: "",
68
+ description: "zipped .dsym package for the build",
69
+ optional: true,
70
+ default_value: Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH]),
71
+ FastlaneCore::ConfigItem.new(key: :apk,
72
+ env_name: "",
73
+ description: ".apk file for the build",
74
+ optional: true,
75
+ default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]),
76
+ FastlaneCore::ConfigItem.new(key: :mapping,
77
+ env_name: "",
78
+ description: "The path to the mapping.txt file",
79
+ optional: true,
80
+ default_value: Actions.lane_context[SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH]),
62
81
  FastlaneCore::ConfigItem.new(key: :prefix_schema,
63
82
  env_name: "SHUTTLE_PREFIX_SCHEMA",
64
83
  description: "Prefix schema in uploaded app",
65
84
  default_value: Actions.lane_context[SharedValues::PREFIX_SCHEMA],
66
85
  optional: true),
67
- FastlaneCore::ConfigItem.new(key: :apk_url,
68
- env_name: "SHUTTLE_APK_URL",
69
- description: "Url to uploaded apk",
70
- default_value: Actions.lane_context[SharedValues::S3_APK_OUTPUT_PATH],
71
- optional: true),
72
86
  FastlaneCore::ConfigItem.new(key: :app_identifier,
73
87
  description: "App identifier, either bundle id or package name",
74
88
  type: String,
@@ -114,14 +128,14 @@ module Fastlane
114
128
 
115
129
  def self.validate(platform, params)
116
130
  case platform
117
- when :android
118
- apk_url = params[:apk_url]
119
- UI.user_error!("No apk url given, pass using `apk_url: 'url'` or make sure s3 action succeded and exposed S3_APK_OUTPUT_PATH shared value") unless apk_url and !apk_url.empty?
120
131
  when :ios
121
- plist_url = params[:plist_url]
122
- UI.user_error!("No plist url given, pass using `plist_url: 'url'` or make sure s3 action succeded and exposed S3_PLIST_OUTPUT_PATH shared value") unless plist_url and !plist_url.empty?
123
132
  url_scheme = params[:prefix_schema]
124
133
  UI.user_error!("No prefix scheme given. Make sure `add_prefix_schema` action succeded before build action") if url_scheme.nil?
134
+ ipa = params[:ipa]
135
+ UI.user_error!("No .ipa file path given. Make sure `gym` action succeded") if ipa.nil?
136
+ when :android
137
+ apk = params[:apk]
138
+ UI.user_error!("No .apk file path given. Make sure `gradle` action succeded") if apk.nil?
125
139
  end
126
140
  end
127
141
 
@@ -135,11 +149,13 @@ module Fastlane
135
149
  releaser = params[:releaser] || commit_author
136
150
 
137
151
  case platform
152
+ when :android
153
+ apk = params[:apk]
154
+ mapping = params[:mapping]
138
155
  when :ios
139
- href = itms_href(params[:plist_url])
140
156
  prefix_schema = params[:prefix_schema]
141
- when :android
142
- href = params[:apk_url]
157
+ ipa = params[:ipa]
158
+ dsym = params[:dsym]
143
159
  end
144
160
 
145
161
  {
@@ -147,18 +163,20 @@ module Fastlane
147
163
  app_identifier: app_identifier,
148
164
  app_version: app_version,
149
165
  build_number: build_number,
150
- href: href,
151
166
  release_notes: release_notes,
152
167
  releaser: releaser,
153
168
  binary_size: binary_size,
154
169
  prefix_schema: prefix_schema,
155
- environment: params[:environment]
170
+ environment: params[:environment],
171
+ ipa: ipa,
172
+ apk: apk,
173
+ mapping: mapping,
174
+ dsym: dsym
156
175
  }
157
176
  end
158
177
 
159
- def self.notify(platform, params)
178
+ def self.notify(client, platform, params)
160
179
  build = {
161
- href: params[:href],
162
180
  version: params[:app_version],
163
181
  releaseNotes: params[:release_notes],
164
182
  bytes: params[:binary_size],
@@ -168,12 +186,40 @@ module Fastlane
168
186
  build[:prefixSchema] = params[:prefix_schema] if platform == :ios
169
187
  build[:versionCode] = params[:build_number] if platform == :android
170
188
 
171
- client = Shuttle::Client.new(base_url(params[:environment]), params[:api_token])
172
-
173
189
  client.create_build(platform, params[:app_identifier], build)
174
190
  end
175
191
  private_class_method :notify
176
192
 
193
+ def self.upload(client, platform, params)
194
+ case platform
195
+ when :ios
196
+ upload_urls = client.get_upload_urls(platform, params[:app_identifier], params[:prefix_schema])
197
+ upload_build_url = upload_urls['buildUrl']
198
+ self.upload_file(upload_build_url, params[:ipa])
199
+ UI.success("Successfully uploaded ipa file")
200
+ if params[:dsym]
201
+ upload_dsym_url = upload_urls['debugFileUrl']
202
+ self.upload_file(upload_dsym_url, params[:dsym])
203
+ UI.success("Successfully uploaded dsym file")
204
+ end
205
+ when :android
206
+ upload_urls = client.get_upload_urls(platform, params[:app_identifier], params[:build_number])
207
+ upload_build_url = upload_urls['buildUrl']
208
+ self.upload_file(upload_build_url, params[:apk])
209
+ UI.success("Successfully uploaded apk file")
210
+ if params[:mapping]
211
+ upload_mapping_file_url = upload_urls['debugFileUrl']
212
+ self.upload_file(upload_mapping_file_url, params[:mapping])
213
+ UI.success("Successfully uploaded mapping file")
214
+ end
215
+ end
216
+ end
217
+
218
+ def self.upload_file(url, build)
219
+ client = S3::Client.new
220
+ client.upload_file(url, build)
221
+ end
222
+
177
223
  def self.base_url(environment)
178
224
  case environment
179
225
  when :production
@@ -184,11 +230,6 @@ module Fastlane
184
230
  end
185
231
  private_class_method :base_url
186
232
 
187
- def self.itms_href(plist_url)
188
- "itms-services://?action=download-manifest&url=#{URI.encode_www_form_component(plist_url)}"
189
- end
190
- private_class_method :itms_href
191
-
192
233
  def self.commit_author
193
234
  sh("git --no-pager show -s --format='%ae'", print_command: false, print_command_output: false).strip
194
235
  end
@@ -0,0 +1,30 @@
1
+ require 'faraday'
2
+ require 'json'
3
+
4
+ module Fastlane
5
+ module S3
6
+ class Client
7
+ def upload_file(url, file)
8
+ retry_options = {
9
+ max: 2,
10
+ interval: 0.05,
11
+ interval_randomness: 0.5,
12
+ backoff_factor: 2
13
+ }
14
+ connection = Faraday.new(
15
+ url
16
+ ) do |conn|
17
+ conn.request :retry, retry_options
18
+ conn.request :multipart
19
+ conn.response :raise_error
20
+ conn.adapter :net_http
21
+ end
22
+
23
+ connection.put(
24
+ '',
25
+ File.read(file)
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -21,19 +21,25 @@ module Fastlane
21
21
  ) do |conn|
22
22
  conn.request :retry, retry_options
23
23
  conn.request :multipart
24
-
25
24
  conn.response :raise_error
26
-
25
+ conn.response :json
27
26
  conn.adapter :net_http
28
27
  end
29
28
  end
30
29
 
31
30
  def create_build(platform, app_identifier, build)
32
31
  @conn.post(
33
- "cd/apps/#{platform}/#{app_identifier}/builds",
32
+ "cd/apps/#{platform}/#{app_identifier}/builds/v2",
34
33
  { build: build }.to_json
35
34
  )
36
35
  end
36
+
37
+ def get_upload_urls(platform, app_id, build_identifier)
38
+ response = @conn.get(
39
+ "projects/apps/#{platform}/#{app_id}/builds/#{build_identifier}/upload-url/v2"
40
+ )
41
+ return response.body
42
+ end
37
43
  end
38
44
  end
39
45
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Polidea
3
- VERSION = "3.0.0"
3
+ VERSION = "4.0.0.pre.3"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-polidea
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 4.0.0.pre.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotrek Dubiel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-03 00:00:00.000000000 Z
11
+ date: 2020-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -230,16 +230,16 @@ dependencies:
230
230
  name: simplecov
231
231
  requirement: !ruby/object:Gem::Requirement
232
232
  requirements:
233
- - - ">="
233
+ - - '='
234
234
  - !ruby/object:Gem::Version
235
- version: '0'
235
+ version: '0.17'
236
236
  type: :development
237
237
  prerelease: false
238
238
  version_requirements: !ruby/object:Gem::Requirement
239
239
  requirements:
240
- - - ">="
240
+ - - '='
241
241
  - !ruby/object:Gem::Version
242
- version: '0'
242
+ version: '0.17'
243
243
  - !ruby/object:Gem::Dependency
244
244
  name: fastlane
245
245
  requirement: !ruby/object:Gem::Requirement
@@ -265,8 +265,6 @@ files:
265
265
  - lib/fastlane/plugin/polidea.rb
266
266
  - lib/fastlane/plugin/polidea/actions/add_prefix_schema.rb
267
267
  - lib/fastlane/plugin/polidea/actions/extract_app_info.rb
268
- - lib/fastlane/plugin/polidea/actions/fota_mail.rb
269
- - lib/fastlane/plugin/polidea/actions/fota_s3.rb
270
268
  - lib/fastlane/plugin/polidea/actions/import_provisioning.rb
271
269
  - lib/fastlane/plugin/polidea/actions/release_notes.rb
272
270
  - lib/fastlane/plugin/polidea/actions/release_notes_from_commits.rb
@@ -275,6 +273,7 @@ files:
275
273
  - lib/fastlane/plugin/polidea/helper/mime.rb
276
274
  - lib/fastlane/plugin/polidea/helper/page_generator.rb
277
275
  - lib/fastlane/plugin/polidea/helper/qr_generator.rb
276
+ - lib/fastlane/plugin/polidea/helper/s3.rb
278
277
  - lib/fastlane/plugin/polidea/helper/shuttle.rb
279
278
  - lib/fastlane/plugin/polidea/version.rb
280
279
  - templates/install.erb
@@ -294,9 +293,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
294
293
  version: '0'
295
294
  required_rubygems_version: !ruby/object:Gem::Requirement
296
295
  requirements:
297
- - - ">="
296
+ - - ">"
298
297
  - !ruby/object:Gem::Version
299
- version: '0'
298
+ version: 1.3.1
300
299
  requirements: []
301
300
  rubygems_version: 3.0.1
302
301
  signing_key:
@@ -1,187 +0,0 @@
1
- require 'redcarpet'
2
- require 'fastlane/erb_template_helper'
3
-
4
- module Fastlane
5
- module Actions
6
- class FotaMailAction < Action
7
- def self.is_supported?(platform)
8
- true
9
- end
10
-
11
- def self.run(options)
12
- Fastlane::Polidea.session.action_launched("fota_mail", options)
13
-
14
- Actions.verify_gem!('premailer')
15
- require 'premailer'
16
- inline_images(options)
17
- mailgunit(options)
18
-
19
- Fastlane::Polidea.session.action_completed("fota_mail")
20
- UI.success "Mail sent!"
21
- end
22
-
23
- def self.description
24
- "Send a success/error message to an email group"
25
- end
26
-
27
- def self.available_options
28
- [
29
- FastlaneCore::ConfigItem.new(key: :postmaster,
30
- env_name: "MAILGUN_SANDBOX_POSTMASTER",
31
- description: "Mailgun sandbox domain postmaster for your mail"),
32
- FastlaneCore::ConfigItem.new(key: :apikey,
33
- env_name: "MAILGUN_APIKEY",
34
- description: "Mailgun apikey for your mail"),
35
- FastlaneCore::ConfigItem.new(key: :to,
36
- env_name: "MAILGUN_TO",
37
- description: "Destination of your mail"),
38
- FastlaneCore::ConfigItem.new(key: :from,
39
- env_name: "MAILGUN_FROM",
40
- optional: true,
41
- description: "Mailgun sender name",
42
- default_value: "Polidea"),
43
- FastlaneCore::ConfigItem.new(key: :success,
44
- env_name: "MAILGUN_SUCCESS",
45
- description: "Was this build successful? (true/false)",
46
- optional: true,
47
- default_value: true,
48
- is_string: false),
49
- FastlaneCore::ConfigItem.new(key: :ci_build_link,
50
- env_name: "MAILGUN_CI_BUILD_LINK",
51
- description: "CI Build Link",
52
- optional: true,
53
- is_string: true),
54
- FastlaneCore::ConfigItem.new(key: :template_path,
55
- env_name: "MAILGUN_TEMPLATE_PATH",
56
- description: "Mail HTML template",
57
- optional: true,
58
- is_string: true),
59
- FastlaneCore::ConfigItem.new(key: :platform,
60
- env_name: "MAILGUN_PLATFORM",
61
- description: "Platform used in mail template",
62
- optional: true,
63
- default_value: Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]),
64
- FastlaneCore::ConfigItem.new(key: :app_icon,
65
- env_name: "MAILGUN_APP_ICON",
66
- description: "Path to app icon file",
67
- is_string: true,
68
- optional: true,
69
- default_value: Actions.lane_context[SharedValues::ICON_OUTPUT_PATH]),
70
- FastlaneCore::ConfigItem.new(key: :app_name,
71
- env_name: "MAILGUN_APP_NAME",
72
- description: "Application name",
73
- is_string: true,
74
- default_value: Actions.lane_context[SharedValues::APP_NAME]),
75
- FastlaneCore::ConfigItem.new(key: :app_version,
76
- env_name: "MAILGUN_APP_VERSION",
77
- description: "Application version",
78
- is_string: true,
79
- default_value: Actions.lane_context[SharedValues::APP_VERSION]),
80
- FastlaneCore::ConfigItem.new(key: :build_number,
81
- env_name: "MAILGUN_BUILD_NUMBER",
82
- description: "Build number",
83
- default_value: Actions.lane_context[SharedValues::BUILD_NUMBER]),
84
- FastlaneCore::ConfigItem.new(key: :installation_link,
85
- env_name: "MAILGUN_INSTALLATION_LINK",
86
- description: "Link to installation page",
87
- is_string: true,
88
- default_value: Actions.lane_context[SharedValues::S3_HTML_OUTPUT_PATH]),
89
- FastlaneCore::ConfigItem.new(key: :release_notes,
90
- env_name: "MAILGUN_RELEASE_NOTES",
91
- description: "Release notes",
92
- type: String,
93
- optional: true,
94
- default_value: Actions.lane_context[SharedValues::RELEASE_NOTES]),
95
- FastlaneCore::ConfigItem.new(key: :binary_size,
96
- env_name: "MAILGUN_BINARY_SIZE",
97
- description: "Binary size",
98
- type: Integer,
99
- default_value: Actions.lane_context[SharedValues::BINARY_SIZE]),
100
- FastlaneCore::ConfigItem.new(key: :inline,
101
- type: Array,
102
- optional: true,
103
- default_value: [])
104
- ]
105
- end
106
-
107
- def self.author
108
- "thiagolioy"
109
- end
110
-
111
- def self.inline_images(options)
112
- options[:inline] = [
113
- qr_code(options[:installation_link])
114
- ]
115
- options[:inline] << File.new(options[:app_icon]) if options[:app_icon]
116
- end
117
-
118
- def self.qr_code(installation_link)
119
- qr_code_path = "/tmp/qr_code.png"
120
- QRGenerator.new(installation_link, ::ChunkyPNG::Color.rgb(21, 18, 126)).generate(qr_code_path)
121
- File.new(qr_code_path)
122
- end
123
-
124
- def self.mailgunit(options)
125
- sandbox_domain = options[:postmaster].split("@").last
126
- body = mail_template(options)
127
-
128
- sh """curl -s --user 'api:#{options[:apikey]}' \
129
- https://api.mailgun.net/v3/#{sandbox_domain}/messages \
130
- -F from='#{options[:from]} <#{options[:postmaster]}>' \
131
- -F to='#{options[:to]}' \
132
- -F subject='#{options[:app_name]} #{options[:app_version]} (#{options[:build_number]}) for #{human_platform(options[:platform])} is ready to install' \
133
- --form-string html=#{Shellwords.escape(body)} \ """ + options[:inline].map { |file| "-F inline=@#{file.path}" }.join(" "), print_command: false, print_command_output: false
134
-
135
- return body
136
- end
137
-
138
- def self.mail_template(options)
139
- hash = {
140
- author: Actions.git_author_email,
141
- last_commit: Actions.last_git_commit_message,
142
- is_android: options[:platform] == :android,
143
- app_icon: options[:app_icon] ? File.basename(options[:app_icon]) : nil,
144
- app_name: options[:app_name],
145
- app_version: options[:app_version],
146
- build_number: options[:build_number],
147
- installation_link: options[:installation_link],
148
- release_notes: options[:release_notes],
149
- platform: options[:platform],
150
- release_date: DateTime.now.strftime('%b %d, %Y'),
151
- binary_size: (options[:binary_size] / 1024.0 / 1024.0).round(2).to_s,
152
- qr_code: "qr_code.png"
153
- }
154
- hash[:success] = options[:success]
155
- hash[:ci_build_link] = options[:ci_build_link]
156
-
157
- # create html from template
158
- html_template_path = options[:template_path]
159
- if html_template_path && File.exist?(html_template_path)
160
- eth = Fastlane::ErbTemplateHelper
161
- html_template = eth.load_from_path(html_template_path)
162
- mail_html = eth.render(html_template, hash)
163
- else
164
- mail_html = PageGenerator.mail(hash)
165
- end
166
-
167
- premailer = Premailer.new(
168
- mail_html,
169
- { warn_level: Premailer::Warnings::SAFE, with_html_string: true }
170
- )
171
- premailer.to_inline_css
172
- end
173
-
174
- def self.human_platform(platform)
175
- case platform
176
- when :ios
177
- "iOS"
178
- when :android
179
- "Android"
180
- else
181
- platform.to_s
182
- end
183
- end
184
- end
185
- end
186
- end
187
- # rubocop:enable Metrics/MethodLength
@@ -1,568 +0,0 @@
1
- # rubocop:disable Metrics/AbcSize
2
- # rubocop:disable Metrics/ClassLength
3
- require 'fastlane/erb_template_helper'
4
- require 'ostruct'
5
- require 'securerandom'
6
-
7
- module Fastlane
8
- module Actions
9
- module SharedValues
10
- S3_IPA_OUTPUT_PATH = :S3_IPA_OUTPUT_PATH
11
- S3_DSYM_OUTPUT_PATH = :S3_DSYM_OUTPUT_PATH
12
- S3_PLIST_OUTPUT_PATH = :S3_PLIST_OUTPUT_PATH
13
- S3_APK_OUTPUT_PATH = :S3_APK_OUTPUT_PATH
14
- S3_MAPPING_OUTPUT_PATH = :S3_MAPPING_OUTPUT_PATH
15
- S3_HTML_OUTPUT_PATH = :S3_HTML_OUTPUT_PATH
16
- S3_VERSION_OUTPUT_PATH = :S3_VERSION_OUTPUT_PATH
17
- S3_ICON_OUTPUT_PATH = :S3_ICON_OUTPUT_PATH
18
- end
19
-
20
- class FotaS3Action < Action
21
- def self.run(config)
22
- Fastlane::Polidea.session.action_launched("fota_s3", config)
23
-
24
- platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME].to_sym
25
-
26
- # Calling fetch on config so that default values will be used
27
- params = {}
28
- params[:ipa] = config[:ipa]
29
- params[:apk] = config[:apk]
30
- params[:icon] = config[:icon]
31
- params[:dsym] = config[:dsym]
32
- params[:mapping] = config[:mapping]
33
- params[:access_key] = config[:access_key]
34
- params[:secret_access_key] = config[:secret_access_key]
35
- params[:bucket] = config[:bucket]
36
- params[:region] = config[:region]
37
- params[:acl] = config[:acl]
38
- params[:upload_metadata] = config[:upload_metadata]
39
- params[:plist_template_path] = config[:plist_template_path]
40
- params[:html_template_path] = config[:html_template_path]
41
- params[:html_file_name] = config[:html_file_name]
42
- params[:version_template_path] = config[:version_template_path]
43
- params[:version_file_name] = config[:version_file_name]
44
- params[:acl] = config[:acl]
45
- params[:release_notes] = config[:release_notes]
46
- params[:treat_bucket_as_domain_name] = config[:treat_bucket_as_domain_name]
47
-
48
- case platform
49
- when :ios
50
- upload_ios(params)
51
- when :android
52
- upload_android(params)
53
- end
54
-
55
- Fastlane::Polidea.session.action_completed("fota_s3")
56
- return true
57
- end
58
-
59
- def self.upload_ios(params)
60
- # Pulling parameters for other uses
61
- s3_region = params[:region]
62
- s3_subdomain = params[:region] ? "s3-#{params[:region]}" : "s3"
63
- s3_access_key = params[:access_key]
64
- s3_secret_access_key = params[:secret_access_key]
65
- s3_bucket = params[:bucket]
66
- ipa_file = params[:ipa]
67
- icon_file = params[:icon]
68
- dsym_file = params[:dsym]
69
- acl = params[:acl]
70
- release_notes = params[:release_notes]
71
- treat_bucket_as_domain_name = params[:treat_bucket_as_domain_name]
72
-
73
- validate(params)
74
- UI.user_error!("No IPA file path given, pass using `ipa: 'ipa path'`") unless ipa_file.to_s.length > 0
75
-
76
- UI.message("Will transform S3 urls from https://s3.amazonaws.com/#{s3_bucket} to https://#{s3_bucket}") if treat_bucket_as_domain_name
77
-
78
- bucket = get_bucket(s3_access_key, s3_secret_access_key, s3_region, s3_bucket)
79
-
80
- # Gets info used for the plist
81
- info = FastlaneCore::IpaFileAnalyser.fetch_info_plist_file(ipa_file)
82
-
83
- build_number = info['CFBundleVersion']
84
- bundle_id = info['CFBundleIdentifier']
85
- bundle_version = info['CFBundleShortVersionString']
86
- app_name = info['CFBundleDisplayName'] || info['CFBundleName']
87
- full_version = "#{bundle_version}.#{build_number}"
88
- url_part = get_url_part(app_name, "ios", bundle_version, build_number)
89
-
90
- plist_template_path = params[:plist_template_path]
91
- html_file_name = params[:html_file_name]
92
- version_template_path = params[:version_template_path]
93
- version_file_name = params[:version_file_name]
94
-
95
- ipa_file_basename = File.basename(ipa_file)
96
- ipa_file_name = "#{url_part}#{ipa_file_basename}"
97
- ipa_file_data = File.open(ipa_file, 'rb')
98
-
99
- ipa_url = self.upload_file(bucket, ipa_file_name, ipa_file_data, acl, treat_bucket_as_domain_name)
100
-
101
- # Setting action and environment variables
102
- Actions.lane_context[SharedValues::S3_IPA_OUTPUT_PATH] = ipa_url
103
- ENV[SharedValues::S3_IPA_OUTPUT_PATH.to_s] = ipa_url
104
-
105
- if dsym_file
106
- dsym_file_basename = File.basename(dsym_file)
107
- dsym_file_name = "#{url_part}#{dsym_file_basename}"
108
- dsym_file_data = File.open(dsym_file, 'rb')
109
-
110
- dsym_url = self.upload_file(bucket, dsym_file_name, dsym_file_data, acl, treat_bucket_as_domain_name)
111
-
112
- dsym_file_data.close
113
- end
114
-
115
- if params[:upload_metadata] == false
116
- return true
117
- end
118
-
119
- #####################################
120
- #
121
- # html and plist building
122
- #
123
- #####################################
124
-
125
- # Creating plist and html names
126
- plist_file_name = "#{url_part}manifest.plist"
127
- plist_url = "https://#{s3_subdomain}.amazonaws.com/#{s3_bucket}/#{plist_file_name}"
128
-
129
- html_file_name ||= "#{url_part}index.html"
130
- html_resources_name = "#{url_part}installation-page"
131
-
132
- version_file_name ||= "#{url_part}version.json"
133
-
134
- # grabs module
135
- eth = Fastlane::ErbTemplateHelper
136
-
137
- # Creates plist from template
138
- if plist_template_path && File.exist?(plist_template_path)
139
- plist_template = eth.load_from_path(plist_template_path)
140
- else
141
- plist_template = eth.load("s3_plist_template")
142
- end
143
- plist_render = eth.render(plist_template, {
144
- url: ipa_url,
145
- ipa_url: ipa_url,
146
- bundle_id: bundle_id,
147
- build_number: build_number,
148
- bundle_version: bundle_version,
149
- title: app_name
150
- })
151
-
152
- # Gets icon from ipa and uploads it
153
- icon_url = self.upload_icon(icon_file, url_part, bucket, acl, treat_bucket_as_domain_name)
154
-
155
- # Creates html from template
156
- html_render = PageGenerator.installation_page({
157
- url: "itms-services://?action=download-manifest&url=#{URI.encode_www_form_component(plist_url)}",
158
- app_version: bundle_version,
159
- build_number: build_number,
160
- app_name: app_name,
161
- app_icon: icon_url,
162
- platform: "ios",
163
- release_notes: release_notes
164
- })
165
-
166
- # Creates version from template
167
- if version_template_path && File.exist?(version_template_path)
168
- version_template = eth.load_from_path(version_template_path)
169
- else
170
- version_template = eth.load("s3_version_template")
171
- end
172
- version_render = eth.render(version_template, {
173
- url: plist_url,
174
- plist_url: plist_url,
175
- ipa_url: ipa_url,
176
- build_number: build_number,
177
- bundle_version: bundle_version,
178
- full_version: full_version
179
- })
180
-
181
- #####################################
182
- #
183
- # html and plist uploading
184
- #
185
- #####################################
186
-
187
- plist_url = self.upload_file(bucket, plist_file_name, plist_render, acl, treat_bucket_as_domain_name)
188
- html_url = self.upload_file(bucket, html_file_name, html_render, acl, treat_bucket_as_domain_name)
189
- self.upload_directory(bucket, html_resources_name, "#{__dir__}/../templates/installation-page", acl)
190
- version_url = self.upload_file(bucket, version_file_name, version_render, acl, treat_bucket_as_domain_name)
191
-
192
- # Setting action and environment variables
193
- Actions.lane_context[SharedValues::S3_PLIST_OUTPUT_PATH] = plist_url
194
- ENV[SharedValues::S3_PLIST_OUTPUT_PATH.to_s] = plist_url
195
-
196
- Actions.lane_context[SharedValues::S3_HTML_OUTPUT_PATH] = html_url
197
- ENV[SharedValues::S3_HTML_OUTPUT_PATH.to_s] = html_url
198
-
199
- Actions.lane_context[SharedValues::S3_VERSION_OUTPUT_PATH] = version_url
200
- ENV[SharedValues::S3_VERSION_OUTPUT_PATH.to_s] = version_url
201
-
202
- UI.success("Successfully uploaded ipa file to '#{Actions.lane_context[SharedValues::S3_IPA_OUTPUT_PATH]}'")
203
- UI.success("Successfully uploaded plist file to '#{Actions.lane_context[SharedValues::S3_PLIST_OUTPUT_PATH]}'")
204
- UI.success("Successfully uploaded html file to '#{Actions.lane_context[SharedValues::S3_HTML_OUTPUT_PATH]}'")
205
- UI.success("Successfully uploaded version file to '#{Actions.lane_context[SharedValues::S3_VERSION_OUTPUT_PATH]}'")
206
-
207
- if icon_url
208
- Actions.lane_context[SharedValues::S3_ICON_OUTPUT_PATH] = icon_url
209
- ENV[SharedValues::S3_ICON_OUTPUT_PATH.to_s] = icon_url
210
- UI.success("Successfully uploaded icon file to '#{Actions.lane_context[SharedValues::S3_ICON_OUTPUT_PATH]}'")
211
- end
212
- if dsym_url
213
- Actions.lane_context[SharedValues::S3_DSYM_OUTPUT_PATH] = dsym_url
214
- ENV[SharedValues::S3_DSYM_OUTPUT_PATH.to_s] = dsym_url
215
- UI.success("Successfully uploaded dsym file to '#{Actions.lane_context[SharedValues::S3_DSYM_OUTPUT_PATH]}'")
216
- end
217
- end
218
-
219
- def self.upload_android(params)
220
- # Pulling parameters for other uses
221
- s3_region = params[:region]
222
- s3_access_key = params[:access_key]
223
- s3_secret_access_key = params[:secret_access_key]
224
- s3_bucket = params[:bucket]
225
- apk_file = params[:apk]
226
- icon_file = params[:icon]
227
- mapping_file = params[:mapping]
228
- acl = params[:acl]
229
- release_notes = params[:release_notes]
230
- treat_bucket_as_domain_name = params[:treat_bucket_as_domain_name]
231
-
232
- validate(params)
233
- UI.user_error!("No APK file path given, pass using `apk: 'apk path'`") unless apk_file.to_s.length > 0
234
-
235
- UI.message("Will transform S3 urls from https://s3.amazonaws.com/#{s3_bucket} to https://#{s3_bucket}") if treat_bucket_as_domain_name
236
-
237
- bucket = get_bucket(s3_access_key, s3_secret_access_key, s3_region, s3_bucket)
238
-
239
- # Gets info used from the apk manifest
240
- manifest = Android::Apk.new(apk_file).manifest
241
-
242
- app_name = manifest.label
243
- build_number = manifest.version_code
244
- app_version = manifest.version_name
245
- url_part = get_url_part(app_name, "android", app_version, build_number)
246
-
247
- html_file_name = params[:html_file_name]
248
-
249
- apk_file_basename = File.basename(apk_file)
250
- apk_file_name = "#{url_part}#{apk_file_basename}"
251
- apk_file_data = File.open(apk_file, 'rb')
252
-
253
- apk_url = self.upload_file(bucket, apk_file_name, apk_file_data, acl, treat_bucket_as_domain_name)
254
-
255
- # Setting action and environment variables
256
- Actions.lane_context[SharedValues::S3_APK_OUTPUT_PATH] = apk_url
257
- ENV[SharedValues::S3_APK_OUTPUT_PATH.to_s] = apk_url
258
-
259
- if mapping_file
260
- mapping_file_basename = File.basename(mapping_file)
261
- mapping_file_name = "#{url_part}#{mapping_file_basename}"
262
- mapping_file_data = File.open(mapping_file, 'rb')
263
-
264
- mapping_url = self.upload_file(bucket, mapping_file_name, mapping_file_data, acl, treat_bucket_as_domain_name)
265
-
266
- # Setting action and environment variables
267
- Actions.lane_context[SharedValues::S3_MAPPING_OUTPUT_PATH] = mapping_url
268
- ENV[SharedValues::S3_MAPPING_OUTPUT_PATH.to_s] = mapping_url
269
-
270
- mapping_file_data.close
271
- end
272
-
273
- #####################################
274
- #
275
- # html building
276
- #
277
- #####################################
278
-
279
- # Creating html names
280
-
281
- html_file_name ||= "#{url_part}index.html"
282
- html_resources_name = "#{url_part}installation-page"
283
-
284
- # Gets icon from ipa and uploads it
285
- icon_url = self.upload_icon(icon_file, url_part, bucket, acl, treat_bucket_as_domain_name)
286
-
287
- # Creates html from template
288
- html_render = PageGenerator.installation_page({
289
- url: apk_url,
290
- app_version: app_version,
291
- build_number: build_number,
292
- app_name: app_name,
293
- app_icon: icon_url,
294
- platform: "android",
295
- release_notes: release_notes
296
- })
297
-
298
- html_url = self.upload_file(bucket, html_file_name, html_render, acl, treat_bucket_as_domain_name)
299
- self.upload_directory(bucket, html_resources_name, "#{__dir__}/../templates/installation-page", acl)
300
-
301
- Actions.lane_context[SharedValues::S3_HTML_OUTPUT_PATH] = html_url
302
- ENV[SharedValues::S3_HTML_OUTPUT_PATH.to_s] = html_url
303
-
304
- UI.success("Successfully uploaded apk file to '#{Actions.lane_context[SharedValues::S3_APK_OUTPUT_PATH]}'")
305
- UI.success("Successfully uploaded html file to '#{Actions.lane_context[SharedValues::S3_HTML_OUTPUT_PATH]}'")
306
-
307
- if icon_url
308
- Actions.lane_context[SharedValues::S3_ICON_OUTPUT_PATH] = icon_url
309
- ENV[SharedValues::S3_ICON_OUTPUT_PATH.to_s] = icon_url
310
- UI.success("Successfully uploaded icon file to '#{Actions.lane_context[SharedValues::S3_ICON_OUTPUT_PATH]}'")
311
- end
312
- if mapping_url
313
- UI.success("Successfully uploaded mapping file to '#{Actions.lane_context[SharedValues::S3_MAPPING_OUTPUT_PATH]}'")
314
- end
315
- end
316
-
317
- def self.validate(params)
318
- s3_access_key = params[:access_key]
319
- s3_secret_access_key = params[:secret_access_key]
320
- s3_bucket = params[:bucket]
321
-
322
- UI.user_error!("No S3 access key given, pass using `access_key: 'key'`") unless s3_access_key.to_s.length > 0
323
- UI.user_error!("No S3 secret access key given, pass using `secret_access_key: 'secret key'`") unless s3_secret_access_key.to_s.length > 0
324
- UI.user_error!("No S3 bucket given, pass using `bucket: 'bucket'`") unless s3_bucket.to_s.length > 0
325
- end
326
-
327
- def self.get_url_part(app_name, platform, app_version, build_number)
328
- random_part = SecureRandom.hex(10)
329
- "#{app_name}/#{platform}/#{app_version}_#{build_number}/#{random_part}/"
330
- end
331
-
332
- def self.get_bucket(s3_access_key, s3_secret_access_key, s3_region, s3_bucket)
333
- self.s3_client(s3_access_key, s3_secret_access_key, s3_region).bucket(s3_bucket)
334
- end
335
-
336
- def self.s3_client(s3_access_key, s3_secret_access_key, s3_region)
337
- Actions.verify_gem!('aws-sdk-s3')
338
- require 'aws-sdk-s3'
339
-
340
- if s3_region
341
- s3_client = Aws::S3::Resource.new(
342
- access_key_id: s3_access_key,
343
- secret_access_key: s3_secret_access_key,
344
- region: s3_region
345
- )
346
- else
347
- s3_client = Aws::S3::Resource.new(
348
- access_key_id: s3_access_key,
349
- secret_access_key: s3_secret_access_key
350
- )
351
- end
352
- s3_client
353
- end
354
-
355
- def self.upload_file(bucket, file_name, file_data, acl, treat_bucket_as_domain_name)
356
- obj = bucket.put_object({
357
- key: file_name,
358
- body: file_data,
359
- acl: acl,
360
- content_type: Mime.content_type_for_file(file_name)
361
- })
362
-
363
- # When you enable versioning on a S3 bucket,
364
- # writing to an object will create an object version
365
- # instead of replacing the existing object.
366
- # http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/ObjectVersion.html
367
- if obj.kind_of? Aws::S3::ObjectVersion
368
- obj = obj.object
369
- end
370
-
371
- if treat_bucket_as_domain_name
372
- # Return public url
373
- shorten_url(obj.public_url.to_s)
374
- else
375
- obj.public_url.to_s
376
- end
377
- end
378
-
379
- def self.upload_directory(bucket, directory_name, directory_path, acl)
380
- files = files_at_path(directory_path)
381
-
382
- files.each do |file|
383
- local_path = directory_path + file
384
- s3_path = directory_name + file
385
-
386
- bucket.put_object({
387
- key: s3_path,
388
- body: File.open(local_path),
389
- acl: acl,
390
- content_type: Mime.content_type_for_file(local_path)
391
- })
392
- end
393
- end
394
-
395
- def self.files_at_path(path)
396
- files = Dir.glob(path + "/**/*")
397
- to_remove = []
398
- files.each do |file|
399
- if File.directory?(file)
400
- to_remove.push file
401
- else
402
- file.slice! path
403
- end
404
- end
405
- to_remove.each do |file|
406
- files.delete file
407
- end
408
- return files
409
- end
410
-
411
- #
412
- # NOT a fan of this as this was taken straight from Shenzhen
413
- # https://github.com/nomad/shenzhen/blob/986792db5d4d16a80c865a2748ee96ba63644821/lib/shenzhen/plugins/s3.rb#L32
414
- #
415
- # Need to find a way to not use this copied method
416
- #
417
- # AGAIN, I am not happy about this right now.
418
- # Using this for prototype reasons.
419
- #
420
- def self.expand_path_with_substitutions_from_ipa_plist(ipa, path)
421
- substitutions = path.scan(/\{CFBundle[^}]+\}/)
422
- return path if substitutions.empty?
423
- info = FastlaneCore::IpaFileAnalyser.fetch_info_plist_file(ipa) or return path
424
-
425
- substitutions.uniq.each do |substitution|
426
- key = substitution[1...-1]
427
- value = info[key]
428
- path.gsub!(Regexp.new(substitution), value) if value
429
- end
430
-
431
- return path
432
- end
433
-
434
- def self.upload_icon(icon_path, url_part, bucket, acl, treat_bucket_as_domain_name)
435
- return unless icon_path
436
- icon_file_basename = File.basename(icon_path)
437
- icon_file = File.open(icon_path)
438
- icon_file_name = "#{url_part}#{icon_file_basename}"
439
- self.upload_file(bucket, icon_file_name, icon_file, acl, treat_bucket_as_domain_name)
440
- end
441
-
442
- def self.shorten_url(url)
443
- uri = URI.parse(url)
444
- uri.scheme + ':/' + uri.path
445
- end
446
-
447
- def self.description
448
- "Generates a plist file and uploads all to AWS S3"
449
- end
450
-
451
- def self.available_options
452
- [
453
- FastlaneCore::ConfigItem.new(key: :ipa,
454
- env_name: "",
455
- description: ".ipa file for the build",
456
- optional: true,
457
- default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH]),
458
- FastlaneCore::ConfigItem.new(key: :dsym,
459
- env_name: "",
460
- description: "zipped .dsym package for the build",
461
- optional: true,
462
- default_value: Actions.lane_context[SharedValues::DSYM_OUTPUT_PATH]),
463
- FastlaneCore::ConfigItem.new(key: :apk,
464
- env_name: "",
465
- description: ".apk file for the build",
466
- optional: true,
467
- default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH]),
468
- FastlaneCore::ConfigItem.new(key: :mapping,
469
- env_name: "",
470
- description: "The path to the mapping.txt file",
471
- optional: true,
472
- default_value: Actions.lane_context[SharedValues::GRADLE_MAPPING_TXT_OUTPUT_PATH]),
473
- FastlaneCore::ConfigItem.new(key: :icon,
474
- env_name: "",
475
- description: "app icon file to upload",
476
- optional: true,
477
- default_value: Actions.lane_context[SharedValues::ICON_OUTPUT_PATH]),
478
- FastlaneCore::ConfigItem.new(key: :upload_metadata,
479
- env_name: "",
480
- description: "Upload relevant metadata for this build",
481
- optional: true,
482
- default_value: true,
483
- is_string: false),
484
- FastlaneCore::ConfigItem.new(key: :plist_template_path,
485
- env_name: "",
486
- description: "plist template path",
487
- optional: true),
488
- FastlaneCore::ConfigItem.new(key: :html_template_path,
489
- env_name: "",
490
- description: "html erb template path",
491
- optional: true),
492
- FastlaneCore::ConfigItem.new(key: :html_file_name,
493
- env_name: "",
494
- description: "uploaded html filename",
495
- optional: true),
496
- FastlaneCore::ConfigItem.new(key: :version_template_path,
497
- env_name: "",
498
- description: "version erb template path",
499
- optional: true),
500
- FastlaneCore::ConfigItem.new(key: :version_file_name,
501
- env_name: "",
502
- description: "uploaded version filename",
503
- optional: true),
504
- FastlaneCore::ConfigItem.new(key: :access_key,
505
- env_name: "S3_ACCESS_KEY",
506
- description: "AWS Access Key ID ",
507
- optional: true,
508
- default_value: ENV['AWS_ACCESS_KEY_ID']),
509
- FastlaneCore::ConfigItem.new(key: :secret_access_key,
510
- env_name: "S3_SECRET_ACCESS_KEY",
511
- description: "AWS Secret Access Key ",
512
- optional: true,
513
- default_value: ENV['AWS_SECRET_ACCESS_KEY']),
514
- FastlaneCore::ConfigItem.new(key: :bucket,
515
- env_name: "S3_BUCKET",
516
- description: "AWS bucket name",
517
- optional: true,
518
- default_value: ENV['AWS_BUCKET_NAME']),
519
- FastlaneCore::ConfigItem.new(key: :region,
520
- env_name: "S3_REGION",
521
- description: "AWS region (for bucket creation) ",
522
- optional: true,
523
- default_value: ENV['AWS_REGION']),
524
- FastlaneCore::ConfigItem.new(key: :acl,
525
- env_name: "S3_ACL",
526
- description: "Uploaded object permissions e.g public_read (default), private, public_read_write, authenticated_read ",
527
- optional: true,
528
- default_value: "public-read"),
529
- FastlaneCore::ConfigItem.new(key: :release_notes,
530
- env_name: "S3_RELEASE_NOTES",
531
- description: "Release notes",
532
- type: String,
533
- optional: true,
534
- default_value: Actions.lane_context[SharedValues::RELEASE_NOTES]),
535
- FastlaneCore::ConfigItem.new(key: :treat_bucket_as_domain_name,
536
- description: "If it's true, it transforms all urls from https://s3.amazonaws.com/BUCKET_NAME to https://BUCKET_NAME",
537
- is_string: false,
538
- optional: true,
539
- default_value: true)
540
- ]
541
- end
542
-
543
- def self.output
544
- [
545
- ['S3_IPA_OUTPUT_PATH', 'Direct HTTP link to the uploaded ipa file'],
546
- ['S3_DSYM_OUTPUT_PATH', 'Direct HTTP link to the uploaded dsym file'],
547
- ['S3_PLIST_OUTPUT_PATH', 'Direct HTTP link to the uploaded plist file'],
548
- ['S3_APK_OUTPUT_PATH', 'Direct HTTP link to the uploaded apk file'],
549
- ['S3_MAPPING_OUTPUT_PATH', 'Direct HTTP link to the uploaded mapping.txt file'],
550
- ['S3_HTML_OUTPUT_PATH', 'Direct HTTP link to the uploaded HTML file'],
551
- ['S3_VERSION_OUTPUT_PATH', 'Direct HTTP link to the uploaded Version file'],
552
- ['S3_ICON_OUTPUT_PATH', 'Direct HTTP link to the uploaded icon file']
553
- ]
554
- end
555
-
556
- def self.author
557
- "joshdholtz"
558
- end
559
-
560
- def self.is_supported?(platform)
561
- [:ios, :android].include? platform
562
- end
563
- end
564
- end
565
- end
566
- # rubocop:enable Metrics/AbcSize
567
- # rubocop:enable Metrics/MethodLength
568
- # rubocop:enable Metrics/ClassLength