class Fastlane::Actions::SendBuildToBugsnagAction

Constants

BUILD_TOOL

Public Class Methods

authors() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 99
def self.authors
  ["cawllec"]
end
available_options() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 123
def self.available_options
  git_options = load_git_remote_options
  [
    FastlaneCore::ConfigItem.new(key: :config_file,
                                 description: "AndroidManifest.xml/Info.plist location",
                                 optional: true,
                                 default_value: default_config_file_path),
    FastlaneCore::ConfigItem.new(key: :api_key,
                                 description: "Bugsnag API Key",
                                 optional: true),
    FastlaneCore::ConfigItem.new(key: :app_version,
                                 description: "App version being built",
                                 optional: true),
    FastlaneCore::ConfigItem.new(key: :android_version_code,
                                 description: "Android app version code",
                                 optional: true),
    FastlaneCore::ConfigItem.new(key: :ios_bundle_version,
                                 description: "iOS/macOS/tvOS bundle version",
                                 optional: true),
    FastlaneCore::ConfigItem.new(key: :release_stage,
                                 description: "Release stage being built, i.e. staging, production",
                                 optional: true),
    FastlaneCore::ConfigItem.new(key: :builder,
                                 description: "The name of the entity triggering the build",
                                 optional: true,
                                 default_value: `whoami`.chomp),
    FastlaneCore::ConfigItem.new(key: :repository,
                                 description: "The source control repository URL for this application",
                                 optional: true,
                                 default_value: git_options[:repository]),
    FastlaneCore::ConfigItem.new(key: :revision,
                                 description: "The source control revision id",
                                 optional: true,
                                 default_value: git_options[:revision]),
    FastlaneCore::ConfigItem.new(key: :provider,
                                 description: "The source control provider, one of 'github-enterprise', 'gitlab-onpremise', or 'bitbucket-server', if any",
                                 optional: true,
                                 default_value: nil,
                                 verify_block: proc do |value|
                                   valid = ['github', 'github-enterprise', 'gitlab', 'gitlab-onpremise', 'bitbucket', 'bitbucket-server'].include? value
                                   unless valid
                                     UI.user_error!("Provider must be one of 'github', 'github-enterprise', 'gitlab', 'gitlab-onpremise', 'bitbucket', 'bitbucket-server', or unspecified")
                                   end
                                 end),
    FastlaneCore::ConfigItem.new(key: :endpoint,
                                 description: "Bugsnag deployment endpoint",
                                 optional: true,
                                 default_value: "https://build.bugsnag.com")
  ]
end
category() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 107
def self.category
  :building
end
description() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 95
def self.description
  "Notifies Bugsnag of a build"
end
details() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 115
def self.details
  "Notifies Bugsnag of a new build being released including app version and source control details"
end
example_code() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 103
def self.example_code
  ['send_build_to_bugsnag']
end
is_supported?(platform) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 119
def self.is_supported?(platform)
  [:ios, :mac, :android].include?(platform)
end
missing_api_key_message(params) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 59
def self.missing_api_key_message(params)
  message = "A Bugsnag API key is required to release a build. "
  if lane_context[:PLATFORM_NAME] == :android
    if params[:config_file]
      message << "Set com.bugsnag.android.API_KEY in your AndroidManifest.xml to detect API key automatically."
    else
      message << "Set the config_file option with the path to your AndroidManifest.xml and set com.bugsnag.android.API_KEY in it to detect API key automatically."
    end
  else
    if params[:config_file]
      message << "Set bugsnag.apiKey in your Info.plist file to detect API key automatically."
    else
      message << "Set the config_file option with the path to your Info.plist and set bugsnag.apiKey in it to detect API key automatically."
    end
  end
  message
end
missing_app_version_message(params) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 77
def self.missing_app_version_message(params)
  message = "An app version must be specified release a build. "
  if lane_context[:PLATFORM_NAME] == :android
    if params[:config_file]
      message << "Set com.bugsnag.android.APP_VERSION in your AndroidManifest.xml to detect this value automatically."
    else
      message << "Set the config_file option with the path to your AndroidManifest.xml and set com.bugsnag.android.APP_VERSION in it to detect this value automatically."
    end
  else
    if params[:config_file]
      message << "Set the app_version option with your app version or set config_file to update the path to your Info.plist"
    else
      message << "Set the config_file option with the path to your Info.plist"
    end
  end
  message
