fastlane 2.105.2 → 2.106.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -72
  3. data/bin/fastlane +24 -1
  4. data/deliver/lib/deliver/app_screenshot.rb +7 -0
  5. data/deliver/lib/deliver/submit_for_review.rb +14 -1
  6. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
  7. data/fastlane/lib/fastlane/actions/download_from_play_store.rb +61 -0
  8. data/fastlane/lib/fastlane/actions/modify_services.rb +7 -5
  9. data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +2 -2
  10. data/fastlane/lib/fastlane/actions/register_device.rb +6 -4
  11. data/fastlane/lib/fastlane/actions/register_devices.rb +9 -8
  12. data/fastlane/lib/fastlane/actions/upload_to_play_store.rb +12 -0
  13. data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +1 -1
  14. data/fastlane/lib/fastlane/version.rb +1 -1
  15. data/fastlane/swift/Deliverfile.swift +1 -1
  16. data/fastlane/swift/Fastlane.swift +24 -6
  17. data/fastlane/swift/Gymfile.swift +1 -1
  18. data/fastlane/swift/Matchfile.swift +1 -1
  19. data/fastlane/swift/MatchfileProtocol.swift +3 -4
  20. data/fastlane/swift/Precheckfile.swift +1 -1
  21. data/fastlane/swift/Scanfile.swift +1 -1
  22. data/fastlane/swift/Screengrabfile.swift +1 -1
  23. data/fastlane/swift/Snapshotfile.swift +1 -1
  24. data/fastlane_core/lib/fastlane_core.rb +0 -1
  25. data/fastlane_core/lib/fastlane_core/device_manager.rb +1 -1
  26. data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
  27. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +13 -10
  28. data/frameit/lib/frameit/editor.rb +3 -2
  29. data/frameit/lib/frameit/frame_downloader.rb +1 -1
  30. data/match/lib/match.rb +2 -2
  31. data/match/lib/match/change_password.rb +31 -18
  32. data/match/lib/match/commands_generator.rb +20 -7
  33. data/match/lib/match/encryption.rb +18 -0
  34. data/match/lib/match/encryption/interface.rb +17 -0
  35. data/match/lib/match/encryption/openssl.rb +155 -0
  36. data/match/lib/match/generator.rb +5 -5
  37. data/match/lib/match/module.rb +5 -1
  38. data/match/lib/match/nuke.rb +33 -15
  39. data/match/lib/match/options.rb +11 -12
  40. data/match/lib/match/runner.rb +59 -38
  41. data/match/lib/match/storage.rb +16 -0
  42. data/match/lib/match/storage/git_storage.rb +228 -0
  43. data/match/lib/match/storage/interface.rb +50 -0
  44. data/snapshot/lib/snapshot/screenshot_rotate.rb +3 -8
  45. data/spaceship/README.md +10 -1
  46. data/spaceship/lib/spaceship/api/.DS_Store +0 -0
  47. data/spaceship/lib/spaceship/api/.base.rb.swp +0 -0
  48. data/spaceship/lib/spaceship/du/du_client.rb +2 -0
  49. data/spaceship/lib/spaceship/portal/device.rb +0 -1
  50. data/spaceship/lib/spaceship/portal/portal_client.rb +7 -1
  51. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  52. data/spaceship/lib/spaceship/tunes/app_details.rb +1 -1
  53. data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
  54. metadata +46 -20
  55. data/fastlane_core/lib/fastlane_core/itunes_search_api.rb +0 -50
  56. data/match/lib/match/encrypt.rb +0 -133
  57. data/match/lib/match/git_helper.rb +0 -209
@@ -2,13 +2,16 @@ require 'commander'
2
2
 
3
3
  require 'fastlane_core/configuration/configuration'
4
4
  require_relative 'module'
5
+
5
6
  require_relative 'nuke'
6
- require_relative 'git_helper'
7
7
  require_relative 'change_password'
8
8
  require_relative 'setup'
