Skip to content

Commit

Permalink
fix: match cache in read-only mode (fastlane#21767)
Browse files Browse the repository at this point in the history
  • Loading branch information
nekrich authored and SubhrajyotiSen committed Jan 17, 2024
1 parent 917cdf2 commit afab881
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 24 deletions.
33 changes: 18 additions & 15 deletions match/lib/match/runner.rb
Expand Up @@ -68,14 +68,14 @@ def run(params)
# then in the future address the root cause of https://github.com/fastlane/fastlane/issues/11324
app_identifiers = app_identifiers.flatten.uniq

# Cache bundle ids, certificates, profiles, and devices.
self.cache = Portal::Cache.build(
params: params,
bundle_id_identifiers: app_identifiers
)

# Verify the App ID (as we don't want 'match' to fail at a later point)
if spaceship
# Cache bundle ids, certificates, profiles, and devices.
self.cache = Portal::Cache.build(
params: params,
bundle_id_identifiers: app_identifiers
)

# Verify the App ID (as we don't want 'match' to fail at a later point)
app_identifiers.each do |app_identifier|
spaceship.bundle_identifier_exists(username: params[:username], app_identifier: app_identifier, cached_bundle_ids: self.cache.bundle_ids)
end
Expand Down Expand Up @@ -247,16 +247,19 @@ def fetch_provisioning_profile(params: nil, profile_type:, certificate_id: nil,
stored_profile_path = profiles.last
force = params[:force]

portal_profile = self.cache.portal_profile(stored_profile_path: stored_profile_path, keychain_path: keychain_path) if stored_profile_path
if spaceship
# check if profile needs to be updated only if not in readonly mode
portal_profile = self.cache.portal_profile(stored_profile_path: stored_profile_path, keychain_path: keychain_path) if stored_profile_path

if params[:force_for_new_devices]
force ||= ProfileIncludes.should_force_include_all_devices?(params: params, portal_profile: portal_profile, cached_devices: self.cache.devices)
end
if params[:force_for_new_devices]
force ||= ProfileIncludes.should_force_include_all_devices?(params: params, portal_profile: portal_profile, cached_devices: self.cache.devices)
end

if params[:include_all_certificates]
# Clearing specified certificate id which will prevent a profile being created with only one certificate
certificate_id = nil
force ||= ProfileIncludes.should_force_include_all_certificates?(params: params, portal_profile: portal_profile, cached_certificates: self.cache.certificates)
if params[:include_all_certificates]
# Clearing specified certificate id which will prevent a profile being created with only one certificate
certificate_id = nil
force ||= ProfileIncludes.should_force_include_all_certificates?(params: params, portal_profile: portal_profile, cached_certificates: self.cache.certificates)
end
end

is_new_profile_created = false
Expand Down
71 changes: 62 additions & 9 deletions match/spec/runner_spec.rb
@@ -1,21 +1,14 @@
require_relative 'spec_helper'

describe Match do
describe Match::Runner do
let(:keychain) { 'login.keychain' }
let(:fake_cache) { double('fake_cache') }

before do
allow(ENV).to receive(:[]).and_call_original
allow(ENV).to receive(:[]).with('MATCH_KEYCHAIN_NAME').and_return(keychain)
allow(ENV).to receive(:[]).with('MATCH_KEYCHAIN_PASSWORD').and_return(nil)

allow(Match::Portal::Cache).to receive(:new).and_return(fake_cache)
allow(fake_cache).to receive(:bundle_ids).and_return(nil)
allow(fake_cache).to receive(:certificates).and_return(nil)
allow(fake_cache).to receive(:profiles).and_return(nil)
allow(fake_cache).to receive(:devices).and_return(nil)
allow(fake_cache).to receive(:portal_profile).and_return(nil)
allow(fake_cache).to receive(:reset_certificates)

# There is another test
ENV.delete('FASTLANE_TEAM_ID')
ENV.delete('FASTLANE_TEAM_NAME')
Expand Down Expand Up @@ -48,6 +41,8 @@
keychain_path = FastlaneCore::Helper.keychain_path("login.keychain") # can be .keychain or .keychain-db
destination = File.expand_path("~/Library/MobileDevice/Provisioning Profiles/98264c6b-5151-4349-8d0f-66691e48ae35.mobileprovision")

fake_cache = create_fake_cache

fake_storage = "fake_storage"
expect(Match::Storage::GitStorage).to receive(:configure).with({
git_url: git_url,
Expand Down Expand Up @@ -117,6 +112,8 @@
username: "flapple@something.com"
}

create_fake_cache

config = FastlaneCore::Configuration.create(Match::Options.available_options, values)
repo_dir = "./match/spec/fixtures/existing"
cert_path = "./match/spec/fixtures/existing/certs/distribution/E7P4EE896K.cer"
Expand Down Expand Up @@ -195,6 +192,8 @@
cert_path = "./match/spec/fixtures/existing/certs/distribution/E7P4EE896K.cer"
key_path = "./match/spec/fixtures/existing/certs/distribution/E7P4EE896K.p12"

create_fake_cache

fake_storage = "fake_storage"
expect(Match::Storage::GitStorage).to receive(:configure).with({
git_url: git_url,
Expand Down Expand Up @@ -233,6 +232,58 @@
end.to raise_error("Your certificate 'E7P4EE896K.cer' is not valid, please check end date and renew it if necessary")
end

it "installs profiles in read-only mode", requires_security: true do
# GIVEN

# Downloaded and decrypted storage location.
repo_dir = "./match/spec/fixtures/existing"
# Valid cert and key
stored_valid_cert_path = "#{repo_dir}/certs/distribution/E7P4EE896K.cer"
stored_valid_profile_path = "#{repo_dir}/profiles/appstore/AppStore_tools.fastlane.app.mobileprovision"

# match options
match_test_options = {
readonly: true # Current test suite.
}
match_config = create_match_config_with_git_storage(extra_values: match_test_options)

# EXPECTATIONS

# Ensure cache is not used.
create_fake_cache(allow_usage: false)

# Storage
fake_storage = create_fake_storage(match_config: match_config, repo_dir: repo_dir)
# Ensure no changes in storage are made.
expect(fake_storage).not_to receive(:save_changes!)

# Encryption
fake_encryption = create_fake_encryption(storage: fake_storage)
# Ensure there are no new files to encrypt.
expect(fake_encryption).not_to receive(:encrypt_files)

# Utils
# Ensure match validates stored certificate.
expect(Match::Utils).to receive(:is_cert_valid?).with(stored_valid_cert_path).and_return(true)

# Certificates
# Ensure a new certificate is not generated.
expect(Match::Generator).not_to receive(:generate_certificate).with(match_config, :distribution, fake_storage.working_directory, specific_cert_type: nil)

# Profiles
begin # Ensure profiles are installed, but not validated.
keychain_path = FastlaneCore::Helper.keychain_path("login.keychain")
expect(FastlaneCore::ProvisioningProfile).to receive(:install).with(stored_valid_profile_path, keychain_path)
expect(Match::Generator).not_to receive(:generate_provisioning_profile)
end

# WHEN
Match::Runner.new.run(match_config)

# THEN
# Rely on expectations defined above.
end

it "skips provisioning profiles when skip_provisioning_profiles set to true", requires_security: true do
git_url = "https://github.com/fastlane/fastlane/tree/master/certificates"
values = {
Expand All @@ -250,6 +301,8 @@
keychain_path = FastlaneCore::Helper.keychain_path("login.keychain") # can be .keychain or .keychain-db
destination = File.expand_path("~/Library/MobileDevice/Provisioning Profiles/98264c6b-5151-4349-8d0f-66691e48ae35.mobileprovision")

create_fake_cache

fake_storage = "fake_storage"
expect(Match::Storage::GitStorage).to receive(:configure).with({
git_url: git_url,
Expand Down
110 changes: 110 additions & 0 deletions match/spec/spec_helper.rb
Expand Up @@ -8,3 +8,113 @@ def before_each_match
ENV["DELIVER_USER"] = "flapple@krausefx.com"
ENV["DELIVER_PASSWORD"] = "so_secret"
end

def create_fake_storage(match_config:, repo_dir:)
fake_storage = "fake_storage"
expect(Match::Storage::GitStorage).to receive(:configure).with({
git_url: match_config[:git_url],
shallow_clone: true,
skip_docs: false,
git_branch: "master",
git_full_name: nil,
git_user_email: nil,
clone_branch_directly: false,
git_basic_authorization: nil,
git_bearer_authorization: nil,
git_private_key: nil,
type: match_config[:type],
platform: match_config[:platform]
}).and_return(fake_storage)

allow(fake_storage).to receive(:git_url).and_return(match_config[:git_url])
allow(fake_storage).to receive(:working_directory).and_return(repo_dir)
allow(fake_storage).to receive(:prefixed_working_directory).and_return(repo_dir)

# Ensure match downloads storage.
expect(fake_storage).to receive(:download).and_return(nil)
# Ensure match clears changes after completion.
expect(fake_storage).to receive(:clear_changes).and_return(nil)

return fake_storage
end

def default_app_identifier
"tools.fastlane.app"
end

def default_provisioning_type
"appstore"
end

def default_git_url
"https://github.com/fastlane/fastlane/tree/master/certificates"
end

def default_username
"flapple@something.com"
end

def create_match_config_with_git_storage(extra_values: {}, git_url: nil, app_identifier: nil, type: nil, username: nil)
values = {
app_identifier: app_identifier || default_app_identifier,
type: type || default_provisioning_type,
git_url: git_url || default_git_url,
username: username || default_username,
shallow_clone: true
}

extra_values.each do |k, v|
values[k] = v
end

match_config = FastlaneCore::Configuration.create(Match::Options.available_options, values)

return match_config
end

def create_fake_encryption(storage:)
fake_encryption = "fake_encryption"
expect(Match::Encryption::OpenSSL).to receive(:new).with(keychain_name: storage.git_url, working_directory: storage.working_directory).and_return(fake_encryption)

# Ensure files from storage are decrypted.
expect(fake_encryption).to receive(:decrypt_files).and_return(nil)

return fake_encryption
end

def create_fake_spaceship_ensure
spaceship_ensure = "spaceship"

allow(Match::SpaceshipEnsure).to receive(:new).and_return(spaceship_ensure)

# Ensure app identifiers are validated.
expect(spaceship_ensure).to receive(:bundle_identifier_exists).and_return(true)

return spaceship_ensure
end

def create_fake_cache(allow_usage: true)
fake_cache = 'fake_cache'

allow(Match::Portal::Cache).to receive(:new).and_return(fake_cache)

if allow_usage
allow(fake_cache).to receive(:bundle_ids).and_return(nil)
allow(fake_cache).to receive(:certificates).and_return(nil)
allow(fake_cache).to receive(:profiles).and_return(nil)
allow(fake_cache).to receive(:devices).and_return(nil)
allow(fake_cache).to receive(:portal_profile).and_return(nil)
allow(fake_cache).to receive(:reset_certificates)
else
expect(Match::Portal::Cache).not_to receive(:new)

expect(fake_cache).not_to receive(:bundle_ids)
expect(fake_cache).not_to receive(:certificates)
expect(fake_cache).not_to receive(:profiles)
expect(fake_cache).not_to receive(:devices)
expect(fake_cache).not_to receive(:portal_profile)
expect(fake_cache).not_to receive(:reset_certificates)
end

fake_cache
end

0 comments on commit afab881

Please sign in to comment.