fastlane 2.196.0 → 2.212.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (211) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +88 -81
  4. data/cert/lib/cert/runner.rb +19 -8
  5. data/deliver/lib/assets/ScreenshotsHelp +29 -6
  6. data/deliver/lib/deliver/app_screenshot.rb +30 -4
  7. data/deliver/lib/deliver/app_screenshot_iterator.rb +1 -1
  8. data/deliver/lib/deliver/options.rb +6 -2
  9. data/deliver/lib/deliver/runner.rb +88 -24
  10. data/deliver/lib/deliver/submit_for_review.rb +25 -3
  11. data/deliver/lib/deliver/upload_price_tier.rb +3 -1
  12. data/deliver/lib/deliver/upload_screenshots.rb +2 -2
  13. data/fastlane/lib/assets/AppfileTemplate +1 -1
  14. data/fastlane/lib/assets/AppfileTemplate.swift +1 -1
  15. data/fastlane/lib/fastlane/actions/badge.rb +1 -1
  16. data/fastlane/lib/fastlane/actions/changelog_from_git_commits.rb +1 -1
  17. data/fastlane/lib/fastlane/actions/danger.rb +14 -0
  18. data/fastlane/lib/fastlane/actions/docs/build_app.md +5 -5
  19. data/fastlane/lib/fastlane/actions/docs/capture_android_screenshots.md +19 -2
  20. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +1 -1
  21. data/fastlane/lib/fastlane/actions/docs/run_tests.md +1 -1
  22. data/fastlane/lib/fastlane/actions/docs/upload_to_app_store.md.erb +1 -1
  23. data/fastlane/lib/fastlane/actions/download_dsyms.rb +62 -46
  24. data/fastlane/lib/fastlane/actions/ensure_git_status_clean.rb +15 -4
  25. data/fastlane/lib/fastlane/actions/ensure_xcode_version.rb +1 -1
  26. data/fastlane/lib/fastlane/actions/get_push_certificate.rb +1 -1
  27. data/fastlane/lib/fastlane/actions/get_version_number.rb +8 -3
  28. data/fastlane/lib/fastlane/actions/git_commit.rb +4 -6
  29. data/fastlane/lib/fastlane/actions/import_certificate.rb +1 -1
  30. data/fastlane/lib/fastlane/actions/notarize.rb +29 -11
  31. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +1 -1
  32. data/fastlane/lib/fastlane/actions/pod_push.rb +19 -1
  33. data/fastlane/lib/fastlane/actions/read_podspec.rb +1 -1
  34. data/fastlane/lib/fastlane/actions/run_tests.rb +19 -9
  35. data/fastlane/lib/fastlane/actions/set_github_release.rb +11 -5
  36. data/fastlane/lib/fastlane/actions/setup_ci.rb +13 -4
  37. data/fastlane/lib/fastlane/actions/trainer.rb +49 -0
  38. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +31 -4
  39. data/fastlane/lib/fastlane/actions/update_info_plist.rb +1 -1
  40. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +10 -1
  41. data/fastlane/lib/fastlane/actions/upload_symbols_to_sentry.rb +1 -1
  42. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  43. data/fastlane/lib/fastlane/actions/xcode_install.rb +5 -1
  44. data/fastlane/lib/fastlane/actions/xcode_select.rb +1 -1
  45. data/fastlane/lib/fastlane/actions/xcodebuild.rb +8 -2
  46. data/fastlane/lib/fastlane/actions/xcodes.rb +152 -0
  47. data/fastlane/lib/fastlane/actions/xcov.rb +5 -0
  48. data/fastlane/lib/fastlane/actions/xcversion.rb +17 -7
  49. data/fastlane/lib/fastlane/cli_tools_distributor.rb +5 -0
  50. data/fastlane/lib/fastlane/commands_generator.rb +2 -1
  51. data/fastlane/lib/fastlane/documentation/docs_generator.rb +17 -12
  52. data/fastlane/lib/fastlane/fast_file.rb +18 -5
  53. data/fastlane/lib/fastlane/features.rb +3 -0
  54. data/fastlane/lib/fastlane/helper/xcodebuild_formatter_helper.rb +9 -0
  55. data/fastlane/lib/fastlane/helper/xcodes_helper.rb +28 -0
  56. data/fastlane/lib/fastlane/helper/xcversion_helper.rb +0 -9
  57. data/fastlane/lib/fastlane/lane_manager.rb +1 -1
  58. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +1 -1
  59. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +5 -1
  60. data/fastlane/lib/fastlane/setup/setup_ios.rb +1 -1
  61. data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +1 -1
  62. data/fastlane/lib/fastlane/swift_lane_manager.rb +11 -3
  63. data/fastlane/lib/fastlane/swift_runner_upgrader.rb +54 -1
  64. data/fastlane/lib/fastlane/tools.rb +18 -1
  65. data/fastlane/lib/fastlane/version.rb +1 -1
  66. data/fastlane/swift/Actions.swift +1 -1
  67. data/fastlane/swift/Appfile.swift +2 -2
  68. data/fastlane/swift/ArgumentProcessor.swift +1 -1
  69. data/fastlane/swift/Atomic.swift +150 -0
  70. data/fastlane/swift/ControlCommand.swift +1 -1
  71. data/fastlane/swift/Deliverfile.swift +2 -2
  72. data/fastlane/swift/DeliverfileProtocol.swift +8 -4
  73. data/fastlane/swift/Fastlane.swift +570 -239
  74. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.pbxproj +30 -20
  75. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/xcshareddata/xcschemes/FastlaneRunner.xcscheme +1 -1
  76. data/fastlane/swift/Gymfile.swift +2 -2
  77. data/fastlane/swift/GymfileProtocol.swift +20 -8
  78. data/fastlane/swift/LaneFileProtocol.swift +2 -2
  79. data/fastlane/swift/MainProcess.swift +3 -3
  80. data/fastlane/swift/Matchfile.swift +2 -2
  81. data/fastlane/swift/MatchfileProtocol.swift +21 -5
  82. data/fastlane/swift/OptionalConfigValue.swift +1 -1
  83. data/fastlane/swift/Plugins.swift +1 -1
  84. data/fastlane/swift/Precheckfile.swift +2 -2
  85. data/fastlane/swift/PrecheckfileProtocol.swift +3 -3
  86. data/fastlane/swift/RubyCommand.swift +1 -1
  87. data/fastlane/swift/RubyCommandable.swift +1 -1
  88. data/fastlane/swift/Runner.swift +13 -9
  89. data/fastlane/swift/RunnerArgument.swift +1 -1
  90. data/fastlane/swift/Scanfile.swift +2 -2
  91. data/fastlane/swift/ScanfileProtocol.swift +31 -11
  92. data/fastlane/swift/Screengrabfile.swift +2 -2
  93. data/fastlane/swift/ScreengrabfileProtocol.swift +3 -3
  94. data/fastlane/swift/Snapshotfile.swift +2 -2
  95. data/fastlane/swift/SnapshotfileProtocol.swift +12 -8
  96. data/fastlane/swift/SocketClient.swift +9 -5
  97. data/fastlane/swift/SocketClientDelegateProtocol.swift +2 -2
  98. data/fastlane/swift/SocketResponse.swift +1 -1
  99. data/fastlane/swift/formatting/Brewfile.lock.json +46 -23
  100. data/fastlane/swift/main.swift +1 -1
  101. data/fastlane/swift/upgrade_manifest.json +1 -1
  102. data/fastlane_core/README.md +1 -0
  103. data/fastlane_core/lib/fastlane_core/cert_checker.rb +79 -17
  104. data/fastlane_core/lib/fastlane_core/device_manager.rb +5 -1
  105. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +10 -5
  106. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +409 -26
  107. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +1 -0
  108. data/fastlane_core/lib/fastlane_core/project.rb +19 -2
  109. data/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb +7 -0
  110. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +4 -2
  111. data/frameit/lib/frameit/device.rb +1 -1
  112. data/frameit/lib/frameit/device_types.rb +9 -0
  113. data/frameit/lib/frameit/editor.rb +16 -18
  114. data/frameit/lib/frameit/frame_downloader.rb +1 -1
  115. data/frameit/lib/frameit/trim_box.rb +6 -0
  116. data/gym/lib/gym/generators/build_command_generator.rb +70 -23
  117. data/gym/lib/gym/options.rb +30 -5
  118. data/match/lib/match/change_password.rb +2 -0
  119. data/match/lib/match/commands_generator.rb +2 -1
  120. data/match/lib/match/encryption/openssl.rb +1 -1
  121. data/match/lib/match/encryption.rb +3 -0
  122. data/match/lib/match/generator.rb +1 -0
  123. data/match/lib/match/importer.rb +10 -1
  124. data/match/lib/match/migrate.rb +4 -3
  125. data/match/lib/match/module.rb +54 -2
  126. data/match/lib/match/nuke.rb +114 -47
  127. data/match/lib/match/options.rb +22 -1
  128. data/match/lib/match/runner.rb +25 -6
  129. data/match/lib/match/setup.rb +1 -1
  130. data/match/lib/match/spaceship_ensure.rb +5 -2
  131. data/match/lib/match/storage/gitlab/client.rb +102 -0
  132. data/match/lib/match/storage/gitlab/secure_file.rb +65 -0
  133. data/match/lib/match/storage/gitlab_secure_files.rb +182 -0
  134. data/match/lib/match/storage/google_cloud_storage.rb +7 -6
  135. data/match/lib/match/storage/s3_storage.rb +3 -3
  136. data/match/lib/match/storage.rb +4 -0
  137. data/match/lib/match/table_printer.rb +2 -1
  138. data/match/lib/match/utils.rb +15 -2
  139. data/pem/lib/pem/manager.rb +30 -7
  140. data/pem/lib/pem/options.rb +9 -0
  141. data/pilot/lib/pilot/build_manager.rb +34 -14
  142. data/pilot/lib/pilot/options.rb +6 -1
  143. data/scan/lib/scan/detect_values.rb +6 -0
  144. data/scan/lib/scan/error_handler.rb +9 -0
  145. data/scan/lib/scan/options.rb +49 -9
  146. data/scan/lib/scan/runner.rb +171 -25
  147. data/scan/lib/scan/test_command_generator.rb +65 -5
  148. data/sigh/lib/sigh/download_all.rb +14 -2
  149. data/sigh/lib/sigh/module.rb +3 -1
  150. data/sigh/lib/sigh/options.rb +5 -0
  151. data/sigh/lib/sigh/runner.rb +12 -2
  152. data/snapshot/lib/assets/SnapshotHelper.swift +3 -3
  153. data/snapshot/lib/snapshot/latest_os_version.rb +2 -5
  154. data/snapshot/lib/snapshot/options.rb +24 -8
  155. data/snapshot/lib/snapshot/reports_generator.rb +1 -0
  156. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +10 -3
  157. data/snapshot/lib/snapshot/test_command_generator.rb +37 -2
  158. data/spaceship/lib/spaceship/client.rb +71 -40
  159. data/spaceship/lib/spaceship/commands_generator.rb +1 -1
  160. data/spaceship/lib/spaceship/connect_api/api_client.rb +10 -5
  161. data/spaceship/lib/spaceship/connect_api/models/actor.rb +26 -0
  162. data/spaceship/lib/spaceship/connect_api/models/app.rb +52 -6
  163. data/spaceship/lib/spaceship/connect_api/models/app_info.rb +1 -0
  164. data/spaceship/lib/spaceship/connect_api/models/app_info_localization.rb +5 -0
  165. data/spaceship/lib/spaceship/connect_api/models/app_screenshot_set.rb +7 -0
  166. data/spaceship/lib/spaceship/connect_api/models/app_store_version.rb +1 -1
  167. data/spaceship/lib/spaceship/connect_api/models/app_store_version_localization.rb +27 -10
  168. data/spaceship/lib/spaceship/connect_api/models/build.rb +4 -2
  169. data/spaceship/lib/spaceship/connect_api/models/build_bundle.rb +68 -0
  170. data/spaceship/lib/spaceship/connect_api/models/build_bundle_file_sizes.rb +34 -0
  171. data/spaceship/lib/spaceship/connect_api/models/build_delivery.rb +2 -1
  172. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +4 -0
  173. data/spaceship/lib/spaceship/connect_api/models/device.rb +47 -4
  174. data/spaceship/lib/spaceship/connect_api/models/profile.rb +4 -0
  175. data/spaceship/lib/spaceship/connect_api/models/resolution_center_message.rb +29 -0
  176. data/spaceship/lib/spaceship/connect_api/models/resolution_center_thread.rb +67 -0
  177. data/spaceship/lib/spaceship/connect_api/models/review_rejection.rb +19 -0
  178. data/spaceship/lib/spaceship/connect_api/models/review_submission.rb +86 -0
  179. data/spaceship/lib/spaceship/connect_api/models/review_submission_item.rb +40 -0
  180. data/spaceship/lib/spaceship/connect_api/models/user.rb +5 -0
  181. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +19 -0
  182. data/spaceship/lib/spaceship/connect_api/response.rb +23 -6
  183. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +33 -2
  184. data/spaceship/lib/spaceship/connect_api/token.rb +5 -2
  185. data/spaceship/lib/spaceship/connect_api/tunes/tunes.rb +124 -8
  186. data/spaceship/lib/spaceship/connect_api.rb +9 -0
  187. data/spaceship/lib/spaceship/errors.rb +34 -0
  188. data/spaceship/lib/spaceship/globals.rb +9 -0
  189. data/spaceship/lib/spaceship/hashcash.rb +52 -0
  190. data/spaceship/lib/spaceship/portal/certificate.rb +4 -3
  191. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  192. data/spaceship/lib/spaceship/tunes/app_ratings.rb +6 -6
  193. data/spaceship/lib/spaceship/tunes/iap_families.rb +1 -1
  194. data/spaceship/lib/spaceship/tunes/tunes.rb +0 -1
  195. data/spaceship/lib/spaceship/tunes/tunes_client.rb +79 -21
  196. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +11 -3
  197. data/spaceship/lib/spaceship.rb +1 -0
  198. data/supply/lib/supply/client.rb +2 -7
  199. data/supply/lib/supply/options.rb +8 -0
  200. data/supply/lib/supply/uploader.rb +6 -2
  201. data/trainer/lib/assets/junit.xml.erb +28 -0
  202. data/trainer/lib/trainer/commands_generator.rb +51 -0
  203. data/trainer/lib/trainer/junit_generator.rb +31 -0
  204. data/trainer/lib/trainer/module.rb +10 -0
  205. data/trainer/lib/trainer/options.rb +66 -0
  206. data/trainer/lib/trainer/test_parser.rb +398 -0
  207. data/trainer/lib/trainer/xcresult.rb +403 -0
  208. data/trainer/lib/trainer.rb +7 -0
  209. metadata +49 -24
  210. data/spaceship/lib/spaceship/connect_api/testflight/.testflight.rb.swp +0 -0
  211. data/spaceship/lib/spaceship/tunes/user_detail.rb +0 -15
