fastlane 2.141.0 → 2.146.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +82 -82
  4. data/credentials_manager/lib/credentials_manager/appfile_config.rb +4 -0
  5. data/deliver/lib/deliver/app_screenshot.rb +1 -0
  6. data/deliver/lib/deliver/options.rb +30 -1
  7. data/deliver/lib/deliver/setup.rb +4 -4
  8. data/fastlane/lib/fastlane/actions/.update_code_signing_settings.rb.swp +0 -0
  9. data/fastlane/lib/fastlane/actions/README.md +2 -0
  10. data/fastlane/lib/fastlane/actions/app_store_build_number.rb +13 -5
  11. data/fastlane/lib/fastlane/actions/automatic_code_signing.rb +7 -1
  12. data/fastlane/lib/fastlane/actions/clean_build_artifacts.rb +3 -0
  13. data/fastlane/lib/fastlane/actions/cocoapods.rb +2 -2
  14. data/fastlane/lib/fastlane/actions/crashlytics.rb +14 -2
  15. data/fastlane/lib/fastlane/actions/create_pull_request.rb +7 -1
  16. data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +10 -4
  17. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +22 -6
  18. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +23 -7
  19. data/fastlane/lib/fastlane/actions/ensure_git_branch.rb +1 -1
  20. data/fastlane/lib/fastlane/actions/ensure_xcode_version.rb +35 -7
  21. data/fastlane/lib/fastlane/actions/frame_screenshots.rb +2 -1
  22. data/fastlane/lib/fastlane/actions/get_github_release.rb +3 -0
  23. data/fastlane/lib/fastlane/actions/get_version_number.rb +1 -1
  24. data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +9 -3
  25. data/fastlane/lib/fastlane/actions/notarize.rb +183 -0
  26. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +7 -1
  27. data/fastlane/lib/fastlane/actions/s3.rb +5 -291
  28. data/fastlane/lib/fastlane/actions/setup_ci.rb +1 -1
  29. data/fastlane/lib/fastlane/actions/setup_jenkins.rb +11 -2
  30. data/fastlane/lib/fastlane/actions/slather.rb +1 -1
  31. data/fastlane/lib/fastlane/actions/spm.rb +8 -0
  32. data/fastlane/lib/fastlane/actions/swiftlint.rb +45 -9
  33. data/fastlane/lib/fastlane/actions/update_code_signing_settings.rb +198 -0
  34. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +14 -4
  35. data/fastlane/lib/fastlane/actions/verify_build.rb +1 -1
  36. data/fastlane/lib/fastlane/helper/adb_helper.rb +1 -1
  37. data/fastlane/lib/fastlane/helper/s3_client_helper.rb +61 -0
  38. data/fastlane/lib/fastlane/plugins/plugin_manager.rb +1 -1
  39. data/fastlane/lib/fastlane/server/socket_server_action_command_executor.rb +1 -1
  40. data/fastlane/lib/fastlane/version.rb +1 -1
  41. data/fastlane/swift/Deliverfile.swift +1 -1
  42. data/fastlane/swift/Fastlane.swift +201 -13
  43. data/fastlane/swift/Gymfile.swift +1 -1
  44. data/fastlane/swift/Matchfile.swift +1 -1
  45. data/fastlane/swift/MatchfileProtocol.swift +17 -1
  46. data/fastlane/swift/Precheckfile.swift +1 -1
  47. data/fastlane/swift/RubyCommand.swift +1 -1
  48. data/fastlane/swift/Scanfile.swift +1 -1
  49. data/fastlane/swift/ScanfileProtocol.swift +9 -1
  50. data/fastlane/swift/Screengrabfile.swift +1 -1
  51. data/fastlane/swift/Snapshotfile.swift +1 -1
  52. data/fastlane_core/lib/fastlane_core/configuration/config_item.rb +9 -0
  53. data/fastlane_core/lib/fastlane_core/device_manager.rb +3 -3
  54. data/fastlane_core/lib/fastlane_core/ipa_file_analyser.rb +1 -0
  55. data/fastlane_core/lib/fastlane_core/keychain_importer.rb +2 -0
  56. data/fastlane_core/lib/fastlane_core/project.rb +4 -0
  57. data/fastlane_core/lib/fastlane_core/provisioning_profile.rb +15 -2
  58. data/frameit/lib/frameit/commands_generator.rb +25 -0
  59. data/frameit/lib/frameit/config_parser.rb +31 -9
  60. data/frameit/lib/frameit/device.rb +90 -0
  61. data/frameit/lib/frameit/device_types.rb +121 -5
  62. data/frameit/lib/frameit/editor.rb +29 -41
  63. data/frameit/lib/frameit/offsets.rb +8 -1
  64. data/frameit/lib/frameit/options.rb +81 -54
  65. data/frameit/lib/frameit/runner.rb +17 -7
  66. data/frameit/lib/frameit/screenshot.rb +39 -47
  67. data/frameit/lib/frameit/template_finder.rb +15 -12
  68. data/gym/lib/gym/generators/package_command_generator.rb +4 -0
  69. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +5 -0
  70. data/gym/lib/gym/runner.rb +16 -2
  71. data/match/lib/match/change_password.rb +1 -1
  72. data/match/lib/match/encryption.rb +4 -0
  73. data/match/lib/match/importer.rb +37 -20
  74. data/match/lib/match/module.rb +1 -1
  75. data/match/lib/match/nuke.rb +5 -1
  76. data/match/lib/match/options.rb +18 -0
  77. data/match/lib/match/runner.rb +4 -0
  78. data/match/lib/match/setup.rb +1 -1
  79. data/match/lib/match/storage.rb +4 -0
  80. data/match/lib/match/storage/google_cloud_storage.rb +2 -2
  81. data/match/lib/match/storage/s3_storage.rb +167 -0
  82. data/pilot/lib/pilot/build_manager.rb +24 -7
  83. data/pilot/lib/pilot/options.rb +8 -0
  84. data/produce/lib/produce/developer_center.rb +11 -2
  85. data/produce/lib/produce/itunes_connect.rb +11 -3
  86. data/produce/lib/produce/options.rb +12 -0
  87. data/scan/lib/scan/options.rb +10 -0
  88. data/scan/lib/scan/runner.rb +9 -7
  89. data/scan/lib/scan/test_command_generator.rb +11 -4
  90. data/screengrab/lib/screengrab/runner.rb +32 -19
  91. data/snapshot/lib/snapshot/fixes/simulator_shared_pasteboard.rb +16 -0
  92. data/snapshot/lib/snapshot/reports_generator.rb +4 -0
  93. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +2 -0
  94. data/spaceship/lib/spaceship/connect_api/models/app.rb +11 -0
  95. data/spaceship/lib/spaceship/connect_api/models/build.rb +1 -2
  96. data/spaceship/lib/spaceship/connect_api/models/certificate.rb +2 -0
  97. data/spaceship/lib/spaceship/connect_api/testflight/testflight.rb +23 -0
  98. data/spaceship/lib/spaceship/portal/app_service.rb +2 -2
  99. data/spaceship/lib/spaceship/portal/portal_client.rb +13 -0
  100. data/spaceship/lib/spaceship/tunes/app_version.rb +6 -1
  101. data/spaceship/lib/spaceship/tunes/application.rb +2 -1
  102. data/spaceship/lib/spaceship/tunes/tunes_client.rb +2 -2
  103. data/spaceship/lib/spaceship/two_step_or_factor_client.rb +52 -16
  104. data/supply/lib/supply/client.rb +4 -4
  105. data/supply/lib/supply/setup.rb +5 -3
  106. metadata +37 -16