9
9
  require_relative 'runner'
10
10
  require_relative 'options'
11
11
 
12
+ require_relative 'storage'
13
+ require_relative 'encryption'
14
+
12
15
  HighLine.track_eof = false
13
16
 
14
17
  module Match
@@ -96,7 +99,7 @@ module Match
96
99
  params.load_configuration_file("Matchfile")
97
100
 
98
101
  Match::ChangePassword.update(params: params)
99
- UI.success("Successfully changed the password. Make sure to update the password on all your clients and servers")
102
+ UI.success("Successfully changed the password. Make sure to update the password on all your clients and servers by running `fastlane match [environment]`")
100
103
  end
101
104
  end
102
105
 
@@ -109,11 +112,21 @@ module Match
109
112
  c.action do |args, options|
110
113
  params = FastlaneCore::Configuration.create(Match::Options.available_options, options.__hash__)
111
114
  params.load_configuration_file("Matchfile")
112
- decrypted_repo = Match::GitHelper.clone(params[:git_url],
113
- params[:shallow_clone],
114
- branch: params[:git_branch],
115
- clone_branch_directly: params[:clone_branch_directly])
116
- UI.success("Repo is at: '#{decrypted_repo}'")
115
+
116
+ storage = Storage.for_mode(params[:storage_mode], {
117
+ git_url: params[:git_url],
118
+ shallow_clone: params[:shallow_clone],
119
+ git_branch: params[:git_branch],
120
+ clone_branch_directly: params[:clone_branch_directly]
121
+ })
122
+ storage.download
123
+
124
+ encryption = Encryption.for_storage_mode(params[:storage_mode], {
125
+ git_url: params[:git_url],
126
+ working_directory: storage.working_directory
127
+ })
128
+ encryption.decrypt_files
129
+ UI.success("Repo is at: '#{storage.working_directory}'")
117
130
  end
118
131
  end
119
132
 
