Skip to content

Commit

Permalink
[match] increase match speed with caching
Browse files Browse the repository at this point in the history
  • Loading branch information
nekrich committed Dec 5, 2023
1 parent 6e71101 commit 00639f2
Show file tree
Hide file tree
Showing 22 changed files with 1,184 additions and 349 deletions.
3 changes: 3 additions & 0 deletions match/lib/match.rb
Expand Up @@ -12,3 +12,6 @@
require_relative 'match/storage'
require_relative 'match/encryption'
require_relative 'match/module'
require_relative 'match/portal_cache'
require_relative 'match/portal_fetcher'
require_relative 'match/profile_includes'
10 changes: 9 additions & 1 deletion match/lib/match/generator.rb
@@ -1,4 +1,5 @@
require_relative 'module'
require_relative 'profile_includes'

module Match
# Generate missing resources
Expand Down Expand Up @@ -57,7 +58,7 @@ def self.generate_certificate(params, cert_type, working_directory, specific_cer
end

# @return (String) The UUID of the newly generated profile
def self.generate_provisioning_profile(params: nil, prov_type: nil, certificate_id: nil, app_identifier: nil, force: true, working_directory: nil)
def self.generate_provisioning_profile(params: nil, prov_type: nil, certificate_id: nil, app_identifier: nil, force: true, cache: nil, working_directory: nil)
require 'sigh/manager'
require 'sigh/options'

Expand Down Expand Up @@ -104,6 +105,13 @@ def self.generate_provisioning_profile(params: nil, prov_type: nil, certificate_
values[:development] = true
end

if cache
values[:cached_certificates] = cache.certificates
values[:cached_devices] = cache.devices
values[:cached_bundle_ids] = cache.bundle_ids
values[:cached_profiles] = cache.profiles
end

arguments = FastlaneCore::Configuration.create(Sigh::Options.available_options, values)

Sigh.config = arguments
Expand Down
2 changes: 1 addition & 1 deletion match/lib/match/module.rb
Expand Up @@ -23,7 +23,7 @@ def self.profile_type_sym(type)
end

def self.cert_type_sym(type)
type = type.to_s
type = type.to_s.downcase
return :mac_installer_distribution if type == "mac_installer_distribution"
return :developer_id_installer if type == "developer_id_installer"
return :developer_id_application if type == "developer_id"
Expand Down
114 changes: 114 additions & 0 deletions match/lib/match/portal_cache.rb
@@ -0,0 +1,114 @@
require 'fastlane_core/provisioning_profile'
require 'spaceship/client'
require_relative 'portal_fetcher'
module Match
class Portal
class Cache
def self.build(params:, bundle_id_identifiers:)
require_relative 'profile_includes'
require 'sigh'

profile_type = Sigh.profile_type_for_distribution_type(
platform: params[:platform],
distribution_type: params[:type]
)

cache = Portal::Cache.new(
platform: params[:platform],
profile_type: profile_type,
additional_cert_types: params[:additional_cert_types],
bundle_id_identifiers: bundle_id_identifiers,
needs_profiles_devices: ProfileIncludes.can_force_include?(params: params, notify: true) && !params[:force] && !params[:readonly],
needs_profiles_certificate_content: !ProfileIncludes.can_force_include_all_certificates?(params: params),
include_mac_in_profiles: params[:include_mac_in_profiles]
)

return cache
end

attr_reader :platform, :profile_type, :bundle_id_identifiers, :additional_cert_types, :needs_profiles_devices, :needs_profiles_certificate_content, :include_mac_in_profiles

def initialize(platform:, profile_type:, additional_cert_types:, bundle_id_identifiers:, needs_profiles_devices:, needs_profiles_certificate_content:, include_mac_in_profiles:)
@platform = platform
@profile_type = profile_type

# Bundle Ids
@bundle_id_identifiers = bundle_id_identifiers

# Certs
@additional_cert_types = additional_cert_types

# Profiles
@needs_profiles_devices = needs_profiles_devices
@needs_profiles_certificate_content = needs_profiles_certificate_content

# Devices
@include_mac_in_profiles = include_mac_in_profiles
end

def portal_profile(stored_profile_path:, keychain_path:)
parsed = FastlaneCore::ProvisioningProfile.parse(stored_profile_path, keychain_path)
uuid = parsed["UUID"]

portal_profile = self.profiles.detect { |i| i.uuid == uuid }

portal_profile
end

def reset_certificates
@certificates = nil
end

def forget_portal_profile(portal_profile)
return unless @profiles && portal_profile

@profiles -= [portal_profile]
end

def bundle_ids
return @bundle_ids.dup if @bundle_ids

@bundle_ids = Match::Portal::Fetcher.bundle_ids(
bundle_id_identifiers: @bundle_id_identifiers
)

return @bundle_ids.dup
end

def certificates
return @certificates.dup if @certificates

@certificates = Match::Portal::Fetcher.certificates(
platform: @platform,
profile_type: @profile_type,
additional_cert_types: @additional_cert_types
)

return @certificates.dup
end

def profiles
return @profiles.dup if @profiles

@profiles = Match::Portal::Fetcher.profiles(
profile_type: @profile_type,
needs_profiles_devices: @needs_profiles_devices,
needs_profiles_certificate_content: @needs_profiles_certificate_content
)

return @profiles.dup
end

def devices
return @devices.dup if @devices

@devices = Match::Portal::Fetcher.devices(
platform: @platform,
include_mac_in_profiles: @include_mac_in_profiles
)

return @devices.dup
end
end
end
end
79 changes: 79 additions & 0 deletions match/lib/match/portal_fetcher.rb
@@ -0,0 +1,79 @@
require 'fastlane_core/provisioning_profile'
require 'spaceship/client'
require 'spaceship/connect_api/models/profile'

module Match
class Portal
module Fetcher
def self.profiles(profile_type:, needs_profiles_devices: false, needs_profiles_certificate_content: false, name: nil)
includes = ['bundleId']

if needs_profiles_devices
includes += ['devices', 'certificates']
end

if needs_profiles_certificate_content
includes += ['certificates']
end

profiles = Spaceship::ConnectAPI::Profile.all(
filter: { profileType: profile_type, name: name }.compact,
includes: includes.uniq.join(',')
)

profiles
end

def self.certificates(platform:, profile_type:, additional_cert_types:)
require 'sigh'
certificate_types = Sigh.certificate_types_for_profile_and_platform(platform: platform, profile_type: profile_type)

additional_cert_types ||= []
additional_cert_types.map! do |cert_type|
case Match.cert_type_sym(cert_type)
when :mac_installer_distribution
Spaceship::ConnectAPI::Certificate::CertificateType::MAC_INSTALLER_DISTRIBUTION
when :developer_id_installer
Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPER_ID_INSTALLER
end
end

certificate_types += additional_cert_types

filter = { certificateType: certificate_types.sort.join(',') } unless certificate_types.empty?

certificates = Spaceship::ConnectAPI::Certificate.all(
filter: filter
).select(&:valid?)

certificates
end

def self.devices(platform: nil, include_mac_in_profiles: false)
devices = Spaceship::ConnectAPI::Device.devices_for_platform(
platform: platform,
include_mac_in_profiles: include_mac_in_profiles
)

devices
end

def self.bundle_ids(bundle_id_identifiers: nil)
filter = nil
if bundle_id_identifiers
if bundle_id_identifiers.kind_of?(Array)
filter = { identifier: bundle_id_identifiers.join(',') }
else
filter = { identifier: bundle_id_identifiers }
end
end

bundle_ids = Spaceship::ConnectAPI::BundleId.all(
filter: filter
)

bundle_ids
end
end
end
end
121 changes: 121 additions & 0 deletions match/lib/match/profile_includes.rb
@@ -0,0 +1,121 @@
require_relative 'portal_fetcher'
require_relative 'module'

module Match
class ProfileIncludes
PROV_TYPES_WITH_DEVICES = [:adhoc, :development]
PROV_TYPES_WITH_MULTIPLE_CERTIFICATES = [:development]

def self.can_force_include?(params:, notify:)
self.can_force_include_all_devices?(params: params, notify: notify) &&
self.can_force_include_all_certificates?(params: params, notify: notify)
end

###############
#
# DEVICES
#
###############

def self.should_force_include_all_devices?(params:, portal_profile:, cached_devices:)
return false unless self.can_force_include_all_devices?(params: params)

force = device_count_different?(portal_profile: portal_profile, platform: params[:platform], include_mac_in_profiles: params[:include_mac_in_profiles], cached_devices: cached_devices)

return force
end

def self.can_force_include_all_devices?(params:, notify: false)
return false if params[:readonly] || params[:force]
return false unless params[:force_for_new_devices]

provisioning_type = params[:type].to_sym

can_force = PROV_TYPES_WITH_DEVICES.include?(provisioning_type)

if !can_force && notify
# App Store provisioning profiles don't contain device identifiers and
# thus shouldn't be renewed if the device count has changed.
UI.important("Warning: `force_for_new_devices` is set but is ignored for #{provisioning_type}.")
UI.important("You can safely stop specifying `force_for_new_devices` when running Match for type '#{provisioning_type}'.")
end

can_force
end

def self.device_count_different?(portal_profile:, platform:, include_mac_in_profiles:, cached_devices:)
return false unless portal_profile

profile_device_count = portal_profile.devices.count

devices = cached_devices
devices ||= Match::Portal::Fetcher.devices(platform: platform, include_mac_in_profiles: include_mac_in_profiles)
portal_device_count = devices.size

device_count_different = portal_device_count != profile_device_count

UI.important("Devices count differs. Portal count: #{portal_device_count}. Profile count: #{profile_device_count}") if device_count_different

return device_count_different
end

###############
#
# CERTIFICATES
#
###############

def self.should_force_include_all_certificates?(params:, portal_profile:, cached_certificates:)
return false unless self.can_force_include_all_certificates?(params: params)

force = certificate_count_different?(portal_profile: portal_profile, platform: params[:platform], cached_certificates: cached_certificates)

return force
end

def self.can_force_include_all_certificates?(params:, notify: false)
return false if params[:readonly] || params[:force]
return false unless params[:force_for_new_certificates]

unless params[:include_all_certificates]
UI.important("You specified 'force_for_new_certificates: true', but new certificates will not be added, cause 'include_all_certificates' is 'false'") if notify
return false
end

provisioning_type = params[:type].to_sym

can_force = PROV_TYPES_WITH_MULTIPLE_CERTIFICATES.include?(provisioning_type)

if !can_force && notify
# All other (not development) provisioning profiles don't contain
# multiple certificates, thus shouldn't be renewed
# if the certificates count has changed.
UI.important("Warning: `force_for_new_certificates` is set but is ignored for non-'development' provisioning profiles.")
UI.important("You can safely stop specifying `force_for_new_certificates` when running Match for '#{provisioning_type}' provisioning profiles.")
end

can_force
end

def self.certificate_count_different?(portal_profile:, platform:, cached_certificates:)
return false unless portal_profile

# When a certificate expires (not revoked) provisioning profile stays valid.
# And if we regenerate certificate count will not differ:
# * For portal certificates, we filter out the expired one but includes a new certificate;
# * Profile still contains an expired certificate and is valid.
# Thus, we need to check the validity of profile certificates too.
profile_certs_count = portal_profile.certificates.select(&:valid?).count

certificates = cached_certificates
certificates ||= Match::Portal::Fetcher.certificates(platform: platform, profile_type: portal_profile.profile_type)
portal_certs_count = certificates.size

certificate_count_different = portal_certs_count != profile_certs_count

UI.important("Certificate count differs. Portal count: #{portal_certs_count}. Profile count: #{profile_certs_count}") if certificate_count_different

return certificate_count_different
end
end
end

0 comments on commit 00639f2

Please sign in to comment.