@@ -9,11 +9,16 @@ require_relative 'module'
9
9
  require_relative 'storage'
10
10
  require_relative 'encryption'
11
11
 
12
+ require 'tempfile'
13
+ require 'base64'
14
+
12
15
  module Match
16
+ # rubocop:disable Metrics/ClassLength
13
17
  class Nuke
14
18
  attr_accessor :params
15
19
  attr_accessor :type
16
20
 
21
+ attr_accessor :safe_remove_certs
17
22
  attr_accessor :certs
18
23
  attr_accessor :profiles
19
24
  attr_accessor :files
@@ -49,6 +54,8 @@ module Match
49
54
  s3_access_key: params[:s3_access_key].to_s,
50
55
  s3_secret_access_key: params[:s3_secret_access_key].to_s,
51
56
  s3_bucket: params[:s3_bucket].to_s,
57
+ s3_object_prefix: params[:s3_object_prefix].to_s,
58
+ gitlab_project: params[:gitlab_project],
52
59
  team_id: params[:team_id] || Spaceship::ConnectAPI.client.portal_team_id
53
60
  })
54
61
  self.storage.download
@@ -66,7 +73,10 @@ module Match
66
73
  hide_keys: [:app_identifier],
67
74
  title: "Summary for match nuke #{Fastlane::VERSION}")