@@ -0,0 +1,18 @@
1
+ require_relative 'encryption/interface'
2
+ require_relative 'encryption/openssl'
3
+
4
+ module Match
5
+ module Encryption
6
+ # Returns the class to be used for a given `storage_mode`
7
+ def self.for_storage_mode(storage_mode, params)
8
+ if storage_mode == "git"
9
+ params[:keychain_name] = params[:git_url]
10
+ return Encryption::OpenSSL.configure(params)
11
+ elsif storage_mode == "google_cloud"
12
+ # return Encryption::GoogleCloudKMS.configure(params)
13
+ else
14
+ UI.user_error!("Invalid storage mode '#{storage_mode}'")
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module Match
2
+ module Encryption
3
+ class Interface
4
+ # Call this method to trigger the actual
5
+ # encryption
6
+ def encrypt_files
7
+ not_implemented(__method__)
8
+ end
9
+
10
+ # Call this method to trigger the actual
11
+ # decryption
12
+ def decrypt_files
13
+ not_implemented(__method__)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,155 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'securerandom'
4
+ require 'security'
5
+ require 'shellwords'
6
+
7
+ require_relative '../module'
8
+ require_relative '../change_password'
9
+
10
+ module Match
11
+ module Encryption
12
+ class OpenSSL < Interface
13
+ attr_accessor :keychain_name
14
+
15
+ attr_accessor :working_directory
16
+
17
+ def self.configure(params)
18
+ return self.new(
19
+ keychain_name: params[:keychain_name],
20
+ working_directory: params[:working_directory]
21
+ )
22
+ end
23
+
24
+ # @param keychain_name: The identifier used to store the passphrase in the Keychain
25
+ # @param working_directory: The path to where the certificates are stored
26
+ def initialize(keychain_name: nil, working_directory: nil)
27
+ self.keychain_name = keychain_name
28
+ self.working_directory = working_directory
29
+ end
30
+
31
+ def encrypt_files
32
+ iterate(self.working_directory) do |current|
33
+ encrypt_specific_file(path: current, password: password)
34
+ UI.success("🔒 Encrypted '#{File.basename(current)}'") if FastlaneCore::Globals.verbose?
35
+ end
36
+ UI.success("🔒 Successfully encrypted certificates repo")
37
+ end
38
+
39
+ def decrypt_files
40
+ iterate(self.working_directory) do |current|
41
+ begin
42
+ decrypt_specific_file(path: current, password: password)
43
+ rescue => ex
44
+ UI.verbose(ex.to_s)
45
+ UI.error("Couldn't decrypt the repo, please make sure you enter the right password!")
46
+ UI.user_error!("Invalid password passed via 'MATCH_PASSWORD'") if ENV["MATCH_PASSWORD"]
47
+ clear_password
48
+ self.decrypt_files # call itself
49
+ return
50
+ end
51
+ UI.success("🔓 Decrypted '#{File.basename(current)}'") if FastlaneCore::Globals.verbose?
52
+ end
53
+ UI.success("🔓 Successfully decrypted certificates repo")
54
+ end
55
+
56
+ def store_password(password)
57
+ Security::InternetPassword.add(server_name(self.keychain_name), "", password)
58
+ end
59
+
60
+ # removes the password from the keychain again
61
+ def clear_password
62
+ Security::InternetPassword.delete(server: server_name(self.keychain_name))
63
+ end
64
+
65
+ private
66
+
67
+ def iterate(source_path)
68
+ Dir[File.join(source_path, "**", "*.{cer,p12,mobileprovision}")].each do |path|
69
+ next if File.directory?(path)
70
+ yield(path)
71
+ end
72
+ end
73
+
74
+ # server name used for accessing the macOS keychain
75
+ def server_name(keychain_name)
76
+ ["match", keychain_name].join("_")
77
+ end
78
+
79
+ # Access the MATCH_PASSWORD, either from ENV variable, Keychain or user input
80
+ def password
81
+ password = ENV["MATCH_PASSWORD"]
82
+ unless password
83
+ item = Security::InternetPassword.find(server: server_name(self.keychain_name))
84
+ password = item.password if item
85
+ end
86
+
87
+ unless password
88
+ if !UI.interactive?
89
+ UI.error("Neither the MATCH_PASSWORD environment variable nor the local keychain contained a password.")
90
+ UI.error("Bailing out instead of asking for a password, since this is non-interactive mode.")
91
+ UI.user_error!("Try setting the MATCH_PASSWORD environment variable, or temporarily enable interactive mode to store a password.")
92
+ else
93
+ UI.important("Enter the passphrase that should be used to encrypt/decrypt your certificates")
94
+ UI.important("This passphrase is specific per repository and will be stored in your local keychain")
95
+ UI.important("Make sure to remember the password, as you'll need it when you run match on a different machine")
96
+ password = ChangePassword.ask_password(confirm: true)
97
+ store_password(password)
98
+ end
99
+ end
100
+
101
+ return password
102
+ end
103
+
104
+ # We encrypt with MD5 because that was the most common default value in older fastlane versions which used the local OpenSSL installation
105
+ # A more secure key and IV generation is needed in the future
106
+ # IV should be randomly generated and provided unencrypted
107
+ # salt should be randomly generated and provided unencrypted (like in the current implementation)
108
+ # key should be generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters
109
+ # Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550
110
+ def encrypt_specific_file(path: nil, password: nil)
111
+ UI.user_error!("No password supplied") if password.to_s.strip.length == 0
112
+
113
+ data_to_encrypt = File.read(path)
114
+ salt = SecureRandom.random_bytes(8)
115
+
116
+ # The :: is important, as there is a name clash
117
+ cipher = ::OpenSSL::Cipher.new('AES-256-CBC')
118
+ cipher.encrypt
119
+ cipher.pkcs5_keyivgen(password, salt, 1, "MD5")
120
+ encrypted_data = "Salted__" + salt + cipher.update(data_to_encrypt) + cipher.final
121
+
122
+ File.write(path, Base64.encode64(encrypted_data))
123
+ rescue FastlaneCore::Interface::FastlaneError
124
+ raise
125
+ rescue => error
126
+ UI.error(error.to_s)
127
+ UI.crash!("Error encrypting '#{path}'")
128
+ end
129
+
130
+ # The encryption parameters in this implementations reflect the old behaviour which depended on the users' local OpenSSL version
131
+ # 1.0.x OpenSSL and earlier versions use MD5, 1.1.0c and newer uses SHA256, we try both before giving an error
132
+ def decrypt_specific_file(path: nil, password: nil, hash_algorithm: "MD5")
133
+ stored_data = Base64.decode64(File.read(path))
134
+ salt = stored_data[8..15]
135
+ data_to_decrypt = stored_data[16..-1]
136
+
137
+ decipher = ::OpenSSL::Cipher.new('AES-256-CBC')
138
+ decipher.decrypt
139
+ decipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)
140
+
141
+ decrypted_data = decipher.update(data_to_decrypt) + decipher.final
142
+
143
+ File.binwrite(path, decrypted_data)
144
+ rescue => error
145
+ fallback_hash_algorithm = "SHA256"
146
+ if hash_algorithm != fallback_hash_algorithm
147
+ decrypt_specific_file(path, password, fallback_hash_algorithm)
148
+ else
149
+ UI.error(error.to_s)
150
+ UI.crash!("Error decrypting '#{path}'")
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -3,11 +3,11 @@ require_relative 'module'
3
3
  module Match