@@ -1,5 +1,4 @@
1
1
  require 'deliver/app_screenshot'
2
-
3
2
  require_relative 'module'
4
3
  require_relative 'device_types'
5
4
  require_relative 'frame_downloader'
@@ -11,32 +10,36 @@ module Frameit
11
10
  def self.get_template(screenshot)
12
11
  return nil if screenshot.mac?
13
12
 
14
- filename = "Apple #{screenshot.device_name} #{screenshot.color}"
15
-
13
+ filename = create_file_name(screenshot.device_name, screenshot.color.nil? ? screenshot.default_color : screenshot.color)
16
14
  templates = Dir["#{FrameDownloader.templates_path}/#{filename}.{png,jpg}"] # ~/.frameit folder
17
15
 
18
16
  UI.verbose("Looking for #{filename} and found #{templates.count} template(s)")
19
17
 
18
+ return filename if Helper.test?
19
+ if templates.count == 0 && !screenshot.color.nil? && screenshot.color != screenshot.default_color
20
+ filename = create_file_name(screenshot.device_name, screenshot.default_color)
21
+ UI.important("Unfortunately device type '#{screenshot.device_name}' is not available in #{screenshot.color}, falling back to " + (screenshot.default_color.nil? ? "default" : screenshot.default_color) + "...")
22
+ templates = Dir["#{FrameDownloader.templates_path}/#{filename}.{png,jpg}"] # ~/.frameit folder
23
+ UI.verbose("Looking for #{filename} and found #{templates.count} template(s)")
24
+ end
25
+
20
26
  if templates.count == 0
