fastlane 2.109.1 → 2.110.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +66 -66
  3. data/deliver/lib/deliver/.options.rb.swp +0 -0
  4. data/fastlane/lib/fastlane/actions/download.rb +1 -0
  5. data/fastlane/lib/fastlane/actions/install_on_device.rb +2 -2
  6. data/fastlane/lib/fastlane/actions/resign.rb +5 -17
  7. data/fastlane/lib/fastlane/helper/adb_helper.rb +1 -1
  8. data/fastlane/lib/fastlane/swift_fastlane_api_generator.rb +3 -3
  9. data/fastlane/lib/fastlane/version.rb +1 -1
  10. data/fastlane/swift/Deliverfile.swift +1 -1
  11. data/fastlane/swift/Fastlane.swift +73 -65
  12. data/fastlane/swift/Gymfile.swift +1 -1
  13. data/fastlane/swift/Matchfile.swift +1 -1
  14. data/fastlane/swift/MatchfileProtocol.swift +5 -1
  15. data/fastlane/swift/Precheckfile.swift +1 -1
  16. data/fastlane/swift/Scanfile.swift +1 -1
  17. data/fastlane/swift/ScanfileProtocol.swift +29 -29
  18. data/fastlane/swift/Screengrabfile.swift +1 -1
  19. data/fastlane/swift/Snapshotfile.swift +1 -1
  20. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +18 -16
  21. data/frameit/lib/frameit/options.rb +1 -1
  22. data/match/lib/match.rb +1 -0
  23. data/match/lib/match/change_password.rb +7 -0
  24. data/match/lib/match/commands_generator.rb +15 -2
  25. data/match/lib/match/encryption.rb +3 -0
  26. data/match/lib/match/encryption/openssl.rb +2 -2
  27. data/match/lib/match/migrate.rb +97 -0
  28. data/match/lib/match/module.rb +1 -1
  29. data/match/lib/match/nuke.rb +22 -6
  30. data/match/lib/match/options.rb +19 -1
  31. data/match/lib/match/runner.rb +58 -15
  32. data/match/lib/match/spaceship_ensure.rb +6 -1
  33. data/match/lib/match/storage.rb +4 -0
  34. data/match/lib/match/storage/git_storage.rb +1 -10
  35. data/match/lib/match/storage/google_cloud_storage.rb +227 -0
  36. data/match/lib/match/storage/interface.rb +9 -3
  37. data/scan/lib/scan/options.rb +105 -86
  38. data/spaceship/README.md +20 -42
  39. metadata +38 -17
  40. data/fastlane_core/lib/fastlane_core/configuration/.config_item.rb.swp +0 -0
  41. data/pilot/lib/pilot/.options.rb.swp +0 -0
@@ -11,7 +11,7 @@ module Match
11
11
  end
12
12
 
13
13
  def self.storage_modes
14
- return %w(git)
14
+ return %w(git google_cloud)
15
15
  end
16
16
 
17
17
  def self.profile_type_sym(type)
@@ -25,6 +25,8 @@ module Match
25
25
  self.params = params
26
26
  self.type = type
27
27
 
28
+ update_optional_values_depending_on_storage_type(params)
29
+
28
30
  self.storage = Storage.for_mode(params[:storage_mode], {
29
31
  git_url: params[:git_url],
30
32
  shallow_clone: params[:shallow_clone],
@@ -32,7 +34,9 @@ module Match
32
34
  git_branch: params[:git_branch],
33
35
  git_full_name: params[:git_full_name],
34
36
  git_user_email: params[:git_user_email],
35
- clone_branch_directly: params[:clone_branch_directly]
37
+ clone_branch_directly: params[:clone_branch_directly],
38
+ google_cloud_bucket_name: params[:google_cloud_bucket_name].to_s,
39
+ google_cloud_keys_file: params[:google_cloud_keys_file].to_s
36
40
  })
37
41
  self.storage.download
38
42
 
