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



679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 679

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?
    @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



659
660
661
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 659

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

.hide_transporter_output?Boolean

Returns:



663
664
665
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 663

def self.hide_transporter_output?
  @hide_transporter_output
end

Instance Method Details

#displayable_errorsObject



870
871
872
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 870

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



711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 711

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



874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 874

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 = nil
  api_key = api_key_with_p8_file_path(@api_key) if use_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



750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
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
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 750

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 = nil
  api_key = api_key_with_p8_file_path(@api_key) if use_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 any more, 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



819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
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
# File 'fastlane_core/lib/fastlane_core/itunes_transporter.rb', line 819

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 = api_key_with_p8_file_path(@api_key) if use_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 any more, since the upload was successful
  else
    handle_error(@password)
  end

  return result
end