21
- if screenshot.screen_size == Deliver::AppScreenshot::ScreenSize::IOS_35
27
+ if screenshot.deliver_screen_id == Deliver::AppScreenshot::ScreenSize::IOS_35
22
28
  UI.important("Unfortunately 3.5\" device frames were discontinued. Skipping screen '#{screenshot.path}'")
23
29
  UI.error("Looked for: '#{filename}.png'")
24
- elsif screenshot.color == Frameit::Color::ROSE_GOLD || screenshot.color == Frameit::Color::GOLD
25
- # Unfortunately not every device type is available in rose gold or gold
26
- # This is why we can't have nice things #yatusabes
27
- # fallback to a white iPhone, which looks similar
28
- UI.important("Unfortunately device type '#{screenshot.device_name}' is not available in #{screenshot.color}, falling back to silver...")
29
- screenshot.color = Frameit::Color::SILVER
30
- return self.get_template(screenshot)
31
30
  else
32
31
  UI.error("Couldn't find template for screenshot type '#{filename}'")
33
32
  UI.error("Please run `fastlane frameit download_frames` to download the latest frames")
34
33
  end
35
- return filename if Helper.test?
36
34
  return nil
37
35
  else
38
36
  return templates.first.tr(" ", "\ ")
39
37
  end
40
38
  end
39
+
40
+ def self.create_file_name(device_name, color)
41
+ return "#{device_name} #{color}" unless color.nil?
42
+ return device_name
43
+ end
41
44
  end
42
45
  end
@@ -49,6 +49,10 @@ module Gym
49
49
  generator.apps_path
50
50
  end
51
51
 
52
+ def asset_packs_path
53
+ generator.asset_packs_path
54
+ end
55
+
52
56
  # The generator we need to use for the currently used Xcode version
53
57
  # Since we dropped Xcode 6 support, it's just this class, but maybe we'll have
54
58
  # new classes in the future
@@ -136,6 +136,11 @@ module Gym
136
136
  Gym.cache[:apps_path] ||= File.join(temporary_output_path, "Apps")
137
137
  end
138
138
 
139
+ # The path to the Apps folder
140
+ def asset_packs_path
141
+ Gym.cache[:asset_packs_path] ||= File.join(temporary_output_path, "OnDemandResources")
142
+ end
143
+
139
144
  private
140
145
 
141
146
  def normalize_export_options(hash)
@@ -25,11 +25,11 @@ module Gym
25
25
 
26
26
  # Determine platform to archive
27
27
  is_mac = Gym.project.mac? || Gym.building_mac_catalyst_for_mac?
28
- is_ios = !is_mac && (Gym.project.ios? || Gym.project.tvos?)
28
+ is_ios = !is_mac && (Gym.project.ios? || Gym.project.tvos? || Gym.project.watchos?)
29
29
 
30
30
  # Archive
31
31
  if is_ios
