Class: FastlaneCore::ItunesTransporter

Inherits:
Object
  • Object
show all
Defined in:
fastlane_core/lib/fastlane_core/itunes_transporter.rb

Constant Summary collapse

TWO_STEP_HOST_PREFIX =
"deliver.appspecific"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil, altool_compatible_command: false, api_key: nil) ⇒ ItunesTransporter

Returns a new instance of the iTunesTransporter. If no username or password given, it will be taken from the #CredentialsManager::AccountManager

Parameters:

  • use_shell_script (defaults to: false)

    if true, forces use of the iTMSTransporter shell script. if false, allows a direct call to the iTMSTransporter Java app (preferred). see: github.com/fastlane/fastlane/pull/4003

  • provider_short_name (defaults to: nil)

    The provider short name to be given to the iTMSTransporter to identify the correct team for this work. The provider short name is usually your Developer Portal team ID, but in certain cases it is different! see: github.com/fastlane/fastlane/issues/1524#issuecomment-196370628 for more information about how to use the iTMSTransporter to list your provider short names



704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 704

def initialize(user = nil, password = nil, use_shell_script = false, provider_short_name = nil, jwt = nil, altool_compatible_command: false, api_key: nil)
  # Xcode 6.x doesn't have the same iTMSTransporter Java setup as later Xcode versions, so
  # we can't default to using the newer direct Java invocation strategy for those versions.
  use_shell_script ||= Helper.is_mac? && Helper.xcode_version.start_with?('6.')
  use_shell_script ||= Helper.windows?
  use_shell_script ||= Feature.enabled?('FASTLANE_ITUNES_TRANSPORTER_USE_SHELL_SCRIPT')

  if jwt.to_s.empty? && api_key.nil?
    @user = user
    @password = password || load_password_for_transporter
  end

  @jwt = jwt
  @api_key = api_key

  if should_use_altool?(altool_compatible_command, use_shell_script)
    UI.verbose("Using altool as transporter.")
    @transporter_executor = AltoolTransporterExecutor.new
  else
    UI.verbose("Using iTMSTransporter as transporter.")
    @transporter_executor = use_shell_script ? ShellScriptTransporterExecutor.new : JavaTransporterExecutor.new
  end

  @provider_short_name = provider_short_name
end

Class Method Details

.hide_transporter_outputObject

This will be called from the Deliverfile, and disables the logging of the transporter output



684
685
686
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 684

def self.hide_transporter_output
  @hide_transporter_output = !FastlaneCore::Globals.verbose?
end

.hide_transporter_output?Boolean

Returns:



688
689
690
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 688

def self.hide_transporter_output?
  @hide_transporter_output
end

Instance Method Details

#displayable_errorsObject



892
893
894
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 892

def displayable_errors
  @transporter_executor.displayable_errors
end

#download(app_id, dir = nil) ⇒ Bool

Downloads the latest version of the app metadata package from iTC.

Parameters:

  • app_id (Integer)

    The unique App ID

  • dir (String) (defaults to: nil)

    the path in which the package file should be stored

Returns:

  • (Bool)

    True if everything worked fine

Raises:

  • (Deliver::TransporterTransferError)

    when something went wrong when transferring



736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 736

def download(app_id, dir = nil)
  dir ||= "/tmp"

  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'

  UI.message("Going to download app metadata from App Store Connect")
  command = @transporter_executor.build_download_command(@user, @password, app_id, dir, @provider_short_name, @jwt)
  UI.verbose(@transporter_executor.build_download_command(@user, password_placeholder, app_id, dir, @provider_short_name, jwt_placeholder))

  begin
    result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
    handle_two_step_failure(ex)
    return download(app_id, dir)
  end

  return result if Helper.test?

  itmsp_path = File.join(dir, "#{app_id}.itmsp")
  successful = result && File.directory?(itmsp_path)

  if successful
    UI.success("✅ Successfully downloaded the latest package from App Store Connect to #{itmsp_path}")
  else
    handle_error(@password)
  end

  successful
end

#provider_idsObject



896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 896

def provider_ids
  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'

  # Handle AppStore Connect API
  use_api_key = !@api_key.nil?
  api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil

  api_key = @transporter_executor.prepare(original_api_key: @api_key)

  command = @transporter_executor.build_provider_ids_command(@user, @password, @jwt, api_key)
  UI.verbose(@transporter_executor.build_provider_ids_command(@user, password_placeholder, jwt_placeholder, api_key_placeholder))

  lines = []
  begin
    result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?) { |xs| lines = xs }
    return result if Helper.test?
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
    handle_two_step_failure(ex)
    return provider_ids
  ensure
    if use_api_key
      FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
    end
  end

  @transporter_executor.parse_provider_info(lines)
end

#upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil, platform: nil) ⇒ Bool

Uploads the modified package back to App Store Connect

Parameters:

  • app_id (Integer) (defaults to: nil)

    The unique App ID

  • dir (String) (defaults to: nil)

    the path in which the package file is located

  • package_path (String) (defaults to: nil)

    the path to the package file (used instead of app_id and dir)

  • asset_path (String) (defaults to: nil)

    the path to the ipa/dmg/pkg file (used instead of package_path if running on macOS)

Returns:

  • (Bool)

    True if everything worked fine

