fastlane 2.195.0 → 2.213.0

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