4
4
  # Generate missing resources
5
5
  class Generator
6
- def self.generate_certificate(params, cert_type)
6
+ def self.generate_certificate(params, cert_type, working_directory)
7
7
  require 'cert/runner'
8
8
  require 'cert/options'
9
9
 
10
- output_path = File.join(params[:workspace], "certs", cert_type.to_s)
10
+ output_path = File.join(working_directory, "certs", cert_type.to_s)
11
11
 
12
12
  arguments = FastlaneCore::Configuration.create(Cert::Options.available_options, {
13
13
  development: params[:type] == "development",
@@ -32,7 +32,7 @@ module Match
32
32
  end
33
33
 
34
34
  # We don't care about the signing request
35
- Dir[File.join(params[:workspace], "**", "*.certSigningRequest")].each { |path| File.delete(path) }
35
+ Dir[File.join(working_directory, "**", "*.certSigningRequest")].each { |path| File.delete(path) }
36
36
 
37
37
  # we need to return the path
38
38
  # Inside this directory, there is the `.p12` file and the `.cer` file with the same name, but different extension
@@ -40,7 +40,7 @@ module Match
40
40
  end
41
41
 
42
42
  # @return (String) The UUID of the newly generated profile
43
- def self.generate_provisioning_profile(params: nil, prov_type: nil, certificate_id: nil, app_identifier: nil)
43
+ def self.generate_provisioning_profile(params: nil, prov_type: nil, certificate_id: nil, app_identifier: nil, working_directory: nil)
44
44
  require 'sigh/manager'
45
45
  require 'sigh/options'
46
46
 
@@ -56,7 +56,7 @@ module Match
56
56
 
57
57
  values = {
58
58
  app_identifier: app_identifier,
59
- output_path: File.join(params[:workspace], "profiles", prov_type.to_s),
59
+ output_path: File.join(working_directory, "profiles", prov_type.to_s),
60
60
  username: params[:username],
61
61
  force: true,
62
62
  cert_id: certificate_id,
@@ -4,12 +4,16 @@ module Match
4
4
  Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
5
5
  UI = FastlaneCore::UI
6
6
  ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
7
- DESCRIPTION = "Easily sync your certificates and profiles across your team using git"
7
+ DESCRIPTION = "Easily sync your certificates and profiles across your team"
8
8
 
9
9
  def self.environments
10
10
  return %w(appstore adhoc development enterprise)
11
11
  end
12
12
 
13
+ def self.storage_modes
14
+ return %w(git)
15
+ end
16
+
13
17
  def self.profile_type_sym(type)
14
18
  return type.to_sym
15
19
  end
@@ -3,8 +3,11 @@ require 'terminal-table'
3
3
  require 'spaceship'
4
4
  require 'fastlane_core/provisioning_profile'
5
5
  require 'fastlane_core/print_table'
6
+
6
7
  require_relative 'module'
7
- require_relative 'git_helper'
8
+
9
+ require_relative 'storage'
10
+ require_relative 'encryption'
8
11
 
9
12
  module Match
10
13
  class Nuke
@@ -15,22 +18,35 @@ module Match
15
18
  attr_accessor :profiles
16
19
  attr_accessor :files
17
20
 
21
+ attr_accessor :storage
22
+ attr_accessor :encryption
23
+
18
24
  def run(params, type: nil)
19
25
  self.params = params
20
26
  self.type = type
21
27
 
22
- params[:workspace] = GitHelper.clone(params[:git_url],
23
- params[:shallow_clone],
24
- skip_docs: params[:skip_docs],
25
- branch: params[:git_branch],
26
- git_full_name: params[:git_full_name],
27
- git_user_email: params[:git_user_email],
28
- clone_branch_directly: params[:clone_branch_directly])
28
+ self.storage = Storage.for_mode(params[:storage_mode], {
29
+ git_url: params[:git_url],
30
+ shallow_clone: params[:shallow_clone],
31
+ skip_docs: params[:skip_docs],
32
+ git_branch: params[:git_branch],
33
+ git_full_name: params[:git_full_name],
34
+ git_user_email: params[:git_user_email],
35
+ clone_branch_directly: params[:clone_branch_directly]
36
+ })
37
+ self.storage.download
38
+
39
+ # After the download was complete
40
+ self.encryption = Encryption.for_storage_mode(params[:storage_mode], {
41
+ git_url: params[:git_url],
42
+ working_directory: storage.working_directory
43
+ })
44
+ self.encryption.decrypt_files
29
45
 
30
46
  had_app_identifier = self.params.fetch(:app_identifier, ask: false)
31
47
  self.params[:app_identifier] = '' # we don't really need a value here
32
48
  FastlaneCore::PrintTable.print_values(config: params,
33
- hide_keys: [:app_identifier, :workspace],
49
+ hide_keys: [:app_identifier],
34
50
  title: "Summary for match nuke #{Fastlane::VERSION}")
35
51
 
36
52
  prepare_list
@@ -89,11 +105,11 @@ module Match
89
105
  self.profiles += profile_type(prov_type).all
90
106
  end
91
107
 
92
- certs = Dir[File.join(params[:workspace], "**", cert_type.to_s, "*.cer")]
93
- keys = Dir[File.join(params[:workspace], "**", cert_type.to_s, "*.p12")]
108
+ certs = Dir[File.join(self.storage.working_directory, "**", cert_type.to_s, "*.cer")]
109
+ keys = Dir[File.join(self.storage.working_directory, "**", cert_type.to_s, "*.p12")]
94
110
  profiles = []
95
111
  prov_types.each do |prov_type|
96
- profiles += Dir[File.join(params[:workspace], "**", prov_type.to_s, "*.mobileprovision")]
112
+ profiles += Dir[File.join(self.storage.working_directory, "**", prov_type.to_s, "*.mobileprovision")]
97
113
  end
98
114
 
99
115
  self.files = certs + keys + profiles
@@ -119,7 +135,9 @@ module Match
119
135
  rows = self.profiles.collect do |p|
120
136
  status = p.status == 'Active' ? p.status.green : p.status.red
121
137
 
122
- [p.name, p.id, status, p.type, p.expires.strftime("%Y-%m-%d")]
138
+ # Expires is somtimes nil
139
+ expires = p.expires ? p.expires.strftime("%Y-%m-%d") : nil
140
+ [p.name, p.id, status, p.type, expires]
123
141
  end
124
142
  puts(Terminal::Table.new({
125
143
  title: "Provisioning Profiles that are going to be revoked".green,
@@ -176,13 +194,13 @@ module Match
176
194
 
177
195
  # Now we need to commit and push all this too
178
196
  message = ["[fastlane]", "Nuked", "files", "for", type.to_s].join(" ")
179
- GitHelper.commit_changes(params[:workspace], message, self.params[:git_url], params[:git_branch])
197
+ self.storage.save_changes!(files_to_commit: [], custom_message: message)
180
198
  end
181
199
 
182
200
  private
183
201
 
184
202
  def delete_files!
185
- UI.header("Deleting #{self.files.count} files from the git repo...")
203
+ UI.header("Deleting #{self.files.count} files from the storage...")
186
204
 
187
205
  self.files.each do |file|
188
206
  UI.message("Deleting file '#{File.basename(file)}'...")
@@ -29,6 +29,17 @@ module Match
29
29
  UI.user_error!("Unsupported environment #{value}, must be in #{Match.environments.join(', ')}")
30
30
  end
31
31
  end),
32
+ FastlaneCore::ConfigItem.new(key: :storage_mode,
33
+ env_name: "MATCH_STORAGE_MODE",
34
+ description: "Define where you want to store your certificates",
35
+ is_string: true,
36
+ short_option: "-q",
37
+ default_value: 'git',
38
+ verify_block: proc do |value|
39
+ unless Match.storage_modes.include?(value)
40
+ UI.user_error!("Unsupported storage_mode #{value}, must be in #{Match.storage_modes.join(', ')}")
41
+ end
42
+ end),
32
43
  FastlaneCore::ConfigItem.new(key: :app_identifier,
33
44
  short_option: "-a",
34
45
  env_name: "MATCH_APP_IDENTIFIER",
@@ -115,18 +126,6 @@ module Match
115
126
  description: "Clone just the branch specified, instead of the whole repo. This requires that the branch already exists. Otherwise the command will fail",
116
127
  is_string: false,
117
128
  default_value: false),
118
- FastlaneCore::ConfigItem.new(key: :workspace,
119
- description: nil,
120
- verify_block: proc do |value|
121
- unless Helper.test?
122
- if value.start_with?("/var/folders") || value.include?("tmp/") || value.include?("temp/")
123
- # that's fine
124
- else
125
- UI.user_error!("Specify the `git_url` instead of the `path`")
126
- end
127
- end
128
- end,
129
- optional: true),
130
129
  FastlaneCore::ConfigItem.new(key: :force_for_new_devices,
131
130
  env_name: "MATCH_FORCE_FOR_NEW_DEVICES",
132
131
  description: "Renew the provisioning profiles if the device count on the developer portal has changed. Ignored for profile type 'appstore'",
@@ -3,31 +3,45 @@ require 'fastlane_core/provisioning_profile'
3
3
  require 'fastlane_core/print_table'
4
4
  require 'spaceship/client'
5
5
  require_relative 'generator'
6
- require_relative 'git_helper'
7
6
  require_relative 'module'
8
7
  require_relative 'table_printer'
9
8
  require_relative 'spaceship_ensure'
10
9
  require_relative 'utils'
11
10
 
11
+ require_relative 'storage'
12
+ require_relative 'encryption'
13
+
12
14
  module Match
13
15
  class Runner
14
- attr_accessor :files_to_commmit
16
+ attr_accessor :files_to_commit
15
17
  attr_accessor :spaceship
16
18
 
17
19
  def run(params)
18
- self.files_to_commmit = []
20
+ self.files_to_commit = []
19
21
 
20
22
  FastlaneCore::PrintTable.print_values(config: params,
21
- hide_keys: [:workspace],
22
23
  title: "Summary for match #{Fastlane::VERSION}")
23
24
 
24
- params[:workspace] = GitHelper.clone(params[:git_url],
25
- params[:shallow_clone],
26
- skip_docs: params[:skip_docs],
27
- branch: params[:git_branch],
28
- git_full_name: params[:git_full_name],
29
- git_user_email: params[:git_user_email],
30
- clone_branch_directly: params[:clone_branch_directly])
25
+ # Choose the right storage and encryption implementations
26
+ storage = Storage.for_mode(params[:storage_mode], {
27
+ git_url: params[:git_url],
28
+ shallow_clone: params[:shallow_clone],
29
+ skip_docs: params[:skip_docs],
30
+ git_branch: params[:git_branch],
31
+ git_full_name: params[:git_full_name],
32
+ git_user_email: params[:git_user_email],
33
+ clone_branch_directly: params[:clone_branch_directly],
34
+ type: params[:type].to_s,
35
+ platform: params[:platform].to_s
36
+ })
37
+ storage.download
38
+
39
+ # Init the encryption only after the `storage.download` was called to have the right working directory
40
+ encryption = Encryption.for_storage_mode(params[:storage_mode], {
41
+ git_url: params[:git_url],
42
+ working_directory: storage.working_directory
43
+ })
44
+ encryption.decrypt_files
31
45
 
32
46
  unless params[:readonly]
33
47
  self.spaceship = SpaceshipEnsure.new(params[:username], params[:team_id], params[:team_name])
@@ -54,7 +68,7 @@ module Match
54
68
  end
55
69
 
56
70
  # Certificate
57
- cert_id = fetch_certificate(params: params)
71
+ cert_id = fetch_certificate(params: params, working_directory: storage.working_directory)
58
72
  spaceship.certificate_exists(username: params[:username], certificate_id: cert_id) if spaceship
59
73
 
60
74
  # Provisioning Profiles
@@ -62,14 +76,19 @@ module Match
62
76
  loop do
63
77
  break if fetch_provisioning_profile(params: params,
64
78
  certificate_id: cert_id,
65
- app_identifier: app_identifier)
79
+ app_identifier: app_identifier,
80
+ working_directory: storage.working_directory)
66
81
  end
67
82
  end
68
83
 
69
84
  # Done
70
- if self.files_to_commmit.count > 0 && !params[:readonly]
71
- message = GitHelper.generate_commit_message(params)
72
- GitHelper.commit_changes(params[:workspace], message, params[:git_url], params[:git_branch], self.files_to_commmit)
85
+ if self.files_to_commit.count > 0 && !params[:readonly]
86
+ Dir.chdir(storage.working_directory) do
87
+ return if `git status`.include?("nothing to commit")
88
+ end
89
+
90
+ encryption.encrypt_files
91
+ storage.save_changes!(files_to_commit: self.files_to_commit)
73
92
  end
74
93
 
75
94
  # Print a summary table for each app_identifier
@@ -85,23 +104,23 @@ module Match
85
104
  UI.error("To do so, just pass `readonly: true` to your match call.")
86
105
  raise ex
87
106
  ensure
88
- GitHelper.clear_changes
107
+ storage.clear_changes if storage
89
108
  end
90
109
 
91
- def fetch_certificate(params: nil)
110
+ def fetch_certificate(params: nil, working_directory: nil)
92
111
  cert_type = Match.cert_type_sym(params[:type])
93
112
 
94
- certs = Dir[File.join(params[:workspace], "certs", cert_type.to_s, "*.cer")]
95
- keys = Dir[File.join(params[:workspace], "certs", cert_type.to_s, "*.p12")]
113
+ certs = Dir[File.join(working_directory, "certs", cert_type.to_s, "*.cer")]
114
+ keys = Dir[File.join(working_directory, "certs", cert_type.to_s, "*.p12")]
96
115
 
97
116
  if certs.count == 0 || keys.count == 0
98
117
  UI.important("Couldn't find a valid code signing identity in the git repo for #{cert_type}... creating one for you now")
99
118
  UI.crash!("No code signing identity found and can not create a new one because you enabled `readonly`") if params[:readonly]
100
- cert_path = Generator.generate_certificate(params, cert_type)
119
+ cert_path = Generator.generate_certificate(params, cert_type, working_directory)
101
120
  private_key_path = cert_path.gsub(".cer", ".p12")
102
121
 
103
- self.files_to_commmit << cert_path
104
- self.files_to_commmit << private_key_path
122
+ self.files_to_commit << cert_path
123
+ self.files_to_commit << private_key_path
105
124
  else
106
125
  cert_path = certs.last
107
126
  UI.message("Installing certificate...")
@@ -129,15 +148,16 @@ module Match
129
148
  end
130
149
 
131
150
  # @return [String] The UUID of the provisioning profile so we can verify it with the Apple Developer Portal
132
- def fetch_provisioning_profile(params: nil, certificate_id: nil, app_identifier: nil)
151
+ def fetch_provisioning_profile(params: nil, certificate_id: nil, app_identifier: nil, working_directory: nil)
133
152
  prov_type = Match.profile_type_sym(params[:type])
134
153
 
135
154
  names = [Match::Generator.profile_type_name(prov_type), app_identifier]
136
155
  if params[:platform].to_s != :ios.to_s
137
156
  names.push(params[:platform])
138
157
  end
158
+
139
159
  profile_name = names.join("_").gsub("*", '\*') # this is important, as it shouldn't be a wildcard
140
- base_dir = File.join(params[:workspace], "profiles", prov_type.to_s)
160
+ base_dir = File.join(working_directory, "profiles", prov_type.to_s)
141
161
  profiles = Dir[File.join(base_dir, "#{profile_name}.mobileprovision")]
142
162
  keychain_path = FastlaneCore::Helper.keychain_path(params[:keychain_name]) unless params[:keychain_name].nil?
143
163
 
@@ -168,8 +188,9 @@ module Match
168
188
  profile = Generator.generate_provisioning_profile(params: params,
169
189
  prov_type: prov_type,
170
190
  certificate_id: certificate_id,
171
- app_identifier: app_identifier)
172
- self.files_to_commmit << profile
191
+ app_identifier: app_identifier,
192
+ working_directory: working_directory)
193
+ self.files_to_commit << profile
173
194
  end
174
195
 
175
196
  installed_profile = FastlaneCore::ProvisioningProfile.install(profile, keychain_path)
@@ -179,7 +200,7 @@ module Match
179
200
  if spaceship && !spaceship.profile_exists(username: params[:username], uuid: uuid)
180
201
  # This profile is invalid, let's remove the local file and generate a new one
181
202
  File.delete(profile)
182
- # This method will be called again, no need to modify `files_to_commmit`
203
+ # This method will be called again, no need to modify `files_to_commit`
183
204
  return nil
184
205
  end
185
206
 
@@ -209,16 +230,16 @@ module Match
209
230
  end
210
231
 
211
232
  def device_count_different?(profile: nil, keychain_path: nil)
212
- if profile
213
- parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
214
- uuid = parsed["UUID"]
215
- portal_profile = Spaceship.provisioning_profile.all.detect { |i| i.uuid == uuid }
216
-
217
- if portal_profile
218
- profile_device_count = portal_profile.devices.count
219
- portal_device_count = Spaceship.device.all.count
220
- return portal_device_count != profile_device_count
221
- end
233
+ return false unless profile
234
+
235
+ parsed = FastlaneCore::ProvisioningProfile.parse(profile, keychain_path)
236
+ uuid = parsed["UUID"]
237
+ portal_profile = Spaceship.provisioning_profile.all.detect { |i| i.uuid == uuid }
238
+
239
+ if portal_profile
240
+ profile_device_count = portal_profile.devices.count
241
+ portal_device_count = Spaceship.device.all.count
242
+ return portal_device_count != profile_device_count
222
243
  end
223
244
  return false
224
245
  end