68
75
 
76
+ self.safe_remove_certs = params[:safe_remove_certs] || false
77
+
69
78
  prepare_list
79
+ filter_by_cert
70
80
  print_tables
71
81
 
72
82
  if params[:readonly]
@@ -76,11 +86,13 @@ module Match
76
86
  if (self.certs + self.profiles + self.files).count > 0
77
87
  unless params[:skip_confirmation]
78
88
  UI.error("---")
79
- UI.error("Are you sure you want to completely delete and revoke all the")
80
- UI.error("certificates and provisioning profiles listed above? (y/n)")
89
+ remove_or_revoke_message = self.safe_remove_certs ? "remove" : "revoke"
90
+ UI.error("Are you sure you want to completely delete and #{remove_or_revoke_message} all the")
91
+ UI.error("certificates and delete provisioning profiles listed above? (y/n)")
81
92
  UI.error("Warning: By nuking distribution, both App Store and Ad Hoc profiles will be deleted") if type == "distribution"
82
93
  UI.error("Warning: The :app_identifier value will be ignored - this will delete all profiles for all your apps!") if had_app_identifier
83
94
  UI.error("---")
95
+ print_safe_remove_certs_hint
84
96
  end
85
97
  if params[:skip_confirmation] || UI.confirm("Do you really want to nuke everything listed above?")
