Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[match] fix cache issues in read-only mode #21767

Merged
merged 1 commit into from Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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