Class: Match::Storage::GoogleCloudStorage

Inherits:
Interface
  • Object
show all
Defined in:
match/lib/match/storage/google_cloud_storage.rb

Overview

Store the code signing identities in on Google Cloud Storage

Constant Summary collapse

DEFAULT_KEYS_FILE_NAME =
"gc_keys.json"

Constants inherited from Interface

Interface::MATCH_VERSION_FILE_NAME

Instance Attribute Summary collapse

Attributes inherited from Interface

#working_directory

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Interface

#clear_changes, #configure, #save_changes!

Constructor Details

#initialize(type: nil, platform: nil, google_cloud_bucket_name: nil, google_cloud_keys_file: nil, google_cloud_project_id: nil, readonly: nil, username: nil, team_id: nil, team_name: nil, api_key_path: nil, api_key: nil) ⇒ GoogleCloudStorage

Returns a new instance of GoogleCloudStorage.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'match/lib/match/storage/google_cloud_storage.rb', line 55

def initialize(type: nil,
               platform: nil,
               google_cloud_bucket_name: nil,
               google_cloud_keys_file: nil,
               google_cloud_project_id: nil,
               readonly: nil,
               username: nil,
               team_id: nil,
               team_name: nil,
               api_key_path: nil,
               api_key: nil)
  @type = type if type
  @platform = platform if platform
  @google_cloud_project_id = google_cloud_project_id if google_cloud_project_id
  @bucket_name = google_cloud_bucket_name

  @readonly = readonly
  @username = username
  @team_id = team_id
  @team_name = team_name

  @api_key_path = api_key_path
  @api_key = api_key

  @google_cloud_keys_file = ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id)

  if self.google_cloud_keys_file.to_s.length > 0
    # Extract the Project ID from the `JSON` file
    # so the user doesn't have to provide it manually
    keys_file_content = JSON.parse(File.read(self.google_cloud_keys_file))
    if google_cloud_project_id.to_s.length > 0 && google_cloud_project_id != keys_file_content["project_id"]
      UI.important("The google_cloud_keys_file's project ID ('#{keys_file_content['project_id']}') doesn't match the google_cloud_project_id ('#{google_cloud_project_id}'). This may be the wrong keys file.")
    end
    @google_cloud_project_id = keys_file_content["project_id"]
    if self.google_cloud_project_id.to_s.length == 0
      UI.user_error!("Provided keys file on path #{File.expand_path(self.google_cloud_keys_file)} doesn't include required value for `project_id`")
    end
  end

  # Create the Google Cloud Storage client
  # If the JSON auth file is invalid, this line will
  # raise an exception
  begin
    self.gc_storage = Google::Cloud::Storage.new(
      credentials: self.google_cloud_keys_file,
      project_id: self.google_cloud_project_id
    )
  rescue => ex
    UI.error(ex)
    UI.verbose(ex.backtrace.join("\n"))
    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)}'")
  end

  ensure_bucket_is_selected
  check_bucket_permissions
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



27
28
29
# File 'match/lib/match/storage/google_cloud_storage.rb', line 27

def api_key
  @api_key
end

#api_key_pathObject (readonly)

Returns the value of attribute api_key_path.



26
27
28
# File 'match/lib/match/storage/google_cloud_storage.rb', line 26

def api_key_path
  @api_key_path
end

#bucket_nameObject (readonly)

Returns the value of attribute bucket_name.



19
20
21
# File 'match/lib/match/storage/google_cloud_storage.rb', line 19

def bucket_name
  @bucket_name
end

#gc_storageObject

Managed values



30
31
32
# File 'match/lib/match/storage/google_cloud_storage.rb', line 30

def gc_storage
  @gc_storage
end

#google_cloud_keys_fileObject (readonly)

Returns the value of attribute google_cloud_keys_file.



20
21
22
# File 'match/lib/match/storage/google_cloud_storage.rb', line 20

def google_cloud_keys_file
  @google_cloud_keys_file
end

#google_cloud_project_idObject (readonly)

Returns the value of attribute google_cloud_project_id.



21
22
23
# File 'match/lib/match/storage/google_cloud_storage.rb', line 21

def google_cloud_project_id
  @google_cloud_project_id
end

#platformObject (readonly)

Returns the value of attribute platform.



18
19
20
# File 'match/lib/match/storage/google_cloud_storage.rb', line 18

def platform
  @platform
end

#readonlyObject (readonly)

Returns the value of attribute readonly.



22
23
24
# File 'match/lib/match/storage/google_cloud_storage.rb', line 22

def readonly
  @readonly
end

#team_idObject (readonly)

Returns the value of attribute team_id.



24
25
26
# File 'match/lib/match/storage/google_cloud_storage.rb', line 24

def team_id
  @team_id
end

#team_nameObject (readonly)

Returns the value of attribute team_name.



25
26
27
# File 'match/lib/match/storage/google_cloud_storage.rb', line 25

def team_name
  @team_name
end

#typeObject (readonly)

User provided values



17
18
19
# File 'match/lib/match/storage/google_cloud_storage.rb', line 17

def type
  @type
end

#usernameObject (readonly)

Returns the value of attribute username.



23
24
25
# File 'match/lib/match/storage/google_cloud_storage.rb', line 23

def username
  @username
end

Class Method Details

.configure(params) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'match/lib/match/storage/google_cloud_storage.rb', line 32