86
98
  nuke_it_now!
@@ -91,6 +103,8 @@ module Match
91
103
  else
92
104
  UI.success("No relevant certificates or provisioning profiles found, nothing to nuke here :)")
93
105
  end
106
+ ensure
107
+ self.storage.clear_changes if self.storage
94
108
  end
95
109
 
96
110
  # Be smart about optional values here
@@ -114,10 +128,12 @@ module Match
114
128
  if Spaceship::ConnectAPI.client.in_house? && (type == "distribution" || type == "enterprise")
115
129
  UI.error("---")
116
130
  UI.error("⚠️ Warning: This seems to be an Enterprise account!")
117
- UI.error("By nuking your account's distribution, all your apps deployed via ad-hoc will stop working!") if type == "distribution"
118
- UI.error("By nuking your account's enterprise, all your in-house apps will stop working!") if type == "enterprise"
131
+ unless self.safe_remove_certs
132
+ UI.error("By nuking your account's distribution, all your apps deployed via ad-hoc will stop working!") if type == "distribution"
133
+ UI.error("By nuking your account's enterprise, all your in-house apps will stop working!") if type == "enterprise"
134
+ end
119
135
  UI.error("---")
120
-
136
+ print_safe_remove_certs_hint
121
137
  UI.user_error!("Enterprise account nuke cancelled") unless UI.confirm("Do you really want to nuke your Enterprise account?")
122
138
  end
123
139
  end
@@ -136,10 +152,8 @@ module Match
136
152
  # Get all iOS and macOS profile
137
153
  self.profiles = []
138
154
  prov_types.each do |prov_type|
139
- types = profile_types(prov_type)
140
- # Filtering on 'profileType' seems to be undocumented as of 2020-07-30
141
- # but works on both web session and official API
142
- self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") })
155
+ types = Match.profile_types(prov_type)
156
+ self.profiles += Spaceship::ConnectAPI::Profile.all(filter: { profileType: types.join(",") }, includes: "certificates")
143
157
  end
144
158
 
145
159
  # Gets the main and additional cert types
@@ -163,7 +177,7 @@ module Match
163
177
  keys += self.storage.list_files(file_name: ct.to_s, file_ext: "p12")
164
178
  end
165
179
 
166
- # Finds all the iOS and macOS profofiles in the file storage
180
+ # Finds all the iOS and macOS profiles in the file storage
167
181
  profiles = []
168
182
  prov_types.each do |prov_type|
169
183
  profiles += self.storage.list_files(file_name: prov_type.to_s, file_ext: "mobileprovision")
@@ -173,6 +187,78 @@ module Match
173
187
  self.files = certs + keys + profiles
174
188
  end
175
189
 