32
- fix_generic_archive # See https://github.com/fastlane/fastlane/pull/4325
32
+ fix_generic_archive unless Gym.project.watchos? # See https://github.com/fastlane/fastlane/pull/4325
33
33
  return BuildCommandGenerator.archive_path if Gym.config[:skip_package_ipa]
34
34
 
35
35
  package_app
@@ -39,6 +39,7 @@ module Gym
39
39
  move_app_thinning
40
40
  move_app_thinning_size_report
41
41
  move_apps_folder
42
+ move_asset_packs
42
43
  elsif is_mac
43
44
  path = File.expand_path(Gym.config[:output_directory])
44
45
  compress_and_move_dsym
@@ -325,6 +326,19 @@ module Gym
325
326
  end
326
327
  end
327
328
 
329
+ # Move Asset Packs folder to the output directory
330
+ # @return (String) The path to the resulting Asset Packs (aka OnDemandResources) folder
331
+ def move_asset_packs
332
+ if Dir.exist?(PackageCommandGenerator.asset_packs_path)
333
+ FileUtils.mv(PackageCommandGenerator.asset_packs_path, File.expand_path(Gym.config[:output_directory]), force: true)
334
+ asset_packs_path = File.join(File.expand_path(Gym.config[:output_directory]), File.basename(PackageCommandGenerator.asset_packs_path))
335
+
336
+ UI.success("Successfully exported Asset Pack folder:")
337
+ UI.message(asset_packs_path)
338
+ asset_packs_path
339
+ end
340
+ end
341
+
328
342
  def find_archive_path
329
343
  Dir.glob(File.join(BuildCommandGenerator.build_path, "*.ipa")).last
330
344
  end
@@ -45,7 +45,7 @@ module Match
45
45
  end
46
46
 
47
47
  # This method is called from both here, and from `openssl.rb`
48
- def self.ask_password(message: "Passphrase for Git Repo: ", confirm: nil)
48
+ def self.ask_password(message: "Passphrase for Match storage: ", confirm: nil)
49
49
  ensure_ui_interactive
50
50
  loop do
51
51
  password = UI.password(message)
@@ -14,6 +14,10 @@ module Match
14
14
  },
15
15
  "google_cloud" => lambda { |params|
16
16
  return nil
17
+ },
18
+ "s3" => lambda { |params|
19
+ params[:keychain_name] = params[:s3_bucket]
20
+ return Encryption::OpenSSL.configure(params)
17
21
  }
18
22
  }
19
23
  end
@@ -2,23 +2,16 @@ require_relative 'spaceship_ensure'
2
2
  require_relative 'encryption'
3
3
  require_relative 'storage'
4
4
  require_relative 'module'
5
+ require 'fastlane_core/provisioning_profile'
5
6
  require 'fileutils'
6
7
 
7
8
  module Match
8
9
  class Importer
9
- def import_cert(params, cert_path: nil, p12_path: nil)
10
- # Get and verify cert and p12 path
11
- cert_path ||= UI.input("Certificate (.cer) path:")
12
- p12_path ||= UI.input("Private key (.p12) path:")
13
-
14
- cert_path = File.absolute_path(cert_path)
15
- p12_path = File.absolute_path(p12_path)
16
-
17
- UI.user_error!("Certificate does not exist at path: #{cert_path}") unless File.exist?(cert_path)
18
- UI.user_error!("Private key does not exist at path: #{p12_path}") unless File.exist?(p12_path)
19
-
20
- # Base64 encode contents to find match from API to find a cert ID
21
- cert_contents_base_64 = Base64.strict_encode64(File.open(cert_path).read)
10
+ def import_cert(params, cert_path: nil, p12_path: nil, profile_path: nil)
11
+ # Get and verify cert, p12 and profiles path
12
+ cert_path = ensure_valid_file_path(cert_path, "Certificate", ".cer")
13
+ p12_path = ensure_valid_file_path(p12_path, "Private key", ".p12")
14
+ profile_path = ensure_valid_file_path(profile_path, "Provisioning profile", ".mobileprovision or .provisionprofile", optional: true)
22
15
 