def self.configure(params)
  if params[:git_url].to_s.length > 0
    UI.important("Looks like you still define a `git_url` somewhere, even though")
    UI.important("you use Google Cloud Storage. You can remove the `git_url`")
    UI.important("from your Matchfile and Fastfile")
    UI.message("The above is just a warning, fastlane will continue as usual now...")
  end

  return self.new(
    type: params[:type].to_s,
    platform: params[:platform].to_s,
    google_cloud_bucket_name: params[:google_cloud_bucket_name],
    google_cloud_keys_file: params[:google_cloud_keys_file],
    google_cloud_project_id: params[:google_cloud_project_id],
    readonly: params[:readonly],
    username: params[:username],
    team_id: params[:team_id],
    team_name: params[:team_name],
    api_key_path: params[:api_key_path],
    api_key: params[:api_key]
  )
end

Instance Method Details

#api_tokenObject



125
126
127
128
129
# File 'match/lib/match/storage/google_cloud_storage.rb', line 125

def api_token
  api_token ||= Spaceship::ConnectAPI::Token.create(self.api_key) if self.api_key
  api_token ||= Spaceship::ConnectAPI::Token.from_json_file(self.api_key_path) if self.api_key_path
  return api_token
end

#currently_used_team_idObject



112
113
114
115
116
117
118
119
120
121
122
123
# File 'match/lib/match/storage/google_cloud_storage.rb', line 112

def currently_used_team_id
  if self.readonly
    # In readonly mode, we still want to see if the user provided a team_id
    # see `prefixed_working_directory` comments for more details
    return self.team_id
  else
    UI.user_error!("The `team_id` option is required. fastlane cannot automatically determine portal team id via the App Store Connect API (yet)") if self.team_id.to_s.empty?

    spaceship = SpaceshipEnsure.new(self.username, self.team_id, self.team_name, self.api_token)
    return spaceship.team_id
  end
end

#delete_files(files_to_delete: [], custom_message: nil) ⇒ Object



164
165
166
167
168
169
170
171
# File 'match/lib/match/storage/google_cloud_storage.rb', line 164

def delete_files(files_to_delete: [], custom_message: nil)
  files_to_delete.each do |current_file|
    target_path = current_file.gsub(self.working_directory + "/", "")
    file = bucket.file(target_path)
    UI.message("Deleting '#{target_path}' from Google Cloud Storage bucket '#{self.bucket_name}'...")
    file.delete
  end
end

#downloadObject



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'match/lib/match/storage/google_cloud_storage.rb', line 146

def download
  # Check if we already have a functional working_directory
  return if @working_directory

  # No existing working directory, creating a new one now
  self.working_directory = Dir.mktmpdir

  bucket.files.each do |current_file|
    file_path = current_file.name # e.g. "N8X438SEU2/certs/distribution/XD9G7QCACF.cer"
    download_path = File.join(self.working_directory, file_path)

    FileUtils.mkdir_p(File.expand_path("..", download_path))
    UI.verbose("Downloading file from Google Cloud Storage '#{file_path}' on bucket #{self.bucket_name}")
    current_file.download(download_path)
  end
  UI.verbose("Successfully downloaded files from GCS to #{self.working_directory}")
end

#generate_matchfile_contentObject



204
205
206
# File 'match/lib/match/storage/google_cloud_storage.rb', line 204

def generate_matchfile_content
  return "google_cloud_bucket_name(\"#{self.bucket_name}\")"
end

#human_readable_descriptionObject



173
174
175
# File 'match/lib/match/storage/google_cloud_storage.rb', line 173

def human_readable_description
  "Google Cloud Bucket [#{self.google_cloud_project_id}/#{self.bucket_name}]"
end

#list_files(file_name: "", file_ext: "") ⇒ Object



200
201
202
# File 'match/lib/match/storage/google_cloud_storage.rb', line 200

def list_files(file_name: "", file_ext: "")
  Dir[File.join(working_directory, self.team_id, "**", file_name, "*.#{file_ext}")]
end

#prefixed_working_directoryObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'match/lib/match/storage/google_cloud_storage.rb', line 131

def prefixed_working_directory
  # We fall back to "*", which means certificates and profiles
  # from all teams that use this bucket would be installed. This is not ideal, but
  # unless the user provides a `team_id`, we can't know which one to use
  # This only happens if `readonly` is activated, and no `team_id` was provided
  @_folder_prefix ||= currently_used_team_id
  if @_folder_prefix.nil?
    # We use a `@_folder_prefix` variable, to keep state between multiple calls of this
    # method, as the value won't change. This way the warning is only printed once
    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")
    @_folder_prefix = "*"
  end
  return File.join(working_directory, @_folder_prefix)
end

#skip_docsObject



196
197
198
# File 'match/lib/match/storage/google_cloud_storage.rb', line 196

def skip_docs
  false
end

#upload_files(files_to_upload: [], custom_message: nil) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'match/lib/match/storage/google_cloud_storage.rb', line 177

def upload_files(files_to_upload: [], custom_message: nil)
  # `files_to_upload` is an array of files that need to be uploaded to Google Cloud
  # Those doesn't mean they're new, it might just be they're changed
  # Either way, we'll upload them using the same technique

  files_to_upload.each do |current_file|
    # Go from
    #   "/var/folders/px/bz2kts9n69g8crgv4jpjh6b40000gn/T/d20181026-96528-1av4gge/profiles/development/Development_me.mobileprovision"
    # to
    #   "profiles/development/Development_me.mobileprovision"
    #

    # We also have to remove the trailing `/` as Google Cloud doesn't handle it nicely
    target_path = current_file.gsub(self.working_directory + "/", "")
    UI.verbose("Uploading '#{target_path}' to Google Cloud Storage...")
    bucket.create_file(current_file, target_path)
  end
end