190
+ def filter_by_cert
191
+ # Force will continue to revoke and delete all certificates and profiles
192
+ return if self.params[:force] || !UI.interactive?
193
+ return if self.certs.count < 2
194
+
195
+ # Print table showing certificates that can be revoked
196
+ puts("")
197
+ rows = self.certs.each_with_index.collect do |cert, i|
198
+ cert_expiration = cert.expiration_date.nil? ? "Unknown" : Time.parse(cert.expiration_date).strftime("%Y-%m-%d")
199
+ [i + 1, cert.name, cert.id, cert.class.to_s.split("::").last, cert_expiration]
200
+ end
201
+ puts(Terminal::Table.new({
202
+ title: "Certificates that can be #{removed_or_revoked_message}".green,
203
+ headings: ["Option", "Name", "ID", "Type", "Expires"],
204
+ rows: FastlaneCore::PrintTable.transform_output(rows)
205
+ }))
206
+ puts("")
207
+
208
+ UI.important("By default, all listed certificates and profiles will be nuked")
209
+ if UI.confirm("Do you want to only nuke specific certificates and their associated profiles?")
210
+ input_indexes = UI.input("Enter the \"Option\" number(s) from the table above? (comma-separated)").split(',')
211
+
212
+ # Get certificates from option indexes
213
+ self.certs = input_indexes.map do |index|
214
+ self.certs[index.to_i - 1]
215
+ end.compact
216
+
217
+ if self.certs.empty?
218
+ UI.user_error!("No certificates were selected based on option number(s) entered")
219
+ end
220
+
221
+ # Do profile selection logic
222
+ cert_ids = self.certs.map(&:id)
223
+ self.profiles = self.profiles.select do |profile|
224
+ profile_cert_ids = profile.certificates.map(&:id)
225
+ (cert_ids & profile_cert_ids).any?
226
+ end
227
+
228
+ # Do file selection logic
229
+ self.files = self.files.select do |f|
230
+ found = false
231
+
232
+ ext = File.extname(f)
233
+ filename = File.basename(f, ".*")
234
+
235
+ # Attempt to find cert based on filename
236
+ if ext == ".cer" || ext == ".p12"
237
+ found ||= self.certs.any? do |cert|
238
+ filename == cert.id.to_s
239
+ end
240
+ end
241
+
242
+ # Attempt to find profile matched on UUIDs in profile
243
+ if ext == ".mobileprovision" || ext == ".provisionprofile"
244
+ storage_uuid = FastlaneCore::ProvisioningProfile.uuid(f)
245
+
246
+ found ||= self.profiles.any? do |profile|
247
+ tmp_file = Tempfile.new
248
+ tmp_file.write(Base64.decode64(profile.profile_content))
249
+ tmp_file.close
250
+
251
+ # Compare profile uuid in storage to profile uuid on developer portal
252
+ portal_uuid = FastlaneCore::ProvisioningProfile.uuid(tmp_file.path)
253
+ storage_uuid == portal_uuid
254
+ end
255
+ end
256
+
257
+ found
258
+ end
259
+ end
260
+ end
261
+
176
262
  # Print tables to ask the user
177
263
  def print_tables
178
264
  puts("")
@@ -182,7 +268,7 @@ module Match
182
268
  [cert.name, cert.id, cert.class.to_s.split("::").last, cert_expiration]
183
269
  end
184
270
  puts(Terminal::Table.new({
185
- title: "Certificates that are going to be revoked".green,
271
+ title: "Certificates that are going to be #{removed_or_revoked_message}".green,
186
272
  headings: ["Name", "ID", "Type", "Expires"],
187
273
  rows: FastlaneCore::PrintTable.transform_output(rows)
188
274
  }))
@@ -236,8 +322,14 @@ module Match
236
322
  UI.success("Successfully deleted profile")
237
323
  end
238
324
 
239
- UI.header("Revoking #{self.certs.count} certificates...") unless self.certs.count == 0
325
+ removing_or_revoking_message = self.safe_remove_certs ? "Removing" : "Revoking"
326
+ UI.header("#{removing_or_revoking_message} #{self.certs.count} certificates...") unless self.certs.count == 0
240
327
  self.certs.each do |cert|
328
+ if self.safe_remove_certs
329
+ UI.message("Certificate '#{cert.name}' (#{cert.id}) will be removed from repository without revoking it")
330
+ next
331
+ end
332
+
241
333
  UI.message("Revoking certificate '#{cert.name}' (#{cert.id})...")
242
334
  begin
243
335
  cert.delete!
@@ -314,41 +406,16 @@ module Match
314
406
  end
315
407
  end
316
408
 
317
- # The kind of provisioning profile we're interested in
318
- def profile_types(prov_type)
319
- case prov_type.to_sym
320
- when :appstore
321
- return [
322
- Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_STORE,
323
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_STORE,
324
- Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_STORE,
325
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_STORE
326
- ]
327
- when :development
328
- return [
329
- Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_DEVELOPMENT,
330
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_DEVELOPMENT,
331
- Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_DEVELOPMENT,
332
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_DEVELOPMENT
333
- ]
334
- when :enterprise
335
- return [
336
- Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_INHOUSE,
337
- Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_INHOUSE
338
- ]
339
- when :adhoc
340
- return [
341
- Spaceship::ConnectAPI::Profile::ProfileType::IOS_APP_ADHOC,
342
- Spaceship::ConnectAPI::Profile::ProfileType::TVOS_APP_ADHOC
343
- ]
344
- when :developer_id
345
- return [
346
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_APP_DIRECT,
347
- Spaceship::ConnectAPI::Profile::ProfileType::MAC_CATALYST_APP_DIRECT
348
- ]
349
- else
350
- raise "Unknown provisioning type '#{prov_type}'"
351
- end
409
+ # Helpers for `safe_remove_certs`
410
+ def print_safe_remove_certs_hint
411
+ return if self.safe_remove_certs
412
+ UI.important("Hint: You can use --safe_remove_certs option to remove certificates")
413
+ UI.important("from repository without revoking them.")
414
+ end
415
+
416
+ def removed_or_revoked_message
417
+ self.safe_remove_certs ? "removed" : "revoked"
352
418
  end