@@ -41,7 +45,7 @@ module Match
41
45
  git_url: params[:git_url],
42
46
  working_directory: storage.working_directory
43
47
  })
44
- self.encryption.decrypt_files
48
+ self.encryption.decrypt_files if self.encryption
45
49
 
46
50
  had_app_identifier = self.params.fetch(:app_identifier, ask: false)
47
51
  self.params[:app_identifier] = '' # we don't really need a value here
@@ -76,6 +80,14 @@ module Match
76
80
  end
77
81
  end
78
82
 
83
+ # Be smart about optional values here
84
+ # Depending on the storage mode, different values are required
85
+ def update_optional_values_depending_on_storage_type(params)
86
+ if params[:storage_mode] != "git"
87
+ params.option_for_key(:git_url).optional = true
88
+ end
89
+ end
90
+
79
91
  # Collect all the certs/profiles
80
92
  def prepare_list
81
93
  UI.message("Fetching certificates and profiles...")
@@ -189,14 +201,16 @@ module Match
189
201
  end
190
202
 
191
203
  if self.files.count > 0
192
- delete_files!
204
+ files_to_delete = delete_files!
193
205
  end
194
206
 
195
- self.encryption.encrypt_files
207
+ self.encryption.encrypt_files if self.encryption
196
208
 
197
209
  # Now we need to commit and push all this too
198
210
  message = ["[fastlane]", "Nuked", "files", "for", type.to_s].join(" ")
199
- self.storage.save_changes!(files_to_commit: [], custom_message: message)
211
+ self.storage.save_changes!(files_to_commit: [],
212
+ files_to_delete: files_to_delete,
213
+ custom_message: message)
200
214
  end
201
215
 
202
216
  private
@@ -204,7 +218,7 @@ module Match
204
218
  def delete_files!
205
219
  UI.header("Deleting #{self.files.count} files from the storage...")
206
220
 
207
- self.files.each do |file|
221
+ return self.files.collect do |file|
208
222
  UI.message("Deleting file '#{File.basename(file)}'...")
209
223
 
210
224
  # Check if the profile is installed on the local machine
@@ -217,6 +231,8 @@ module Match
217
231
 
218
232
  File.delete(file)
219
233
  UI.success("Successfully deleted file")
234
+
235
+ file
220
236
  end
221
237
  end
222
238
 
@@ -4,6 +4,12 @@ require_relative 'module'
4
4
 
5
5
  module Match
6
6
  class Options
7
+ # This is match specific, as users can append storage specific options
8
+ def self.append_option(option)
9
+ self.available_options # to ensure we created the initial `@available_options` array
10
+ @available_options << option
11
+ end
12
+
7
13
  def self.available_options
8
14
  user = CredentialsManager::AppfileConfig.try_fetch_value(:apple_dev_portal_id)
9
15
  user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
@@ -151,7 +157,19 @@ module Match
151
157
  env_name: "MATCH_PROVISIONING_PROFILE_TEMPLATE_NAME",
152
158
  description: "The name of provisioning profile template. If the developer account has provisioning profile templates (aka: custom entitlements), the template name can be found by inspecting the Entitlements drop-down while creating/editing a provisioning profile (e.g. \"Apple Pay Pass Suppression Development\")",
153
159
  optional: true,
154
- default_value: nil)
160
+ default_value: nil),
161
+ FastlaneCore::ConfigItem.new(key: :google_cloud_bucket_name,
162
+ env_name: "MATCH_GOOGLE_CLOUD_BUCKET_NAME",
163
+ description: "Name of the Google Cloud Storage bucket to use",
164
+ optional: true),
165
+ FastlaneCore::ConfigItem.new(key: :google_cloud_keys_file,
166
+ env_name: "MATCH_GOOGLE_CLOUD_KEYS_FILE",
167
+ description: "Path to the gc_keys.json file",
168
+ optional: true,
169
+ verify_block: proc do |value|
170
+ UI.user_error!("Could not find keys file at path '#{File.expand_path(value)}'") unless File.exist?(value)
171
+ end)
172
+
155
173
  ]