23
16
  # Storage
24
17
  storage = Storage.for_mode(params[:storage_mode], {
@@ -54,20 +47,25 @@ module Match
54
47
 
55
48
  case cert_type
56
49
  when :development
57
- certificate_type = Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DEVELOPMENT
50
+ certificate_type = Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DEVELOPMENT + "," + Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPMENT
58
51
  when :distribution, :enterprise
59
- certificate_type = Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DISTRIBUTION
52
+ certificate_type = Spaceship::ConnectAPI::Certificate::CertificateType::IOS_DISTRIBUTION + "," + Spaceship::ConnectAPI::Certificate::CertificateType::DISTRIBUTION
53
+ when :developer_id_application
54
+ certificate_type = Spaceship::ConnectAPI::Certificate::CertificateType::DEVELOPER_ID_APPLICATION
60
55
  else
61
56
  UI.user_error!("Cert type '#{cert_type}' is not supported")
62
57
  end
63
58
 
64
- output_dir = File.join(storage.prefixed_working_directory, "certs", cert_type.to_s)
59
+ output_dir_certs = File.join(storage.prefixed_working_directory, "certs", cert_type.to_s)
60
+ output_dir_profiles = File.join(storage.prefixed_working_directory, "profiles", cert_type.to_s)
65
61
 
66
62
  # Need to get the cert id by comparing base64 encoded cert content with certificate content from the API responses
67
63
  Spaceship::Portal.login(params[:username])
68
64
  Spaceship::Portal.select_team(team_id: params[:team_id], team_name: params[:team_name])
69
65
  certs = Spaceship::ConnectAPI::Certificate.all(filter: { certificateType: certificate_type })
70
66
 
67
+ # Base64 encode contents to find match from API to find a cert ID
68
+ cert_contents_base_64 = Base64.strict_encode64(File.binread(cert_path))
71
69
  matching_cert = certs.find do |cert|
72
70
  cert.certificate_content == cert_contents_base_64
73
71
  end
@@ -75,18 +73,37 @@ module Match
75
73
  UI.user_error!("This certificate cannot be imported - the certificate contents did not match with any available on the Developer Portal") if matching_cert.nil?
76
74
 
77
75
  # Make dir if doesn't exist
78
- FileUtils.mkdir_p(output_dir)
79
- dest_cert_path = File.join(output_dir, "#{matching_cert.id}.cer")
80
- dest_p12_path = File.join(output_dir, "#{matching_cert.id}.p12")
76
+ FileUtils.mkdir_p(output_dir_certs)
77
+ dest_cert_path = File.join(output_dir_certs, "#{matching_cert.id}.cer")
78
+ dest_p12_path = File.join(output_dir_certs, "#{matching_cert.id}.p12")
79
+
80
+ files_to_commit = [dest_cert_path, dest_p12_path]
81
81
 
82
82
  # Copy files
83
83
  IO.copy_stream(cert_path, dest_cert_path)
84
84
  IO.copy_stream(p12_path, dest_p12_path)
85
- files_to_commit = [dest_cert_path, dest_p12_path]
85
+ unless profile_path.nil?
86
+ FileUtils.mkdir_p(output_dir_profiles)
87
+ bundle_id = FastlaneCore::ProvisioningProfile.bundle_id(profile_path)
88
+ profile_extension = FastlaneCore::ProvisioningProfile.profile_extension(profile_path)
89
+ dest_profile_path = File.join(output_dir_profiles, "#{cert_type.to_s.capitalize}_#{bundle_id}#{profile_extension}")
90
+ files_to_commit.push(dest_profile_path)
91
+ IO.copy_stream(profile_path, dest_profile_path)
92
+ end
86
93
 
87
94
  # Encrypt and commit
88
95
  encryption.encrypt_files if encryption
89
96
  storage.save_changes!(files_to_commit: files_to_commit)
90
97
  end
98
+
99
+ def ensure_valid_file_path(file_path, file_description, file_extension, optional: false)
100
+ optional_file_message = optional ? " or leave empty to skip this file" : ""
101
+ file_path ||= UI.input("#{file_description} (#{file_extension}) path#{optional_file_message}:")
102
+
103
+ file_path = File.absolute_path(file_path) unless file_path == ""
104
+ file_path = File.exist?(file_path) ? file_path : nil
105
+ UI.user_error!("#{file_description} does not exist at path: #{file_path}") unless !file_path.nil? || optional
106
+ file_path
107
+ end
91
108
  end
92
109
  end
@@ -13,7 +13,7 @@ module Match
13
13
  end
14
14
 
15
15
  def self.storage_modes
16
- return %w(git google_cloud)
16
+ return %w(git google_cloud s3)
17
17
  end
18
18
 
19
19
  def self.profile_type_sym(type)
@@ -37,7 +37,11 @@ module Match
37
37
  clone_branch_directly: params[:clone_branch_directly],
38
38
  google_cloud_bucket_name: params[:google_cloud_bucket_name].to_s,
39
39
  google_cloud_keys_file: params[:google_cloud_keys_file].to_s,
40
- google_cloud_project_id: params[:google_cloud_project_id].to_s
40
+ google_cloud_project_id: params[:google_cloud_project_id].to_s,
41
+ s3_region: params[:s3_region].to_s,
42
+ s3_access_key: params[:s3_access_key].to_s,
43
+ s3_secret_access_key: params[:s3_secret_access_key].to_s,
44
+ s3_bucket: params[:s3_bucket].to_s
41
45
  })