353
419
  end
420
+ # rubocop:disable Metrics/ClassLength
354
421
  end
@@ -194,6 +194,11 @@ module Match
194
194
  env_name: "MATCH_GOOGLE_CLOUD_PROJECT_ID",
195
195
  description: "ID of the Google Cloud project to use for authentication",
196
196
  optional: true),
197
+ FastlaneCore::ConfigItem.new(key: :skip_google_cloud_account_confirmation,
198
+ env_name: "MATCH_SKIP_GOOGLE_CLOUD_ACCOUNT_CONFIRMATION",
199
+ description: "Skips confirming to use the system google account",
200
+ type: Boolean,
201
+ default_value: false),
197
202
 
198
203
  # Storage: S3
199
204
  FastlaneCore::ConfigItem.new(key: :s3_region,
@@ -218,6 +223,12 @@ module Match
218
223
  description: "Prefix to be used on all objects uploaded to S3",
219
224
  optional: true),
220
225
 
226
+ # Storage: GitLab Secure Files
227
+ FastlaneCore::ConfigItem.new(key: :gitlab_project,
228
+ env_name: "MATCH_GITLAB_PROJECT",
229
+ description: "GitLab Project Path (i.e. 'gitlab-org/gitlab')",
230
+ optional: true),
231
+
221
232
  # Keychain
222
233
  FastlaneCore::ConfigItem.new(key: :keychain_name,
223
234
  short_option: "-s",
@@ -242,6 +253,11 @@ module Match
242
253
  description: "Renew the provisioning profiles if the device count on the developer portal has changed. Ignored for profile types 'appstore' and 'developer_id'",
243
254
  type: Boolean,
244
255
  default_value: false),
256
+ FastlaneCore::ConfigItem.new(key: :include_mac_in_profiles,
257
+ env_name: "MATCH_INCLUDE_MAC_IN_PROFILES",
258
+ description: "Include Apple Silicon Mac devices in provisioning profiles for iOS/iPadOS apps",
259
+ type: Boolean,
260
+ default_value: false),
245
261
  FastlaneCore::ConfigItem.new(key: :include_all_certificates,
246
262
  env_name: "MATCH_INCLUDE_ALL_CERTIFICATES",
247
263
  description: "Include all matching certificates in the provisioning profile. Works only for the 'development' provisioning profile type",
@@ -249,7 +265,7 @@ module Match
249
265
  default_value: false),
250
266
  FastlaneCore::ConfigItem.new(key: :force_for_new_certificates,
251
267
  env_name: "MATCH_FORCE_FOR_NEW_CERTIFICATES",
252
- description: "Renew the provisioning profiles if the device count on the developer portal has changed. Works only for the 'development' provisioning profile type. Requires 'include_all_certificates' option to be 'true'",
268
+ description: "Renew the provisioning profiles if the certificate count on the developer portal has changed. Works only for the 'development' provisioning profile type. Requires 'include_all_certificates' option to be 'true'",
253
269
  type: Boolean,
254
270
  default_value: false),
255
271
  FastlaneCore::ConfigItem.new(key: :skip_confirmation,
@@ -257,6 +273,11 @@ module Match
257
273
  description: "Disables confirmation prompts during nuke, answering them with yes",
258
274
  type: Boolean,
259
275
  default_value: false),
276
+ FastlaneCore::ConfigItem.new(key: :safe_remove_certs,
277
+ env_name: "MATCH_SAFE_REMOVE_CERTS",
278
+ description: "Remove certs from repository during nuke without revoking them on the developer portal",
279
+ type: Boolean,
280
+ default_value: false),
260
281
  FastlaneCore::ConfigItem.new(key: :skip_docs,
261
282
  env_name: "MATCH_SKIP_DOCS",
262
283
  description: "Skip generation of a README.md for the created git repository",
@@ -48,11 +48,13 @@ module Match
48
48
  google_cloud_bucket_name: params[:google_cloud_bucket_name].to_s,
49
49
  google_cloud_keys_file: params[:google_cloud_keys_file].to_s,
50
50
  google_cloud_project_id: params[:google_cloud_project_id].to_s,
51
+ skip_google_cloud_account_confirmation: params[:skip_google_cloud_account_confirmation],
51
52
  s3_region: params[:s3_region],
52
53
  s3_access_key: params[:s3_access_key],
53
54
  s3_secret_access_key: params[:s3_secret_access_key],
54
55
  s3_bucket: params[:s3_bucket],
55
56
  s3_object_prefix: params[:s3_object_prefix],
57
+ gitlab_project: params[:gitlab_project],
56
58
  readonly: params[:readonly],
57
59
  username: params[:readonly] ? nil : params[:username], # only pass username if not readonly
58
60
  team_id: params[:team_id],
@@ -82,9 +84,9 @@ module Match
82
84
  app_identifiers = params[:app_identifier].to_s.split(/\s*,\s*/).uniq
83
85
  end
84
86
 
85
- # sometimes we get an array with arrays, this is a bug. To unblock people using match, I suggest we flatten!
87
+ # sometimes we get an array with arrays, this is a bug. To unblock people using match, I suggest we flatten
86
88
  # then in the future address the root cause of https://github.com/fastlane/fastlane/issues/11324
87
- app_identifiers.flatten!
89
+ app_identifiers = app_identifiers.flatten
88
90
 
89
91
  # Verify the App ID (as we don't want 'match' to fail at a later point)
90
92
  if spaceship
@@ -285,7 +287,10 @@ module Match
285
287
  FileUtils.cp(profile, params[:output_path])
286
288
  end
287
289
 
288
- if spaceship && !spaceship.profile_exists(username: params[:username], uuid: uuid, platform: params[:platform])
290
+ if spaceship && !spaceship.profile_exists(type: prov_type,
291
+ username: params[:username],
292
+ uuid: uuid,
293
+ platform: params[:platform])
289
294
  # This profile is invalid, let's remove the local file and generate a new one
290
295
  File.delete(profile)
291
296
  # This method will be called again, no need to modify `files_to_commit`
@@ -304,6 +309,12 @@ module Match
304
309
  platform: params[:platform]),