Raises:

  • (Deliver::TransporterTransferError)

    when something went wrong when transferring



775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 775

def upload(app_id = nil, dir = nil, package_path: nil, asset_path: nil, platform: nil)
  raise "app_id and dir are required or package_path or asset_path is required" if (app_id.nil? || dir.nil?) && package_path.nil? && asset_path.nil?

  # Transport can upload .ipa, .dmg, and .pkg files directly with -assetFile
  # However, -assetFile requires -assetDescription if Linux or Windows
  # This will give the asset directly if macOS and asset_path exists
  # otherwise it will use the .itmsp package

  force_itmsp = FastlaneCore::Env.truthy?("ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD")
  can_use_asset_path = Helper.is_mac? && asset_path

  actual_dir = if can_use_asset_path && !force_itmsp
                 # The asset gets deleted upon completion so copying to a temp directory
                 # (with randomized filename, for multibyte-mixed filename upload fails)
                 new_file_name = "#{SecureRandom.uuid}#{File.extname(asset_path)}"
                 tmp_asset_path = File.join(Dir.tmpdir, new_file_name)
                 FileUtils.cp(asset_path, tmp_asset_path)
                 tmp_asset_path
               elsif package_path
                 package_path
               else
                 File.join(dir, "#{app_id}.itmsp")
               end

  UI.message("Going to upload updated app to App Store Connect")
  UI.success("This might take a few minutes. Please don't interrupt the script.")

  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'

  # Handle AppStore Connect API
  use_api_key = !@api_key.nil?
  api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
  api_key = @transporter_executor.prepare(original_api_key: @api_key)

  command = @transporter_executor.build_upload_command(@user, @password, actual_dir, @provider_short_name, @jwt, platform, api_key)
  UI.verbose(@transporter_executor.build_upload_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt_placeholder, platform, api_key_placeholder))

  begin
    result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
    handle_two_step_failure(ex)
    return upload(app_id, dir, package_path: package_path, asset_path: asset_path)
  ensure
    if use_api_key
      FileUtils.rm_rf(api_key[:key_dir]) unless api_key.nil?
    end
  end

  if result
    UI.header("Successfully uploaded package to App Store Connect. It might take a few minutes until it's visible online.")

    FileUtils.rm_rf(actual_dir) unless Helper.test? # we don't need the package anymore, since the upload was successful
  else
    handle_error(@password)
  end

  return result
end

#verify(app_id = nil, dir = nil, package_path: nil, asset_path: nil, platform: nil) ⇒ Bool

Verifies the given binary with App Store Connect

Parameters:

  • app_id (Integer) (defaults to: nil)

    The unique App ID

  • dir (String) (defaults to: nil)

    the path in which the package file is located

  • package_path (String) (defaults to: nil)

    the path to the package file (used instead of app_id and dir)

Returns:

  • (Bool)

    True if everything worked fine

Raises:

  • (Deliver::TransporterTransferError)

    when something went wrong when transferring



842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 842

def verify(app_id = nil, dir = nil, package_path: nil, asset_path: nil, platform: nil)
  raise "app_id and dir are required or package_path or asset_path is required" if (app_id.nil? || dir.nil?) && package_path.nil? && asset_path.nil?

  force_itmsp = FastlaneCore::Env.truthy?("ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD")
  can_use_asset_path = Helper.is_mac? && asset_path

  actual_dir = if can_use_asset_path && !force_itmsp
                 # The asset gets deleted upon completion so copying to a temp directory
                 # (with randomized filename, for multibyte-mixed filename upload fails)
                 new_file_name = "#{SecureRandom.uuid}#{File.extname(asset_path)}"
                 tmp_asset_path = File.join(Dir.tmpdir, new_file_name)
                 FileUtils.cp(asset_path, tmp_asset_path)
                 tmp_asset_path
               elsif package_path
                 package_path
               else
                 File.join(dir, "#{app_id}.itmsp")
               end

  password_placeholder = @jwt.nil? ? 'YourPassword' : nil
  jwt_placeholder = @jwt.nil? ? nil : 'YourJWT'

  # Handle AppStore Connect API
  use_api_key = !@api_key.nil?

  # Masking credentials for verbose outputs
  api_key_placeholder = use_api_key ? { key_id: "YourKeyID", issuer_id: "YourIssuerID", key_dir: "YourTmpP8KeyDir" } : nil
  api_key = @transporter_executor.prepare(original_api_key: @api_key)

  command = @transporter_executor.build_verify_command(@user, @password, actual_dir, @provider_short_name, jwt: @jwt, platform: platform, api_key: api_key)
  UI.verbose(@transporter_executor.build_verify_command(@user, password_placeholder, actual_dir, @provider_short_name, jwt: jwt_placeholder, platform: platform, api_key: api_key_placeholder))

  begin
    result = @transporter_executor.execute(command, ItunesTransporter.hide_transporter_output?)
  rescue TransporterRequiresApplicationSpecificPasswordError => ex
    handle_two_step_failure(ex)
    return verify(app_id, dir, package_path: package_path)
  end

  if result
    UI.header("Successfully verified package on App Store Connect")

    FileUtils.rm_rf(actual_dir) unless Helper.test? # we don't need the package anymore, since the upload was successful
  else
    handle_error(@password)
  end

  return result
end