156
174
  end
157
175
  end
@@ -16,12 +16,22 @@ module Match
16
16
  attr_accessor :files_to_commit
17
17
  attr_accessor :spaceship
18
18
 
19
+ attr_accessor :storage_mode
20
+
21
+ # The Team ID that was fetched
22
+ # This will always be `nil` in readonly mode
23
+ attr_accessor :currently_used_team_id
24
+
19
25
  def run(params)
20
26
  self.files_to_commit = []
21
27
 
22
28
  FastlaneCore::PrintTable.print_values(config: params,
23
29
  title: "Summary for match #{Fastlane::VERSION}")
24
30
 
31
+ update_optional_values_depending_on_storage_type(params)
32
+
33
+ self.storage_mode = params[:storage_mode]
34
+
25
35
  # Choose the right storage and encryption implementations
26
36
  storage = Storage.for_mode(params[:storage_mode], {
27
37
  git_url: params[:git_url],
@@ -32,7 +42,9 @@ module Match
32
42
  git_user_email: params[:git_user_email],
33
43
  clone_branch_directly: params[:clone_branch_directly],
34
44
  type: params[:type].to_s,
35
- platform: params[:platform].to_s
45
+ platform: params[:platform].to_s,
46
+ google_cloud_bucket_name: params[:google_cloud_bucket_name].to_s,
47
+ google_cloud_keys_file: params[:google_cloud_keys_file].to_s
36
48
  })
37
49
  storage.download
38
50
 
@@ -41,10 +53,16 @@ module Match
41
53
  git_url: params[:git_url],
42
54
  working_directory: storage.working_directory
43
55
  })
44
- encryption.decrypt_files
56
+ encryption.decrypt_files if encryption
45
57
 
46
- unless params[:readonly]
58
+ if params[:readonly]
59
+ # In readonly mode, we still want to see if the user provided a team_id
60
+ # see `prefixed_working_directory` comments for more details
61
+ self.currently_used_team_id = params[:team_id]
62
+ else
47
63
  self.spaceship = SpaceshipEnsure.new(params[:username], params[:team_id], params[:team_name])
64
+ self.currently_used_team_id = self.spaceship.team_id
65
+
48
66
  if params[:type] == "enterprise" && !Spaceship.client.in_house?
49
67
  UI.user_error!("You defined the profile type 'enterprise', but your Apple account doesn't support In-House profiles")
50
68
  end
@@ -81,13 +99,8 @@ module Match
81
99
  end
82
100
  end
83
101
 
84
- # Done
85
102
  if self.files_to_commit.count > 0 && !params[:readonly]
86
- Dir.chdir(storage.working_directory) do
87
- return if `git status`.include?("nothing to commit")
88
- end
89
-
90
- encryption.encrypt_files
103
+ encryption.encrypt_files if encryption
91
104
  storage.save_changes!(files_to_commit: self.files_to_commit)
92
105
  end
93
106
 
@@ -107,16 +120,46 @@ module Match
107
120
  storage.clear_changes if storage
108
121
  end
109
122
 
123
+ # Used when creating a new certificate or profile
124
+ def prefixed_working_directory(working_directory)
125
+ if self.storage_mode == "git"
126
+ return working_directory
127
+ elsif self.storage_mode == "google_cloud"
128
+ # We fall back to "*", which means certificates and profiles
129
+ # from all teams that use this bucket would be installed. This is not ideal, but
130
+ # unless the user provides a `team_id`, we can't know which one to use
131
+ # This only happens if `readonly` is activated, and no `team_id` was provided
132
+ @_folder_prefix ||= self.currently_used_team_id
133
+ if @_folder_prefix.nil?
134
+ # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
135
+ # method, as the value won't change. This way the warning is only printed once
136
+ 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")
137
+ @_folder_prefix = "*"
138
+ end
139
+ return File.join(working_directory, @_folder_prefix)
140
+ else
141
+ UI.crash!("No implementation for `prefixed_working_directory`")
142
+ end
143
+ end
144
+
145
+ # Be smart about optional values here
146
+ # Depending on the storage mode, different values are required
147
+ def update_optional_values_depending_on_storage_type(params)
148
+ if params[:storage_mode] != "git"
149
+ params.option_for_key(:git_url).optional = true
150
+ end
151
+ end
152
+
110
153
  def fetch_certificate(params: nil, working_directory: nil)