305
310
  parsed["TeamIdentifier"].first)
306
311
 
312
+ cert_info = Utils.get_cert_info(parsed["DeveloperCertificates"].first.string).to_h
313
+ Utils.fill_environment(Utils.environment_variable_name_certificate_name(app_identifier: app_identifier,
314
+ type: prov_type,
315
+ platform: params[:platform]),
316
+ cert_info["Common Name"])
317
+
307
318
  Utils.fill_environment(Utils.environment_variable_name_profile_name(app_identifier: app_identifier,
308
319
  type: prov_type,
309
320
  platform: params[:platform]),
@@ -325,7 +336,7 @@ module Match
325
336
 
326
337
  prov_types_without_devices = [:appstore, :developer_id]
327
338
  if !prov_types_without_devices.include?(prov_type) && !params[:force]
328
- force = device_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym)
339
+ force = device_count_different?(profile: profile, keychain_path: keychain_path, platform: params[:platform].to_sym, include_mac_in_profiles: params[:include_mac_in_profiles])
329
340
  else
330
341
  # App Store provisioning profiles don't contain device identifiers and
331
342
  # thus shouldn't be renewed if the device count has changed.
@@ -336,7 +347,7 @@ module Match
336
347
  return force
337
348
  end
338
349
 
339
- def device_count_different?(profile: nil, keychain_path: nil, platform: nil)
350
+ def device_count_different?(profile: nil, keychain_path: nil, platform: nil, include_mac_in_profiles: false)
340
351
  return false unless profile
341
352
 
342
353
  parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
@@ -368,6 +379,9 @@ module Match
368
379
  else
369
380
  []
370
381
  end
382
+ if platform == :ios && include_mac_in_profiles
383
+ device_classes += [Spaceship::ConnectAPI::Device::DeviceClass::APPLE_SILICON_MAC]
384
+ end
371
385
 
372
386
  devices = Spaceship::ConnectAPI::Device.all
373
387
  unless device_classes.empty?
@@ -419,7 +433,12 @@ module Match
419
433
 
420
434
  return false unless portal_profile
421
435
 
422
- profile_certs_count = portal_profile.fetch_all_certificates.count
436
+ # When a certificate expires (not revoked) provisioning profile stays valid.
437
+ # And if we regenerate certificate count will not differ:
438
+ # * For portal certificates, we filter out the expired one but includes a new certificate;
439
+ # * Profile still contains an expired certificate and is valid.
440
+ # Thus, we need to check the validity of profile certificates too.
441
+ profile_certs_count = portal_profile.fetch_all_certificates.select(&:valid?).count
423
442
 
424
443
  certificate_types =
425
444
  case platform
@@ -34,7 +34,7 @@ module Match
34
34
  end
35
35
 
36
36
  def storage_options
37
- return ["git", "google_cloud", "s3"]
37
+ return ["git", "google_cloud", "s3", "gitlab_secure_files"]
38
38
  end
39
39
  end
40
40
  end
@@ -70,13 +70,16 @@ module Match
70
70
  UI.error("for the user #{username}")
71
71
  UI.error("Make sure to use the same user and team every time you run 'match' for this")
72
72
  UI.error("Git repository. This might be caused by revoking the certificate on the Dev Portal")
73
+ UI.error("If missing certificate is a Developer ID Installer, you may need to auth with Apple ID instead of App Store API Key")
73
74
  UI.user_error!("To reset the certificates of your Apple account, you can use the `fastlane match nuke` feature, more information on https://docs.fastlane.tools/actions/match/")
74
75
  end
75
76
 
76
- def profile_exists(username: nil, uuid: nil, platform: nil)
77
+ def profile_exists(type: nil, username: nil, uuid: nil, platform: nil)
77
78
  # App Store Connect API does not allow filter of profile by platform or uuid (as of 2020-07-30)
78
79
  # Need to fetch all profiles and search for uuid on client side
79
- found = Spaceship::ConnectAPI::Profile.all.find do |profile|
80
+ # But we can filter provisioning profiles based on their type (this, in general way faster than getting all profiles)
81
+ filter = { profileType: Match.profile_types(type).join(",") } if type
82
+ found = Spaceship::ConnectAPI::Profile.all(filter: filter).find do |profile|
80
83
  profile.uuid == uuid
