fastlane 2.105.2 → 2.106.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +73 -72
- data/bin/fastlane +24 -1
- data/deliver/lib/deliver/app_screenshot.rb +7 -0
- data/deliver/lib/deliver/submit_for_review.rb +14 -1
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/download_from_play_store.rb +61 -0
- data/fastlane/lib/fastlane/actions/modify_services.rb +7 -5
- data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +2 -2
- data/fastlane/lib/fastlane/actions/register_device.rb +6 -4
- data/fastlane/lib/fastlane/actions/register_devices.rb +9 -8
- data/fastlane/lib/fastlane/actions/upload_to_play_store.rb +12 -0
- data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +1 -1
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/Fastlane.swift +24 -6
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +3 -4
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane_core/lib/fastlane_core.rb +0 -1
- data/fastlane_core/lib/fastlane_core/device_manager.rb +1 -1
- data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +13 -10
- data/frameit/lib/frameit/editor.rb +3 -2
- data/frameit/lib/frameit/frame_downloader.rb +1 -1
- data/match/lib/match.rb +2 -2
- data/match/lib/match/change_password.rb +31 -18
- data/match/lib/match/commands_generator.rb +20 -7
- data/match/lib/match/encryption.rb +18 -0
- data/match/lib/match/encryption/interface.rb +17 -0
- data/match/lib/match/encryption/openssl.rb +155 -0
- data/match/lib/match/generator.rb +5 -5
- data/match/lib/match/module.rb +5 -1
- data/match/lib/match/nuke.rb +33 -15
- data/match/lib/match/options.rb +11 -12
- data/match/lib/match/runner.rb +59 -38
- data/match/lib/match/storage.rb +16 -0
- data/match/lib/match/storage/git_storage.rb +228 -0
- data/match/lib/match/storage/interface.rb +50 -0
- data/snapshot/lib/snapshot/screenshot_rotate.rb +3 -8
- data/spaceship/README.md +10 -1
- data/spaceship/lib/spaceship/api/.DS_Store +0 -0
- data/spaceship/lib/spaceship/api/.base.rb.swp +0 -0
- data/spaceship/lib/spaceship/du/du_client.rb +2 -0
- data/spaceship/lib/spaceship/portal/device.rb +0 -1
- data/spaceship/lib/spaceship/portal/portal_client.rb +7 -1
- data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
- data/spaceship/lib/spaceship/tunes/app_details.rb +1 -1
- data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
- metadata +46 -20
- data/fastlane_core/lib/fastlane_core/itunes_search_api.rb +0 -50
- data/match/lib/match/encrypt.rb +0 -133
- 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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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(
|
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(
|
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(
|
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,
|
data/match/lib/match/module.rb
CHANGED
@@ -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
|
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
|
data/match/lib/match/nuke.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
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(
|
93
|
-
keys = Dir[File.join(
|
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(
|
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
|
-
|
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
|
-
|
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
|
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)}'...")
|
data/match/lib/match/options.rb
CHANGED
@@ -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'",
|
data/match/lib/match/runner.rb
CHANGED
@@ -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 :
|
16
|
+
attr_accessor :files_to_commit
|
15
17
|
attr_accessor :spaceship
|
16
18
|
|
17
19
|
def run(params)
|
18
|
-
self.
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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.
|
71
|
-
|
72
|
-
|
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
|
-
|
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(
|
95
|
-
keys = Dir[File.join(
|
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.
|
104
|
-
self.
|
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(
|
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
|
-
|
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 `
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
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
|