111
154
  cert_type = Match.cert_type_sym(params[:type])
112
155
 
113
- certs = Dir[File.join(working_directory, "certs", cert_type.to_s, "*.cer")]
114
- keys = Dir[File.join(working_directory, "certs", cert_type.to_s, "*.p12")]
156
+ certs = Dir[File.join(prefixed_working_directory(working_directory), "certs", cert_type.to_s, "*.cer")]
157
+ keys = Dir[File.join(prefixed_working_directory(working_directory), "certs", cert_type.to_s, "*.p12")]
115
158
 
116
159
  if certs.count == 0 || keys.count == 0
117
- UI.important("Couldn't find a valid code signing identity in the git repo for #{cert_type}... creating one for you now")
160
+ UI.important("Couldn't find a valid code signing identity for #{cert_type}... creating one for you now")
118
161
  UI.crash!("No code signing identity found and can not create a new one because you enabled `readonly`") if params[:readonly]
119
- cert_path = Generator.generate_certificate(params, cert_type, working_directory)
162
+ cert_path = Generator.generate_certificate(params, cert_type, prefixed_working_directory(working_directory))
120
163
  private_key_path = cert_path.gsub(".cer", ".p12")
121
164
 
122
165
  self.files_to_commit << cert_path
@@ -157,7 +200,7 @@ module Match
157
200
  end
158
201
 
159
202
  profile_name = names.join("_").gsub("*", '\*') # this is important, as it shouldn't be a wildcard
160
- base_dir = File.join(working_directory, "profiles", prov_type.to_s)
203
+ base_dir = File.join(prefixed_working_directory(working_directory), "profiles", prov_type.to_s)
161
204
  profiles = Dir[File.join(base_dir, "#{profile_name}.mobileprovision")]
162
205
  keychain_path = FastlaneCore::Helper.keychain_path(params[:keychain_name]) unless params[:keychain_name].nil?
163
206
 
@@ -189,7 +232,7 @@ module Match
189
232
  prov_type: prov_type,
190
233
  certificate_id: certificate_id,
191
234
  app_identifier: app_identifier,
192
- working_directory: working_directory)
235
+ working_directory: prefixed_working_directory(working_directory))
193
236
  self.files_to_commit << profile
194
237
  end
195
238
 
@@ -23,6 +23,11 @@ module Match
23
23
  Spaceship.select_team(team_id: team_id, team_name: team_name)
24
24
  end
25
25
 
26
+ # The team ID of the currently logged in team
27
+ def team_id
28
+ return Spaceship.client.team_id
29
+ end
30
+
26
31
  def bundle_identifier_exists(username: nil, app_identifier: nil)
27
32
  found = Spaceship.app.find(app_identifier)
28
33
  return if found
@@ -46,7 +51,7 @@ module Match
46
51
  end
47
52
  return if found
48
53
 
49
- UI.error("Certificate '#{certificate_id}' (stored in your git repo) is not available on the Developer Portal")
54
+ UI.error("Certificate '#{certificate_id}' (stored in your storage) is not available on the Developer Portal")
50
55
  UI.error("for the user #{username}")
51
56
  UI.error("Make sure to use the same user and team every time you run 'match' for this")
52
57
  UI.error("Git repository. This might be caused by revoking the certificate on the Dev Portal")
@@ -1,5 +1,6 @@
1
1
  require_relative 'storage/interface'
2
2
  require_relative 'storage/git_storage'
3
+ require_relative 'storage/google_cloud_storage'
3
4
 