81
84
  end
82
85
 
@@ -0,0 +1,102 @@
1
+ require 'net/http/post/multipart'
2
+ require 'securerandom'
3
+
4
+ require_relative '../../module'
5
+ require_relative './secure_file'
6
+
7
+ module Match
8
+ module Storage
9
+ class GitLab
10
+ class Client
11
+ def initialize(api_v4_url:, project_id:, job_token: nil, private_token: nil)
12
+ @job_token = job_token
13
+ @private_token = private_token
14
+ @api_v4_url = api_v4_url
15
+ @project_id = project_id
16
+
17
+ UI.important("JOB_TOKEN and PRIVATE_TOKEN both defined, using JOB_TOKEN to execute this job.") if @job_token && @private_token
18
+ end
19
+
20
+ def base_url
21
+ return "#{@api_v4_url}/projects/#{CGI.escape(@project_id)}/secure_files"
22
+ end
23
+
24
+ def authentication_key
25
+ if @job_token
26
+ return "JOB-TOKEN"
27
+ elsif @private_token
28
+ return "PRIVATE-TOKEN"
29
+ end
30
+ end
31
+
32
+ def authentication_value
33
+ if @job_token
34
+ return @job_token
35
+ elsif @private_token
36
+ return @private_token
37
+ end
38
+ end
39
+
40
+ def files
41
+ @files ||= begin
42
+ url = URI.parse(base_url)
43
+
44
+ request = Net::HTTP::Get.new(url.request_uri)
45
+
46
+ res = execute_request(url, request)
47
+
48
+ data = []
49
+
50
+ JSON.parse(res.body).each do |file|
51
+ data << SecureFile.new(client: self, file: file)
52
+ end
53
+
54
+ data
55
+ end
56
+ end
57
+
58
+ def find_file_by_name(name)
59
+ files.select { |secure_file| secure_file.file.name == name }.first
60
+ end
61
+
62
+ def upload_file(current_file, target_file)
63
+ url = URI.parse(base_url)
64
+
65
+ File.open(current_file) do |file|
66
+ request = Net::HTTP::Post::Multipart.new(
67
+ url.path,
68
+ "file" => UploadIO.new(file, "application/octet-stream"),
69
+ "name" => target_file
70
+ )
71
+
72
+ response = execute_request(url, request)
73
+
74
+ log_upload_error(response, target_file) if response.code != "201"
75
+ end
76
+ end
77
+
78
+ def log_upload_error(response, target_file)
79
+ begin
80
+ response_body = JSON.parse(response.body)
81
+ rescue JSON::ParserError
82
+ response_body = response.body
83
+ end
84
+
85
+ if response_body["message"] && (response_body["message"]["name"] == ["has already been taken"])
86
+ UI.error("#{target_file} already exists in GitLab project #{@project_id}, file not uploaded")
87
+ else
88
+ UI.error("Upload error for #{target_file}: #{response_body}")
89
+ end
90
+ end
91
+
92
+ def execute_request(url, request)
93
+ request[authentication_key] = authentication_value
94
+
95
+ http = Net::HTTP.new(url.host, url.port)
96
+ http.use_ssl = url.instance_of?(URI::HTTPS)
97
+ http.request(request)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,65 @@
1
+ require 'open-uri'
2
+
3
+ require_relative '../../module'
4
+
5
+ module Match
6
+ module Storage
7
+ class GitLab
8
+ class SecureFile
9
+ attr_reader :client, :file
10
+
11
+ def initialize(file:, client:)
12
+ @file = OpenStruct.new(file)
13
+ @client = client
14
+ end
15
+
16
+ def file_url
17
+ "#{@client.base_url}/#{@file.id}"
18
+ end
19
+
20
+ def create_subfolders(working_directory)
21
+ FileUtils.mkdir_p("#{working_directory}/#{destination_file_path}")
22
+ end
23
+
24
+ def destination_file_path
25
+ filename = @file.name.split('/').last
26
+
27
+ @file.name.gsub(filename, '').gsub(%r{^/}, '')
28
+ end
29
+
30
+ def valid_checksum?(file)
31
+ Digest::SHA256.hexdigest(File.read(file)) == @file.checksum
32
+ end
33
+
34
+ def download(working_directory)
35
+ url = URI("#{file_url}/download")
36
+
37
+ begin
38
+ destination_file = "#{working_directory}/#{@file.name}"
39
+
40
+ create_subfolders(working_directory)
41
+ File.open(destination_file, "wb") do |saved_file|
42
+ URI.open(url, "rb", { @client.authentication_key => @client.authentication_value }) do |data|
43
+ saved_file.write(data.read)
44
+ end
45
+
46
+ FileUtils.chmod('u=rw,go-r', destination_file)
47
+ end
48
+
49
+ UI.crash!("Checksum validation failed for #{@file.name}") unless valid_checksum?(destination_file)
50
+ rescue OpenURI::HTTPError => msg
51
+ UI.error("Unable to download #{@file.name} - #{msg}")
52
+ end
53
+ end
54
+
55
+ def delete
56
+ url = URI(file_url)
57
+
58
+ request = Net::HTTP::Delete.new(url.request_uri)
59
+
60
+ @client.execute_request(url, request)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end