fastlane-plugin-firebase_app_distribution 0.1.0 → 0.2.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +6 -17
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +54 -30
- data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb +58 -0
- data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb +185 -0
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb +48 -0
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +21 -0
- data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb +22 -26
- data/lib/fastlane/plugin/firebase_app_distribution/helper/upload_status_response.rb +25 -0
- data/lib/fastlane/plugin/firebase_app_distribution/version.rb +1 -1
- metadata +19 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ab7379d2d8feb6112f778c3b4a64e2f2181a099a
|
4
|
+
data.tar.gz: bdb617b84fb387a5486faee45d23be3043a9d9af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69f808e806793eb81839ac45241445c8764884f35c92c25066c86121c5ecb7ebf981371ed347fceadbfb0161df03ac6613fbd3f2aebe42a935783ca55d239df5
|
7
|
+
data.tar.gz: 83c298cdfd08b945957f10f9194142423418b89ee5dc17a2718b1506013e79fbba2f94ad11e1b7c0b92a874c3e69731eba327e66f393bb61b99fae0bf423d5b5
|
data/README.md
CHANGED
@@ -1,26 +1,15 @@
|
|
1
|
-
# firebase_app_distribution plugin
|
2
1
|
|
3
|
-
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-firebase_app_distribution)
|
4
|
-
|
5
|
-
## Getting Started
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
```bash
|
10
|
-
fastlane add_plugin firebase_app_distribution
|
11
|
-
```
|
3
|
+
# ![Firebase App Distribution](fad-icon.png) Firebase App Distribution
|
12
4
|
|
13
|
-
|
14
|
-
|
15
|
-
Release your beta builds to Firebase App Distro
|
16
|
-
|
17
|
-
**Note to author:** Add a more detailed description about this plugin here. If your plugin contains multiple actions, make sure to mention them here.
|
5
|
+
[![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-firebase_app_distribution)
|
18
6
|
|
19
|
-
|
7
|
+
Firebase App Distribution makes distributing your apps to trusted testers painless. By getting your apps onto testers' devices quickly, you can get feedback early and often. To learn more about Firebase App Distribution, go [here](https://firebase.google.com/docs/app-distribution).
|
20
8
|
|
21
|
-
Check out the [example `Fastfile`](fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
|
22
9
|
|
23
|
-
|
10
|
+
## Getting Started
|
11
|
+
- [iOS](https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane)
|
12
|
+
- [Android](https://firebase.google.com/docs/app-distribution/android/distribute-fastlane)
|
24
13
|
|
25
14
|
## Run tests for this plugin
|
26
15
|
|
data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
CHANGED
@@ -1,58 +1,70 @@
|
|
1
|
-
require 'tempfile'
|
2
1
|
require 'fastlane/action'
|
3
2
|
require 'open3'
|
4
3
|
require 'shellwords'
|
4
|
+
require 'googleauth'
|
5
|
+
require_relative '../helper/upload_status_response'
|
5
6
|
require_relative '../helper/firebase_app_distribution_helper'
|
7
|
+
require_relative '../helper/firebase_app_distribution_error_message'
|
8
|
+
require_relative '../client/firebase_app_distribution_api_client'
|
9
|
+
require_relative '../helper/firebase_app_distribution_auth_client'
|
6
10
|
|
7
11
|
## TODO: should always use a file underneath? I think so.
|
8
12
|
## How should we document the usage of release notes?
|
9
13
|
module Fastlane
|
10
14
|
module Actions
|
11
15
|
class FirebaseAppDistributionAction < Action
|
12
|
-
|
13
|
-
DEFAULT_FIREBASE_CLI_PATH = %x(which firebase).chomp
|
16
|
+
DEFAULT_FIREBASE_CLI_PATH = `which firebase`
|
14
17
|
FIREBASECMD_ACTION = "appdistribution:distribute".freeze
|
15
|
-
|
18
|
+
|
19
|
+
extend Auth::FirebaseAppDistributionAuthClient
|
16
20
|
extend Helper::FirebaseAppDistributionHelper
|
17
21
|
|
18
22
|
def self.run(params)
|
19
23
|
params.values # to validate all inputs before looking for the ipa/apk
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
+
auth_token = fetch_auth_token(params[:service_credentials_file])
|
25
|
+
fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token)
|
26
|
+
platform = Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
|
27
|
+
binary_path = params[:ipa_path] || params[:apk_path]
|
28
|
+
|
29
|
+
if params[:app] # Set app_id if it is specified as a parameter
|
30
|
+
app_id = params[:app]
|
31
|
+
elsif platform == :ios
|
32
|
+
archive_path = Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]
|
33
|
+
if archive_path
|
34
|
+
app_id = get_ios_app_id_from_archive(archive_path)
|
35
|
+
end
|
24
36
|
end
|
25
37
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
cmd << "--testers-file #{testers_file}" if testers_file
|
34
|
-
cmd << "--release-notes-file #{release_file}" if release_file
|
38
|
+
if app_id.nil?
|
39
|
+
UI.crash!(ErrorMessage::MISSING_APP_ID)
|
40
|
+
end
|
41
|
+
release_id = fad_api_client.upload(app_id, binary_path)
|
42
|
+
if release_id.nil?
|
43
|
+
return
|
44
|
+
end
|
35
45
|
|
36
|
-
|
37
|
-
|
38
|
-
print_command: false,
|
39
|
-
print_command_output: true
|
40
|
-
)
|
46
|
+
release_notes = get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
|
47
|
+
fad_api_client.post_notes(app_id, release_id, release_notes)
|
41
48
|
|
42
|
-
|
49
|
+
testers = get_value_from_value_or_file(params[:testers], params[:testers_file])
|
50
|
+
groups = get_value_from_value_or_file(params[:groups], params[:groups_file])
|
51
|
+
emails = string_to_array(testers)
|
52
|
+
group_ids = string_to_array(groups)
|
53
|
+
fad_api_client.enable_access(app_id, release_id, emails, group_ids)
|
54
|
+
UI.success("App Distribution upload finished successfully")
|
43
55
|
end
|
44
56
|
|
45
57
|
def self.description
|
46
|
-
"Release your beta builds
|
58
|
+
"Release your beta builds with Firebase App Distribution"
|
47
59
|
end
|
48
60
|
|
49
61
|
def self.authors
|
50
|
-
["Stefan Natchev"]
|
62
|
+
["Stefan Natchev", "Manny Jimenez Github: mannyjimenez0810, Alonso Salas Infante Github: alonsosalasinfante"]
|
51
63
|
end
|
52
64
|
|
53
65
|
# supports markdown.
|
54
66
|
def self.details
|
55
|
-
"Release your beta builds
|
67
|
+
"Release your beta builds with Firebase App Distribution"
|
56
68
|
end
|
57
69
|
|
58
70
|
def self.available_options
|
@@ -90,7 +102,7 @@ module Fastlane
|
|
90
102
|
FastlaneCore::ConfigItem.new(key: :app,
|
91
103
|
env_name: "FIREBASEAPPDISTRO_APP",
|
92
104
|
description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
|
93
|
-
optional:
|
105
|
+
optional: true,
|
94
106
|
type: String),
|
95
107
|
FastlaneCore::ConfigItem.new(key: :firebase_cli_path,
|
96
108
|
env_name: "FIREBASEAPPDISTRO_FIREBASE_CLI_PATH",
|
@@ -100,12 +112,13 @@ module Fastlane
|
|
100
112
|
optional: false,
|
101
113
|
type: String,
|
102
114
|
verify_block: proc do |value|
|
115
|
+
value.chomp!
|
103
116
|
if value.to_s == "" || !File.exist?(value)
|
104
117
|
UI.user_error!("firebase_cli_path: missing path to firebase cli tool. Please install firebase in $PATH or specify path")
|
105
118
|
end
|
106
119
|
|
107
120
|
unless is_firebasecmd_supported?(value)
|
108
|
-
|
121
|
+
UI.user_error!("firebase_cli_path: `#{value}` does not support the `#{FIREBASECMD_ACTION}` command. Please download (https://appdistro.page.link/firebase-cli-download) or specify the path to the correct version of firebse")
|
109
122
|
end
|
110
123
|
end),
|
111
124
|
FastlaneCore::ConfigItem.new(key: :groups,
|
@@ -140,6 +153,19 @@ module Fastlane
|
|
140
153
|
description: "Release notes file for this build",
|
141
154
|
optional: true,
|
142
155
|
type: String),
|
156
|
+
FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
|
157
|
+
description: "Auth token for firebase cli",
|
158
|
+
optional: true,
|
159
|
+
type: String),
|
160
|
+
FastlaneCore::ConfigItem.new(key: :debug,
|
161
|
+
description: "Print verbose debug output",
|
162
|
+
optional: true,
|
163
|
+
default_value: false,
|
164
|
+
is_string: false),
|
165
|
+
FastlaneCore::ConfigItem.new(key: :service_credentials_file,
|
166
|
+
description: "Path to Google service account json",
|
167
|
+
optional: true,
|
168
|
+
type: String)
|
143
169
|
]
|
144
170
|
end
|
145
171
|
|
@@ -162,8 +188,6 @@ module Fastlane
|
|
162
188
|
]
|
163
189
|
end
|
164
190
|
|
165
|
-
private
|
166
|
-
|
167
191
|
## TODO: figure out if we can surpress color output.
|
168
192
|
def self.is_firebasecmd_supported?(cmd)
|
169
193
|
outerr, status = Open3.capture2e(cmd, "--non-interactive", FIREBASECMD_ACTION, "--help")
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'googleauth'
|
2
|
+
require 'googleauth/stores/file_token_store'
|
3
|
+
require "google/apis/people_v1"
|
4
|
+
require "fileutils"
|
5
|
+
|
6
|
+
module Fastlane
|
7
|
+
module Actions
|
8
|
+
class FirebaseAppDistributionLoginAction < Action
|
9
|
+
OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
|
10
|
+
SCOPE = "https://www.googleapis.com/auth/cloud-platform"
|
11
|
+
CLIENT_ID = "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"
|
12
|
+
CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
|
13
|
+
|
14
|
+
def self.run(params)
|
15
|
+
client_id = Google::Auth::ClientId.new(CLIENT_ID, CLIENT_SECRET)
|
16
|
+
authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, nil)
|
17
|
+
url = authorizer.get_authorization_url(base_url: OOB_URI)
|
18
|
+
|
19
|
+
UI.message("Please open the following address in your browser:")
|
20
|
+
UI.message(url)
|
21
|
+
UI.message("")
|
22
|
+
code = UI.input("Enter the resulting code here: ")
|
23
|
+
credentials = authorizer.get_credentials_from_code(code: code, base_url: OOB_URI)
|
24
|
+
|
25
|
+
UI.message("Refresh Token: #{credentials.refresh_token}")
|
26
|
+
UI.message("")
|
27
|
+
UI.message("Set the refresh token as a FIREBASE_TOKEN environment variable")
|
28
|
+
rescue Signet::AuthorizationError
|
29
|
+
UI.error("The code you entered was invalid. Ensure that you have copied the code correctly.")
|
30
|
+
rescue => error
|
31
|
+
UI.error(error.to_s)
|
32
|
+
UI.crash!("An error has occured please login again.")
|
33
|
+
end
|
34
|
+
|
35
|
+
#####################################################
|
36
|
+
# @!group Documentation
|
37
|
+
#####################################################
|
38
|
+
|
39
|
+
def self.description
|
40
|
+
"Authenticate with Firebase App Distribution using a Google account."
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.details
|
44
|
+
"Log in to Firebase App Distribution using a Google account to generate an authentication "\
|
45
|
+
"token. This token is stored within an environment variable and used to authenticate with your Firebase project. "\
|
46
|
+
"See https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane for more information."
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.authors
|
50
|
+
["Manny Jimenez Github: mannyjimenez0810, Alonso Salas Infante Github: alonsosalasinfante"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.is_supported?(platform)
|
54
|
+
[:ios, :android].include?(platform)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
require_relative '../actions/firebase_app_distribution_login'
|
3
|
+
|
4
|
+
module Fastlane
|
5
|
+
module Client
|
6
|
+
class FirebaseAppDistributionApiClient
|
7
|
+
BASE_URL = "https://firebaseappdistribution.googleapis.com"
|
8
|
+
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
|
9
|
+
MAX_POLLING_RETRIES = 60
|
10
|
+
POLLING_INTERVAL_SECONDS = 2
|
11
|
+
|
12
|
+
def initialize(auth_token)
|
13
|
+
@auth_token = auth_token
|
14
|
+
end
|
15
|
+
|
16
|
+
# Enables tester access to the specified app release. Skips this
|
17
|
+
# step if no testers are passed in (emails and group_ids are nil/empty).
|
18
|
+
#
|
19
|
+
# args
|
20
|
+
# app_id - Firebase App ID
|
21
|
+
# release_id - App release ID, returned by upload_status endpoint
|
22
|
+
# emails - String array of app testers' email addresses
|
23
|
+
# group_ids - String array of Firebase tester group IDs
|
24
|
+
#
|
25
|
+
# Throws a user_error if app_id, emails, or group_ids are invalid
|
26
|
+
def enable_access(app_id, release_id, emails, group_ids)
|
27
|
+
if (emails.nil? || emails.empty?) && (group_ids.nil? || group_ids.empty?)
|
28
|
+
UI.message("No testers passed in. Skipping this step")
|
29
|
+
return
|
30
|
+
end
|
31
|
+
payload = { emails: emails, groupIds: group_ids }
|
32
|
+
begin
|
33
|
+
connection.post(enable_access_url(app_id, release_id), payload.to_json) do |request|
|
34
|
+
request.headers["Authorization"] = "Bearer " + @auth_token
|
35
|
+
end
|
36
|
+
rescue Faraday::ResourceNotFound
|
37
|
+
UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
38
|
+
rescue Faraday::ClientError
|
39
|
+
UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroups: #{group_ids}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Posts notes for the specified app release. Skips this
|
44
|
+
# step if no notes are passed in (release_notes is nil/empty).
|
45
|
+
#
|
46
|
+
# args
|
47
|
+
# app_id - Firebase App ID
|
48
|
+
# release_id - App release ID, returned by upload_status endpoint
|
49
|
+
# release_notes - String of notes for this release
|
50
|
+
#
|
51
|
+
# Throws a user_error if app_id or release_id are invalid
|
52
|
+
def post_notes(app_id, release_id, release_notes)
|
53
|
+
payload = { releaseNotes: { releaseNotes: release_notes } }
|
54
|
+
if release_notes.nil? || release_notes.empty?
|
55
|
+
UI.message("No release notes passed in. Skipping this step.")
|
56
|
+
return
|
57
|
+
end
|
58
|
+
begin
|
59
|
+
connection.post(release_notes_create_url(app_id, release_id), payload.to_json) do |request|
|
60
|
+
request.headers["Authorization"] = "Bearer " + @auth_token
|
61
|
+
end
|
62
|
+
rescue Faraday::ResourceNotFound
|
63
|
+
UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
64
|
+
# rescue Faraday::ClientError
|
65
|
+
# UI.user_error!("#{ErrorMessage::INVALID_RELEASE_ID}: #{release_id}")
|
66
|
+
end
|
67
|
+
UI.success("Release notes have been posted.")
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_upload_token(app_id, binary_path)
|
71
|
+
begin
|
72
|
+
binary_hash = Digest::SHA256.hexdigest(File.open(binary_path).read)
|
73
|
+
rescue Errno::ENOENT
|
74
|
+
UI.crash!("#{ErrorMessage::APK_NOT_FOUND}: #{binary_path}")
|
75
|
+
end
|
76
|
+
|
77
|
+
begin
|
78
|
+
response = connection.get(v1_apps_url(app_id)) do |request|
|
79
|
+
request.headers["Authorization"] = "Bearer " + @auth_token
|
80
|
+
end
|
81
|
+
rescue Faraday::ResourceNotFound
|
82
|
+
UI.crash!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
83
|
+
end
|
84
|
+
contact_email = response.body[:contactEmail]
|
85
|
+
if contact_email.nil? || contact_email.strip.empty?
|
86
|
+
UI.crash!(ErrorMessage::GET_APP_NO_CONTACT_EMAIL_ERROR)
|
87
|
+
end
|
88
|
+
return upload_token_format(response.body[:appId], response.body[:projectNumber], binary_hash)
|
89
|
+
end
|
90
|
+
|
91
|
+
def upload_binary(app_id, binary_path)
|
92
|
+
connection.post(binary_upload_url(app_id), File.open(binary_path).read) do |request|
|
93
|
+
request.headers["Authorization"] = "Bearer " + @auth_token
|
94
|
+
end
|
95
|
+
rescue Faraday::ResourceNotFound
|
96
|
+
UI.crash!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
97
|
+
rescue Errno::ENOENT
|
98
|
+
UI.crash!("#{ErrorMessage::APK_NOT_FOUND}: #{binary_path}")
|
99
|
+
end
|
100
|
+
|
101
|
+
# Uploads the binary file if it has not already been uploaded
|
102
|
+
# Takes at least POLLING_INTERVAL_SECONDS between polling get_upload_status
|
103
|
+
#
|
104
|
+
# args
|
105
|
+
# app_id - Firebase App ID
|
106
|
+
# binary_path - Absolute path to your app's apk/ipa file
|
107
|
+
#
|
108
|
+
# Returns the release_id on a successful release, otherwise returns nil.
|
109
|
+
#
|
110
|
+
# Throws an error if the number of polling retries exceeds MAX_POLLING_RETRIES
|
111
|
+
def upload(app_id, binary_path)
|
112
|
+
upload_token = get_upload_token(app_id, binary_path)
|
113
|
+
upload_status_response = get_upload_status(app_id, upload_token)
|
114
|
+
if upload_status_response.success?
|
115
|
+
UI.success("This APK/IPA has been uploaded before. Skipping upload step.")
|
116
|
+
else
|
117
|
+
UI.message("This APK has not been uploaded before.")
|
118
|
+
MAX_POLLING_RETRIES.times do
|
119
|
+
if upload_status_response.success?
|
120
|
+
UI.success("Uploaded APK/IPA Successfully!")
|
121
|
+
break
|
122
|
+
elsif upload_status_response.in_progress?
|
123
|
+
sleep(POLLING_INTERVAL_SECONDS)
|
124
|
+
else
|
125
|
+
UI.message("Uploading the APK/IPA.")
|
126
|
+
upload_binary(app_id, binary_path)
|
127
|
+
end
|
128
|
+
upload_status_response = get_upload_status(app_id, upload_token)
|
129
|
+
end
|
130
|
+
unless upload_status_response.success?
|
131
|
+
UI.error("It took longer than expected to process your APK/IPA, please try again.")
|
132
|
+
return nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
upload_status_response.release_id
|
136
|
+
end
|
137
|
+
|
138
|
+
# Gets the upload status for the app release.
|
139
|
+
def get_upload_status(app_id, app_token)
|
140
|
+
begin
|
141
|
+
response = connection.get(upload_status_url(app_id, app_token)) do |request|
|
142
|
+
request.headers["Authorization"] = "Bearer " + @auth_token
|
143
|
+
end
|
144
|
+
rescue Faraday::ResourceNotFound
|
145
|
+
UI.crash!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
|
146
|
+
end
|
147
|
+
return UploadStatusResponse.new(response.body)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def v1_apps_url(app_id)
|
153
|
+
"/v1alpha/apps/#{app_id}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def release_notes_create_url(app_id, release_id)
|
157
|
+
"#{v1_apps_url(app_id)}/releases/#{release_id}/notes"
|
158
|
+
end
|
159
|
+
|
160
|
+
def enable_access_url(app_id, release_id)
|
161
|
+
"#{v1_apps_url(app_id)}/releases/#{release_id}/enable_access"
|
162
|
+
end
|
163
|
+
|
164
|
+
def binary_upload_url(app_id)
|
165
|
+
"/app-binary-uploads?app_id=#{app_id}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def upload_status_url(app_id, app_token)
|
169
|
+
"#{v1_apps_url(app_id)}/upload_status/#{app_token}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def upload_token_format(app_id, project_number, binary_hash)
|
173
|
+
CGI.escape("projects/#{project_number}/apps/#{app_id}/releases/-/binaries/#{binary_hash}")
|
174
|
+
end
|
175
|
+
|
176
|
+
def connection
|
177
|
+
@connection ||= Faraday.new(url: BASE_URL) do |conn|
|
178
|
+
conn.response(:json, parser_options: { symbolize_names: true })
|
179
|
+
conn.response(:raise_error) # raise_error middleware will run before the json middleware
|
180
|
+
conn.adapter(Faraday.default_adapter)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'fastlane_core/ui/ui'
|
2
|
+
module Fastlane
|
3
|
+
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
4
|
+
module Auth
|
5
|
+
module FirebaseAppDistributionAuthClient
|
6
|
+
TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
|
7
|
+
|
8
|
+
def fetch_auth_token(google_service_path)
|
9
|
+
if !google_service_path.nil? && !google_service_path.empty?
|
10
|
+
service_account(google_service_path)
|
11
|
+
elsif ENV["FIREBASE_TOKEN"]
|
12
|
+
firebase_token
|
13
|
+
elsif ENV["GOOGLE_APPLICATION_CREDENTIALS"]
|
14
|
+
service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"])
|
15
|
+
else
|
16
|
+
UI.crash!(ErrorMessage::MISSING_CREDENTIALS)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def firebase_token
|
23
|
+
begin
|
24
|
+
client = Signet::OAuth2::Client.new(
|
25
|
+
token_credential_uri: TOKEN_CREDENTIAL_URI,
|
26
|
+
client_id: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_ID,
|
27
|
+
client_secret: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_SECRET,
|
28
|
+
refresh_token: ENV["FIREBASE_TOKEN"]
|
29
|
+
)
|
30
|
+
rescue Signet::AuthorizationError
|
31
|
+
UI.crash!(ErrorMessage::REFRESH_TOKEN_ERROR)
|
32
|
+
end
|
33
|
+
client.fetch_access_token!
|
34
|
+
client.access_token
|
35
|
+
end
|
36
|
+
|
37
|
+
def service_account(google_service_path)
|
38
|
+
service_account_credentials = Google::Auth::ServiceAccountCredentials.make_creds(
|
39
|
+
json_key_io: File.open(google_service_path),
|
40
|
+
scope: Fastlane::Actions::FirebaseAppDistributionLoginAction::SCOPE
|
41
|
+
)
|
42
|
+
service_account_credentials.fetch_access_token!["access_token"]
|
43
|
+
rescue Errno::ENOENT
|
44
|
+
UI.crash!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module ErrorMessage
|
2
|
+
MISSING_CREDENTIALS = "Missing credentials. Please check that a refresh token was set or service credentials were passed in and try again"
|
3
|
+
APK_NOT_FOUND = "Could not find the APK/IPA. Make sure you set the apk_path parameter to point to your APK/IPA"
|
4
|
+
MISSING_APP_ID = "Missing app id. Please check that it was passed in and try again"
|
5
|
+
SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again"
|
6
|
+
PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file"
|
7
|
+
PARSE_APK_METADATA_ERROR = "Failed to extract APK/IPA metadata from the APK/IPA path"
|
8
|
+
UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes"
|
9
|
+
UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups"
|
10
|
+
UPLOAD_APK_ERROR = "App Distribution halted because it had a problem uploading the APK/IPA"
|
11
|
+
APK_PROCESSING_ERROR = "App Distribution failed to process the APK/IPA"
|
12
|
+
GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information"
|
13
|
+
REFRESH_TOKEN_ERROR = "Could not generate credentials from the refresh token specified"
|
14
|
+
GET_APP_ERROR = "App Distribution failed to fetch app information"
|
15
|
+
APP_NOT_ONBOARDED_ERROR = "App Distribution not onboarded"
|
16
|
+
GET_APP_NO_CONTACT_EMAIL_ERROR = "App Distribution could not find a contact email associated with this app. Contact Email"
|
17
|
+
INVALID_APP_ID = "App Distribution could not find your app. Make sure to onboard your app by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution. App ID"
|
18
|
+
INVALID_PATH = "Could not read content from"
|
19
|
+
INVALID_TESTERS = "Could not enable access for testers. Ensure that the groups exist and the tester emails are formatted correctly"
|
20
|
+
INVALID_RELEASE_ID = "App distribution failed to fetch release with id"
|
21
|
+
end
|
data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
CHANGED
@@ -1,39 +1,35 @@
|
|
1
1
|
require 'fastlane_core/ui/ui'
|
2
|
-
|
3
2
|
module Fastlane
|
4
3
|
UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
|
5
|
-
|
6
4
|
module Helper
|
7
5
|
module FirebaseAppDistributionHelper
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
if @tempfiles == nil
|
17
|
-
@tempfiles = []
|
6
|
+
def get_value_from_value_or_file(value, path)
|
7
|
+
if (value.nil? || value.empty?) && !path.nil?
|
8
|
+
begin
|
9
|
+
return File.open(path).read
|
10
|
+
rescue Errno::ENOENT
|
11
|
+
UI.crash!("#{ErrorMessage::INVALID_PATH}: #{path}")
|
12
|
+
end
|
18
13
|
end
|
19
|
-
|
20
|
-
contents ||= from[parameter_name]
|
21
|
-
return nil if contents.nil?
|
22
|
-
|
23
|
-
file = Tempfile.new(parameter_name.to_s)
|
24
|
-
file.write(contents)
|
25
|
-
file.close
|
26
|
-
@tempfiles << file
|
27
|
-
|
28
|
-
file.path
|
14
|
+
value
|
29
15
|
end
|
30
16
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
17
|
+
# Returns the array representation of a string with comma seperated values.
|
18
|
+
#
|
19
|
+
# Does not work with strings whose individual values have spaces. EX "Hello World" the space will be removed to "HelloWorld"
|
20
|
+
def string_to_array(string)
|
21
|
+
return nil if string.nil? || string.empty?
|
22
|
+
string_array = string.gsub(/\s+/, '').split(",")
|
23
|
+
return string_array
|
35
24
|
end
|
36
25
|
|
26
|
+
def get_ios_app_id_from_archive(path)
|
27
|
+
app_path = parse_plist("#{path}/Info.plist")["ApplicationProperties"]["ApplicationPath"]
|
28
|
+
UI.shell_error!("can't extract application path from Info.plist at #{path}") if app_path.empty?
|
29
|
+
identifier = parse_plist("#{path}/Products/#{app_path}/GoogleService-Info.plist")["GOOGLE_APP_ID"]
|
30
|
+
UI.shell_error!("can't extract GOOGLE_APP_ID") if identifier.empty?
|
31
|
+
return identifier
|
32
|
+
end
|
37
33
|
end
|
38
34
|
end
|
39
35
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class UploadStatusResponse
|
2
|
+
def initialize(response_json_hash)
|
3
|
+
@response_json_hash = response_json_hash
|
4
|
+
end
|
5
|
+
|
6
|
+
def status
|
7
|
+
@response_json_hash[:status]
|
8
|
+
end
|
9
|
+
|
10
|
+
def success?
|
11
|
+
status == 'SUCCESS'
|
12
|
+
end
|
13
|
+
|
14
|
+
def in_progress?
|
15
|
+
status == "IN_PROGRESS"
|
16
|
+
end
|
17
|
+
|
18
|
+
def release_hash
|
19
|
+
@response_json_hash[:release]
|
20
|
+
end
|
21
|
+
|
22
|
+
def release_id
|
23
|
+
release_hash ? release_hash[:id] : nil
|
24
|
+
end
|
25
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-firebase_app_distribution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.pre.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stefan Natchev
|
8
|
+
- Manny Jimenez
|
9
|
+
- Alonso Salas Infante
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date:
|
13
|
+
date: 2020-07-28 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: pry
|
@@ -137,7 +139,10 @@ dependencies:
|
|
137
139
|
- !ruby/object:Gem::Version
|
138
140
|
version: 2.127.1
|
139
141
|
description:
|
140
|
-
email:
|
142
|
+
email:
|
143
|
+
- snatchev@google.com
|
144
|
+
- mannyjimenez@google.com
|
145
|
+
- alonsosi@google.com
|
141
146
|
executables: []
|
142
147
|
extensions: []
|
143
148
|
extra_rdoc_files: []
|
@@ -146,9 +151,14 @@ files:
|
|
146
151
|
- README.md
|
147
152
|
- lib/fastlane/plugin/firebase_app_distribution.rb
|
148
153
|
- lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
|
154
|
+
- lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb
|
155
|
+
- lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
|
156
|
+
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
|
157
|
+
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
|
149
158
|
- lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
|
159
|
+
- lib/fastlane/plugin/firebase_app_distribution/helper/upload_status_response.rb
|
150
160
|
- lib/fastlane/plugin/firebase_app_distribution/version.rb
|
151
|
-
homepage:
|
161
|
+
homepage: https://github.com/fastlane/fastlane-plugin-firebase_app_distribution
|
152
162
|
licenses:
|
153
163
|
- MIT
|
154
164
|
metadata: {}
|
@@ -163,12 +173,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
163
173
|
version: '0'
|
164
174
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
175
|
requirements:
|
166
|
-
- - "
|
176
|
+
- - ">"
|
167
177
|
- !ruby/object:Gem::Version
|
168
|
-
version:
|
178
|
+
version: 1.3.1
|
169
179
|
requirements: []
|
170
|
-
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.2.5
|
171
182
|
signing_key:
|
172
183
|
specification_version: 4
|
173
|
-
summary: Release your beta builds to Firebase App
|
184
|
+
summary: Release your beta builds to Firebase App Distribution. https://firebase.google.com/docs/app-distribution
|
174
185
|
test_files: []
|