end
return_value() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 111
def self.return_value
  nil
end
run(params) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 10
def self.run(params)
  payload = {buildTool: BUILD_TOOL, sourceControl: {}}

  # If a configuration file was found or was specified, load in the options:
  if params[:config_file]
    UI.message("Loading build information from #{params[:config_file]}")
    config_options = load_config_file_options(params[:config_file])

    # for each of the config options, if it's not been overriden by any
    # input to the lane, write it to the payload:
    payload[:apiKey] = params[:api_key] || config_options[:apiKey]
    payload[:appVersion] = params[:app_version] || config_options[:appVersion]
    payload[:appVersionCode] = params[:android_version_code] || config_options[:appVersionCode]
    payload[:appBundleVersion] = params[:ios_bundle_version] || config_options[:appBundleVersion]
    payload[:releaseStage] = params[:release_stage] || config_options[:releaseStage] || "production"
  else
    # No configuration file was found or specified, use the input parameters:
    payload[:apiKey] = params[:api_key]
    payload[:appVersion] = params[:app_version]
    payload[:appVersionCode] = params[:android_version_code]
    payload[:appBundleVersion] = params[:ios_bundle_version]
    payload[:releaseStage] = params[:release_stage] || "production"
  end

  # If builder, or source control information has been provided into
  # Fastlane, apply it to the payload here.
  payload[:builderName] = params[:builder] if params[:builder]
  payload[:sourceControl][:revision] = params[:revision] if params[:revision]
  payload[:sourceControl][:repository] = params[:repository] if params[:repository]
  payload[:sourceControl][:provider] = params[:provider] if params[:provider]

  payload.reject! {|k,v| v == nil || (v.is_a?(Hash) && v.empty?)}

  if payload[:apiKey].nil? || !payload[:apiKey].is_a?(String)
    UI.user_error! missing_api_key_message(params)
  end
  if payload[:appVersion].nil?
    UI.user_error! missing_app_version_message(params)
  end

  # If verbose flag is enabled (`--verbose`), display the payload debug info
  UI.verbose("Sending build to Bugsnag with payload:")
  payload.each do |param|
    UI.verbose("  #{param[0].to_s.rjust(18)}: #{param[1]}")
  end

  send_notification(params[:endpoint], ::JSON.dump(payload))
end

Private Class Methods

default_android_manifest_path() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 247
def self.default_android_manifest_path
  Dir.glob("./{android/,}{app,}/src/main/AndroidManifest.xml").sort.first
end
default_config_file_path() click to toggle source

Used to get a default configuration file (AndroidManifest.xml or Info.plist)

# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 194
def self.default_config_file_path
  case lane_context[:PLATFORM_NAME]
  when nil
    if file_path = default_android_manifest_path
      return file_path
    elsif file_path = default_info_plist_path
      return file_path
    end
  when :android
    if file_path = default_android_manifest_path
      return file_path
    end
  else
    if file_path = default_info_plist_path
      return file_path
    end
  end
end
default_info_plist_path() click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 227
def self.default_info_plist_path
  Dir.glob("./{ios/,}*/Info.plist").reject {|path| path =~ /build|test/i }.sort.first
end
get_meta_data(object, output = []) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 290
def self.get_meta_data(object, output = [])
  if object.is_a?(Array)
    object.each do |item|
      output = get_meta_data(item, output)
    end
  elsif object.is_a?(Hash)
    object.each do |key, value|
      if key === "meta-data"
        output << value
      elsif value.is_a?(Array) || value.is_a?(Hash)
        output = get_meta_data(value, output)
      end
    end
  end
  output.flatten
end
load_config_file_options(config_file) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 213
def self.load_config_file_options config_file
  options = {}
  case File.extname(config_file)
  when ".xml"
    options = load_options_from_xml(config_file)
    return options
  when ".plist"
    options = load_options_from_plist(config_file)
    return options
  else
    UI.user_error("File type of '#{config_file}' was not recognised. This should be .xml for Android and .plist for Cococa")
  end
end
load_git_remote_options() click to toggle source

Get any Git options (remote repo, and revision) from the directory

# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 177
def self.load_git_remote_options
  git_options = {repository:nil, revision:nil}
  require "git"
  begin
    repo = Git.open(Dir.pwd)
    origin = repo.remotes.detect {|r| r.name == "origin"}
    origin = repo.remotes.first unless origin
    if origin
      git_options[:repository] = origin.url
      git_options[:revision] = repo.revparse("HEAD")
    end
  rescue
  end
  return git_options
end
load_options_from_plist(file_path) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 231
def self.load_options_from_plist file_path
  options = {}
  plist_getter = Fastlane::Actions::GetInfoPlistValueAction
  bugsnag_dict = plist_getter.run(path: file_path, key: "bugsnag")
  api_key = bugsnag_dict["apiKey"] unless bugsnag_dict.nil?
  release_stage = bugsnag_dict["releaseStage"] unless bugsnag_dict.nil?
  if api_key.nil?
    api_key = plist_getter.run(path: file_path, key: "BugsnagAPIKey")
  end
  options[:apiKey] = api_key
  options[:releaseStage] = release_stage
  options[:appVersion] = plist_getter.run(path: file_path, key: "CFBundleShortVersionString")
  options[:appBundleVersion] = plist_getter.run(path: file_path, key: "CFBundleVersion")
  return options
end
load_options_from_xml(file_path) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 251
def self.load_options_from_xml file_path
  options = options_from_android_manifest(file_path)
  build_gradle_path = Dir.glob("{android/,}app/build.gradle").sort.first || Dir.glob("build.gradle").sort.first
  options.merge!(options_from_build_gradle(build_gradle_path)) if build_gradle_path
  return options
end
map_meta_data(meta_data) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 307
def self.map_meta_data(meta_data)
  output = {}
  meta_data.each do |hash|
    output[hash["android:name"]] = hash["android:value"]
  end
  output
end
options_from_android_manifest(file_path) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 258
def self.options_from_android_manifest file_path
  options = {}
  begin
    meta_data = parse_android_manifest_options(XmlSimple.xml_in(file_path))
    options[:apiKey] = meta_data["com.bugsnag.android.API_KEY"]
    options[:appVersion] = meta_data["com.bugsnag.android.APP_VERSION"]
    options[:releaseStage] = meta_data["com.bugsnag.android.RELEASE_STAGE"]
  rescue ArgumentError
    nil
  end
  options
end
options_from_build_gradle(file_path) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 271
def self.options_from_build_gradle file_path
  options = {}
  begin
    content = File.read(file_path)
    if content =~ /versionCode (\d+)/
      options[:appVersionCode] = $1
    end
    if content =~ /versionName \W(.*)\W[\s]*\n/
      options[:appVersion] = $1
    end
  rescue
  end
  options
end
parse_android_manifest_options(config_hash) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 286
def self.parse_android_manifest_options config_hash
  map_meta_data(get_meta_data(config_hash))
end
parse_response_body(response) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 315
def self.parse_response_body(response)
  begin
    JSON.load(response.body)
  rescue
    nil
  end
end
send_notification(url, body) click to toggle source
# File lib/fastlane/plugin/bugsnag/actions/send_build_to_bugsnag.rb, line 323
def self.send_notification(url, body)
  require "net/http"
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.read_timeout = 15
  http.open_timeout = 15

  http.use_ssl = uri.scheme == "https"

  uri.path == "" ? "/" : uri.path
  request = Net::HTTP::Post.new(uri, {"Content-Type" => "application/json"})
  request.body = body
  begin
    response = http.request(request)
  rescue => e
    UI.user_error! "Failed to notify Bugsnag of a new build: #{e}"
  end
  if body = parse_response_body(response)
    if body.has_key? "errors"
      errors = body["errors"].map {|error| "\n  * #{error}"}.join
      UI.user_error! "The following errors occurred while notifying Bugsnag:#{errors}.\n\nPlease update your lane config and retry."
    elsif response.code != "200"
      UI.user_error! "Failed to notify Bugsnag of a new build. Please retry. HTTP status code: #{response.code}"
    end
    if body.has_key? "warnings"
      warnings = body["warnings"].map {|warn| "\n  * #{warn}"}.join
      UI.important "Sending the build to Bugsnag succeeded with the following warnings:#{warnings}\n\nPlease update your lane config."
    end
  end
end