class Match::Storage::GoogleCloudStorage

Store the code signing identities in on Google Cloud Storage

Constants

DEFAULT_KEYS_FILE_NAME

Attributes

bucket_name[R]
gc_storage[RW]

Managed values

google_cloud_keys_file[R]
google_cloud_project_id[R]
platform[R]
type[R]

User provided values

Public Class Methods

configure(params) click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 25
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]
  )
end
new(type: nil, platform: nil, google_cloud_bucket_name: nil, google_cloud_keys_file: nil, google_cloud_project_id: nil) click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 42
def initialize(type: nil,
               platform: nil,
               google_cloud_bucket_name: nil,
               google_cloud_keys_file: nil,
               google_cloud_project_id: 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

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

Public Instance Methods

delete_files(files_to_delete: [], custom_message: nil) click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 102
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
download() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 84
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_content() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 138
def generate_matchfile_content
  return "bucket_name(\"#{self.bucket_name}\")"
end
human_readable_description() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 111
def human_readable_description
  "Google Cloud Bucket [#{self.google_cloud_project_id}/#{self.bucket_name}]"
end
skip_docs() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 134
def skip_docs
  false
end
upload_files(files_to_upload: [], custom_message: nil) click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 115
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

Private Instance Methods

bucket() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 144
def bucket
  @_bucket ||= self.gc_storage.bucket(self.bucket_name)

  if @_bucket.nil?
    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")
  end

  return @_bucket
end
ensure_bucket_is_selected() click to toggle source
# File match/lib/match/storage/google_cloud_storage.rb, line 241
def ensure_bucket_is_selected
  # In case the user didn't provide a bucket name yet, they will
  # be asked to provide one here
  while self.bucket_name.to_s.length == 0
    # Have a nice selection of the available buckets here
    # This can only happen after we went through auth of Google Cloud
    available_bucket_identifiers = self.gc_storage.buckets.collect(&:id)
    if available_bucket_identifiers.count > 0
      @bucket_name = UI.select("What Google Cloud Storage bucket do you want to use? (you can define it using the `google_cloud_bucket_name` key)", available_bucket_identifiers)
    else
      UI.error("Looks like your Google Cloud account for the project ID '#{self.google_cloud_project_id}' doesn't")
      UI.error("have any available storage buckets yet. Please visit the following URL")
      UI.message("")
      UI.message("\t\thttps://console.cloud.google.com/storage/browser".cyan)
      UI.message("")
      UI.message("and make sure to have the right project selected on top of the page")
      UI.message("click on " + "Create Bucket".cyan + ", choose a name and confirm")
      UI.message("")
      UI.input("Once you're finished, please confirm with enter")
    end
  end
end
ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id) click to toggle source

This method will make sure the keys file exists If it's missing, it will help the user set things up

# File match/lib/match/storage/google_cloud_storage.rb, line 160
def ensure_keys_file_exists(google_cloud_keys_file, google_cloud_project_id)
  if google_cloud_keys_file && File.exist?(google_cloud_keys_file)
    return google_cloud_keys_file
  end

  return DEFAULT_KEYS_FILE_NAME if File.exist?(DEFAULT_KEYS_FILE_NAME)

  fastlane_folder_gc_keys_path = File.join(FastlaneCore::FastlaneFolder.path, DEFAULT_KEYS_FILE_NAME)
  return fastlane_folder_gc_keys_path if File.exist?(fastlane_folder_gc_keys_path)

  if google_cloud_project_id.to_s.length > 0
    # Check to see if this system has application default keys installed.
    # These are the default keys that the Google Cloud APIs use when no other keys are specified.
    # Users with the Google Cloud SDK installed can generate them by running...
    # `gcloud auth application-default login`
    # ...and then they'll be automatically available.
    # The nice thing about these keys is they can be associated with the user's login in GCP
    # (e.g. my_account@gmail.com), so teams can control access to the certificate bucket
    # using a mailing list of developer logins instead of generating service accounts
    # and keys.
    application_default_keys = nil
    begin
      application_default_keys = Google::Auth.get_application_default
    rescue
      # This means no application default keys have been installed. That's perfectly OK,
      # we can continue and ask the user if they want to use a keys file.
    end

    if application_default_keys && UI.confirm("Do you want to use this system's Google Cloud application default keys?")
      return nil
    end
  end

  # User doesn't seem to have provided a keys file.
  UI.message("Looks like you don't have a Google Cloud #{DEFAULT_KEYS_FILE_NAME.cyan} file")
  UI.message("If you have one, make sure to put it into the '#{Dir.pwd}' directory and call it '#{DEFAULT_KEYS_FILE_NAME.cyan}'")
  unless UI.confirm("Do you want fastlane to help you to create a #{DEFAULT_KEYS_FILE_NAME} file?")
    UI.user_error!("Process stopped, run fastlane again to start things up again")
  end

  UI.message("fastlane will help you create a keys file. First, open the following website")
  UI.message("")
  UI.message("\t\thttps://console.cloud.google.com".cyan)
  UI.message("")
  UI.input("Press enter once you're logged in")

  UI.message("Now it's time to generate a new JSON auth file for fastlane to access Google Cloud")
  UI.message("First, switch to the Google Cloud project you want to use.")
  UI.message("If you don't have one yet, create a new one and switch to it")
  UI.message("")
  UI.message("\t\thttps://console.cloud.google.com/apis/credentials".cyan)
  UI.message("")
  UI.input("Ensure the right project is selected on top of the page and confirm with enter")

  UI.message("Now create a new JSON auth file by clicking on")
  UI.message("")
  UI.message("\t\t 1. Create credentials".cyan)
  UI.message("\t\t 2. Service account key".cyan)
  UI.message("\t\t 3. App Engine default service account".cyan)
  UI.message("\t\t 4. JSON".cyan)
  UI.message("\t\t 5. Create".cyan)
  UI.message("")
  UI.input("Confirm with enter once you created and download the JSON file")

  UI.message("Copy the file to the current directory (#{Dir.pwd})")
  UI.message("and rename it to `#{DEFAULT_KEYS_FILE_NAME.cyan}`")
  UI.message("")
  UI.input("Confirm with enter")

  until File.exist?(DEFAULT_KEYS_FILE_NAME)
    UI.message("Make sure to place the file in '#{Dir.pwd.cyan}' and name it '#{DEFAULT_KEYS_FILE_NAME.cyan}'")
    UI.input("Confirm with enter")
  end

  UI.important("Please never add the #{DEFAULT_KEYS_FILE_NAME.cyan} file in version control.")
  UI.important("Instead please add the file to your .gitignore")
  UI.input("Confirm with enter")

  return DEFAULT_KEYS_FILE_NAME
end