42
46
  self.storage.download
43
47
 
@@ -159,6 +159,24 @@ module Match
159
159
  description: "ID of the Google Cloud project to use for authentication",
160
160
  optional: true),
161
161
 
162
+ # Storage: S3
163
+ FastlaneCore::ConfigItem.new(key: :s3_region,
164
+ env_name: "MATCH_S3_REGION",
165
+ description: "Name of the S3 region",
166
+ optional: true),
167
+ FastlaneCore::ConfigItem.new(key: :s3_access_key,
168
+ env_name: "MATCH_S3_ACCESS_KEY",
169
+ description: "S3 access key",
170
+ optional: true),
171
+ FastlaneCore::ConfigItem.new(key: :s3_secret_access_key,
172
+ env_name: "MATCH_S3_SECRET_ACCESS_KEY",
173
+ description: "S3 secret access key",
174
+ optional: true),
175
+ FastlaneCore::ConfigItem.new(key: :s3_bucket,
176
+ env_name: "MATCH_S3_BUCKET",
177
+ description: "Name of the S3 bucket",
178
+ optional: true),
179
+
162
180
  # Keychain
163
181
  FastlaneCore::ConfigItem.new(key: :keychain_name,
164
182
  short_option: "-s",
@@ -46,6 +46,10 @@ module Match
46
46
  google_cloud_bucket_name: params[:google_cloud_bucket_name].to_s,
47
47
  google_cloud_keys_file: params[:google_cloud_keys_file].to_s,
48
48
  google_cloud_project_id: params[:google_cloud_project_id].to_s,
49
+ s3_region: params[:s3_region].to_s,
50
+ s3_access_key: params[:s3_access_key].to_s,
51
+ s3_secret_access_key: params[:s3_secret_access_key].to_s,
52
+ s3_bucket: params[:s3_bucket].to_s,
49
53
  readonly: params[:readonly],
50
54
  username: params[:readonly] ? nil : params[:username], # only pass username if not readonly
51
55
  team_id: params[:team_id],
@@ -34,7 +34,7 @@ module Match
34
34
  end
35
35
 
36
36
  def storage_options
37
- return ["git", "google_cloud"]
37
+ return ["git", "google_cloud", "s3"]
38
38
  end
39
39
  end
40
40
  end
@@ -1,6 +1,7 @@
1
1
  require_relative 'storage/interface'
2
2
  require_relative 'storage/git_storage'
3
3
  require_relative 'storage/google_cloud_storage'
4
+ require_relative 'storage/s3_storage'
4
5
 
5
6
  module Match
6
7
  module Storage
@@ -12,6 +13,9 @@ module Match
12
13
  },