4
5
  module Match
5
6
  module Storage
@@ -8,6 +9,9 @@ module Match
8
9
  @backends ||= {
9
10
  "git" => lambda { |params|
10
11
  return Storage::GitStorage.configure(params)
12
+ },
13
+ "google_cloud" => lambda { |params|
14
+ return Storage::GoogleCloudStorage.configure(params)
11
15
  }
12
16
  }
13
17
  end
@@ -55,7 +55,7 @@ module Match
55
55
 
56
56
  def download
57
57
  # Check if we already have a functional working_directory
58
- return self.working_directory if @working_directory
58
+ return if @working_directory
59
59
 
60
60
  # No existing working directory, creating a new one now
61
61
  self.working_directory = Dir.mktmpdir
@@ -110,13 +110,6 @@ module Match
110
110
  git_push(commands: commands, commit_message: custom_message)
111
111
  end
112
112
 
113
- def clear_changes
114
- return unless @working_directory
115
-
116
- FileUtils.rm_rf(self.working_directory)
117
- self.working_directory = nil
118
- end
119
-
120
113
  # Generate the commit message based on the user's parameters
121
114
  def generate_commit_message
122
115
  [
@@ -186,8 +179,6 @@ module Match
186
179
  end
187
180
  end
188
181
 
189
- private # rubocop:disable Lint/UselessAccessModifier
190
-
191
182
  def git_push(commands: [], commit_message: nil)
192
183
  commit_message ||= generate_commit_message
193
184
  commands << "git commit -m #{commit_message.shellescape}"
@@ -0,0 +1,227 @@
1
+ require 'fastlane_core/command_executor'
2
+ require 'fastlane_core/configuration/configuration'
3
+ require 'google/cloud/storage'
4
+
5
+ require_relative '../options'
6
+ require_relative '../module'
7
+ require_relative './interface'
8
+
9
+ module Match
10
+ module Storage
11
+ # Store the code signing identities in on Google Cloud Storage
12
+ class GoogleCloudStorage < Interface
13
+ DEFAULT_KEYS_FILE_NAME = "gc_keys.json"
14
+
15
+ # User provided values
16
+ attr_reader :type
17
+ attr_reader :platform
18
+ attr_reader :bucket_name
19
+ attr_reader :google_cloud_keys_file
20
+ attr_reader :project_id
21
+
22
+ # Managed values
23
+ attr_accessor :gc_storage
24
+
25
+ def self.configure(params)
26
+ if params[:git_url].to_s.length > 0
27
+ UI.important("Looks like you still define a `git_url` somewhere, even though")
28
+ UI.important("you use Google Cloud Storage. You can remove the `git_url`")
29
+ UI.important("from your Matchfile and Fastfile")
30
+ UI.message("The above is just a warning, fastlane will continue as usual now...")
31
+ end
32
+
33
+ return self.new(
34
+ type: params[:type].to_s,
35
+ platform: params[:platform].to_s,
36
+ google_cloud_bucket_name: params[:google_cloud_bucket_name],
37
+ google_cloud_keys_file: params[:google_cloud_keys_file]
38
+ )
39
+ end
40
+
41
+ def initialize(type: nil,
42
+ platform: nil,
43
+ google_cloud_bucket_name: nil,
44
+ google_cloud_keys_file: nil)
45
+ @type = type if type
46
+ @platform = platform if platform
47
+ @bucket_name = google_cloud_bucket_name
48
+
49
+ @google_cloud_keys_file = ensure_keys_file_exists(google_cloud_keys_file)
50
+
51
+ # Extract the Project ID from the `JSON` file
52
+ # so the user doesn't have to provide it manually
53
+ keys_file_content = JSON.parse(File.read(self.google_cloud_keys_file))
54
+ @project_id = keys_file_content["project_id"]
55
+ if self.project_id.to_s.length == 0
56
+ UI.user_error!("Provided keys file on path #{File.expand_path(self.google_cloud_keys_file)} doesn't include required value for `project_id`")
57
+ end
58
+
59
+ # Create the Google Cloud Storage client
60
+ # If the JSON auth file is invalid, this line will
61
+ # raise an exception
62
+ begin
63
+ self.gc_storage = Google::Cloud::Storage.new(
64
+ credentials: self.google_cloud_keys_file,
65
+ project_id: self.project_id
66
+ )
67
+ rescue => ex
68
+ UI.error(ex)
69
+ UI.verbose(ex.backtrace.join("\n"))
70
+ UI.user_error!("Couldn't log into your Google Cloud account using the provided JSON file at path '#{File.expand_path(self.google_cloud_keys_file)}'")
71
+ end
72
+
73
+ ensure_bucket_is_selected
74
+ end
75
+
76
+ def download
77
+ # Check if we already have a functional working_directory
78
+ return if @working_directory
79
+
80
+ # No existing working directory, creating a new one now
81
+ self.working_directory = Dir.mktmpdir
82
+
83
+ bucket.files.each do |current_file|
84
+ file_path = current_file.name # e.g. "N8X438SEU2/certs/distribution/XD9G7QCACF.cer"
85
+ download_path = File.join(self.working_directory, file_path)
86
+
87
+ FileUtils.mkdir_p(File.expand_path("..", download_path))
88
+ UI.verbose("Downloading file from Google Cloud Storage '#{file_path}' on bucket #{self.bucket_name}")
89
+ current_file.download(download_path)
90
+ end
91
+ UI.verbose("Successfully downloaded files from GCS to #{self.working_directory}")
92
+ end
93
+
94
+ def delete_files(files_to_delete: [], custom_message: nil)
95
+ files_to_delete.each do |current_file|
96
+ target_path = current_file.gsub(self.working_directory + "/", "")
97
+ file = bucket.file(target_path)
98
+ UI.message("Deleting '#{target_path}' from Google Cloud Storage bucket '#{self.bucket_name}'...")
99
+ file.delete
100
+ end
101
+ finished_pushing_message
102
+ end
103
+
104
+ def upload_files(files_to_upload: [], custom_message: nil)
105
+ # `files_to_upload` is an array of files that need to be uploaded to Google Cloud
106
+ # Those doesn't mean they're new, it might just be they're changed
107
+ # Either way, we'll upload them using the same technique
108
+
109
+ files_to_upload.each do |current_file|
110
+ # Go from
111
+ # "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
112
+ # to
113
+ # "profiles/development/Development_me.mobileprovision"
114
+ #
115
+
116
+ # We also have to remove the trailing `/` as Google Cloud doesn't handle it nicely
117
+ target_path = current_file.gsub(self.working_directory + "/", "")
118
+ UI.verbose("Uploading '#{target_path}' to Google Cloud Storage...")
119
+ bucket.create_file(current_file, target_path)
120
+ end
121
+ finished_pushing_message
122
+ end
123
+
124
+ def skip_docs
125
+ false
126
+ end
127
+
128
+ private
129
+
130
+ def finished_pushing_message
131
+ UI.success("Finished applying changes up to Google Cloud Storage on bucket '#{self.bucket_name}'")
132
+ end
133
+
134
+ def bucket
135
+ @_bucket ||= self.gc_storage.bucket(self.bucket_name)
136
+
137
+ if @_bucket.nil?
138
+ UI.user_error!("Couldn't find Google Cloud Storage bucket with name #{self.bucket_name} for the currently used account. Please make sure you have access")
139
+ end
140
+
141
+ return @_bucket
142
+ end
143
+
144
+ ##########################
145
+ # Setup related methods
146
+ ##########################
147
+
148
+ # This method will make sure the keys file exists
149
+ # If it's missing, it will help the user set things up
150
+ def ensure_keys_file_exists(google_cloud_keys_file)
151
+ if google_cloud_keys_file && File.exist?(google_cloud_keys_file)
152
+ return google_cloud_keys_file
153
+ end
154
+
155
+ if File.exist?(DEFAULT_KEYS_FILE_NAME)
156
+ return DEFAULT_KEYS_FILE_NAME
157
+ end
158
+
159
+ # User doesn't seem to have provided a keys file
160
+ UI.message("Looks like you don't have a Google Cloud #{DEFAULT_KEYS_FILE_NAME.cyan} file yet")
161
+ UI.message("If you have one, make sure to put it into the '#{Dir.pwd}' directory and call it '#{DEFAULT_KEYS_FILE_NAME.cyan}'")
162
+ unless UI.confirm("Do you want fastlane to help you to create a #{DEFAULT_KEYS_FILE_NAME} file?")
163
+ UI.user_error!("Process stopped, run fastlane again to start things up again")
164
+ end
165
+
166
+ UI.message("fastlane will help you create a keys file. First, open the following website")
167
+ UI.message("")
168
+ UI.message("\t\thttps://console.cloud.google.com".cyan)
169
+ UI.message("")
170
+ UI.input("Press enter once you're logged in")
171
+
172
+ UI.message("Now it's time to generate a new JSON auth file for fastlane to access Google Cloud")
173
+ UI.message("First, switch to the Google Cloud project you want to use.")
174
+ UI.message("If you don't have one yet, create a new one and switch to it")
175
+ UI.message("")
176
+ UI.message("\t\thttps://console.cloud.google.com/apis/credentials".cyan)
177
+ UI.message("")
178
+ UI.input("Ensure the right project is selected on top of the page and confirm with enter")
179
+
180
+ UI.message("Now create a new JSON auth file by clicking on")
181
+ UI.message("")
182
+ UI.message("\t\t 1. Create credentials".cyan)
183
+ UI.message("\t\t 2. Service account key".cyan)
184
+ UI.message("\t\t 3. App Engine default service account".cyan)
185
+ UI.message("\t\t 4. JSON".cyan)
186
+ UI.message("\t\t 5. Create".cyan)
187
+ UI.message("")
188
+ UI.input("Confirm with enter once you created and download the JSON file")
189
+
190
+ UI.message("Copy the file to the current directory (#{Dir.pwd})")
191
+ UI.message("and rename it to `#{DEFAULT_KEYS_FILE_NAME.cyan}`")
192
+ UI.message("")
193
+ UI.input("Confirm with enter")
194
+
195
+ until File.exist?(DEFAULT_KEYS_FILE_NAME)
196
+ UI.message("Make sure to place the file in '#{Dir.pwd.cyan}' and name it '#{DEFAULT_KEYS_FILE_NAME.cyan}'")
197
+ UI.input("Confirm with enter")
198
+ end
199
+
200
+ return DEFAULT_KEYS_FILE_NAME
201
+ end
202
+
203
+ def ensure_bucket_is_selected
204
+ # In case the user didn't provide a bucket name yet, they will
205
+ # be asked to provide one here
206
+ while self.bucket_name.to_s.length == 0
207
+ # Have a nice selection of the available buckets here
208
+ # This can only happen after we went through auth of Google Cloud
209
+ available_bucket_identifiers = self.gc_storage.buckets.collect(&:id)
210
+ if available_bucket_identifiers.count > 0
211
+ @bucket_name = UI.select("What Google Cloud Storage bucket do you want to use?", available_bucket_identifiers)
212
+ else
213
+ UI.error("Looks like your Google Cloud account for the project ID '#{self.project_id}' doesn't")
214
+ UI.error("have any available storage buckets yet. Please visit the following URL")
215
+ UI.message("")
216
+ UI.message("\t\thttps://console.cloud.google.com/storage/browser".cyan)
217
+ UI.message("")
218
+ UI.message("and make sure to have the right project selected on top of the page")
219
+ UI.message("click on " + "Create Bucket".cyan + ", choose a name and confirm")
220
+ UI.message("")
221
+ UI.input("Once you're finished, please confirm with enter")
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end