13
14
  "google_cloud" => lambda { |params|
14
15
  return Storage::GoogleCloudStorage.configure(params)
16
+ },
17
+ "s3" => lambda { |params|
18
+ return Storage::S3Storage.configure(params)
15
19
  }
16
20
  }
17
21
  end
@@ -181,7 +181,7 @@ module Match
181
181
  end
182
182
 
183
183
  def generate_matchfile_content
184
- return "bucket_name(\"#{self.bucket_name}\")"
184
+ return "google_cloud_bucket_name(\"#{self.bucket_name}\")"
185
185
  end
186
186
 
187
187
  private
@@ -349,7 +349,7 @@ module Match
349
349
  UI.message("\t\t 1. Click on your bucket to open it".cyan)
350
350
  UI.message("\t\t 2. Click 'Permissions'".cyan)
351
351
  UI.message("\t\t 3. Click 'Add members'".cyan)
352
- UI.message("\t\t 4. Enter the email of your service account".cyan)
352
+ UI.message("\t\t 4. Enter the service account email address".cyan)
353
353
  UI.message("\t\t 5. Set the role to 'Storage Admin'".cyan)
354
354
  UI.message("\t\t 6. Click 'Save'".cyan)
355
355
  UI.message("")
@@ -0,0 +1,167 @@
1
+ require 'fastlane_core/command_executor'
2
+ require 'fastlane_core/configuration/configuration'
3
+ require 'fastlane/helper/s3_client_helper'
4
+
5
+ require_relative '../options'
6
+ require_relative '../module'
7
+ require_relative '../spaceship_ensure'
8
+ require_relative './interface'
9
+
10
+ module Match
11
+ module Storage
12
+ # Store the code signing identities on AWS S3
13
+ class S3Storage < Interface
14
+ attr_reader :s3_bucket
15
+ attr_reader :s3_region
16
+ attr_reader :s3_client
17
+ attr_reader :readonly
18
+ attr_reader :username
19
+ attr_reader :team_id
20
+ attr_reader :team_name
21
+
22
+ def self.configure(params)
23
+ s3_region = params[:s3_region]
24
+ s3_access_key = params[:s3_access_key]
25
+ s3_secret_access_key = params[:s3_secret_access_key]
26
+ s3_bucket = params[:s3_bucket]
27
+
28
+ if params[:git_url].to_s.length > 0
29
+ UI.important("Looks like you still define a `git_url` somewhere, even though")
30
+ UI.important("you use S3 Storage. You can remove the `git_url`")
31
+ UI.important("from your Matchfile and Fastfile")
32
+ UI.message("The above is just a warning, fastlane will continue as usual now...")
33
+ end
34
+
35
+ return self.new(
36
+ s3_region: s3_region,
37
+ s3_access_key: s3_access_key,
38
+ s3_secret_access_key: s3_secret_access_key,
39
+ s3_bucket: s3_bucket,
40
+ readonly: params[:readonly],
41
+ username: params[:username],
42
+ team_id: params[:team_id],
43
+ team_name: params[:team_name]
44
+ )
45
+ end
46
+
47
+ def initialize(s3_region: nil,
48
+ s3_access_key: nil,
49
+ s3_secret_access_key: nil,
50
+ s3_bucket: nil,
51
+ readonly: nil,
52
+ username: nil,
53
+ team_id: nil,
54
+ team_name: nil)
55
+ @s3_bucket = s3_bucket
56
+ @s3_region = s3_region
57
+ @s3_client = Fastlane::Helper::S3ClientHelper.new(access_key: s3_access_key, secret_access_key: s3_secret_access_key, region: s3_region)
58
+ @readonly = readonly
59
+ @username = username
60
+ @team_id = team_id
61
+ @team_name = team_name
62
+ end
63
+
64
+ # To make debugging easier, we have a custom exception here
65
+ def prefixed_working_directory
66
+ # We fall back to "*", which means certificates and profiles
67
+ # from all teams that use this bucket would be installed. This is not ideal, but
68
+ # unless the user provides a `team_id`, we can't know which one to use
69
+ # This only happens if `readonly` is activated, and no `team_id` was provided
70
+ @_folder_prefix ||= currently_used_team_id
71
+ if @_folder_prefix.nil?
72
+ # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
73
+ # method, as the value won't change. This way the warning is only printed once
74
+ UI.important("Looks like you run `match` in `readonly` mode, and didn't provide a `team_id`. This will still work, however it is recommended to provide a `team_id` in your Appfile or Matchfile")
75
+ @_folder_prefix = "*"
76
+ end
77
+ return File.join(working_directory, @_folder_prefix)
78
+ end
79
+
80
+ # Call this method for the initial clone/download of the
81
+ # user's certificates & profiles
82
+ # As part of this method, the `self.working_directory` attribute
83
+ # will be set
84
+ def download
85
+ # Check if we already have a functional working_directory
86
+ return if @working_directory && Dir.exist?(@working_directory)
87
+
88
+ # No existing working directory, creating a new one now
89
+ self.working_directory = Dir.mktmpdir
90
+
91
+ s3_client.find_bucket!(s3_bucket).objects.each do |object|
92
+ file_path = object.key # :team_id/path/to/file
93
+ download_path = File.join(self.working_directory, file_path)
94
+
95
+ FileUtils.mkdir_p(File.expand_path("..", download_path))
96
+ UI.verbose("Downloading file from S3 '#{file_path}' on bucket #{self.s3_bucket}")
97
+
98
+ object.download_file(download_path)
99
+ end
100
+ UI.verbose("Successfully downloaded files from S3 to #{self.working_directory}")
101
+ end
102
+
103
+ # Returns a short string describing + identifing the current
104
+ # storage backend. This will be printed when nuking a storage
105
+ def human_readable_description
106
+ return "S3 Bucket [#{s3_bucket}] on region #{s3_region}"
107
+ end
108
+
109
+ def upload_files(files_to_upload: [], custom_message: nil)
110
+ # `files_to_upload` is an array of files that need to be uploaded to S3
111
+ # Those doesn't mean they're new, it might just be they're changed
112
+ # Either way, we'll upload them using the same technique
113
+
114
+ files_to_upload.each do |file_name|
115
+ # Go from
116
+ # "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
117
+ # to
118
+ # "profiles/development/Development_me.mobileprovision"
119
+ #
120
+
121
+ target_path = sanitize_file_name(file_name)
122
+ UI.verbose("Uploading '#{target_path}' to S3 Storage...")
123
+
124
+ body = File.read(file_name)
125
+ acl = 'private'
126
+ s3_url = s3_client.upload_file(s3_bucket, target_path, body, acl)
127
+ UI.verbose("Uploaded '#{s3_url}' to S3 Storage.")
128
+ end
129
+ end
130
+
131
+ def delete_files(files_to_delete: [], custom_message: nil)
132
+ files_to_delete.each do |file_name|
133
+ target_path = sanitize_file_name(file_name)
134
+ s3_client.delete_file(s3_bucket, target_path)
135
+ end
136
+ end
137
+
138
+ def skip_docs
139
+ false
140
+ end
141
+
142
+ # Implement this for the `fastlane match init` command
143
+ # This method must return the content of the Matchfile
144
+ # that should be generated
145
+ def generate_matchfile_content(template: nil)
146
+ return "s3_bucket(\"#{self.s3_bucket}\")"
147
+ end
148
+
149
+ private
150
+
151
+ def sanitize_file_name(file_name)
152
+ file_name.gsub(self.working_directory + "/", "")
153
+ end
154
+
155
+ def currently_used_team_id
156
+ if self.readonly
157
+ # In readonly mode, we still want to see if the user provided a team_id
158
+ # see `prefixed_working_directory` comments for more details
159
+ return self.team_id
160
+ else
161
+ spaceship = SpaceshipEnsure.new(self.username, self.team_id, self.team_name)
162
+ return spaceship.team_id
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end