fastlane-plugin-appcircle_testing_distribution 0.1.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +27 -3
- data/lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb +54 -37
- data/lib/fastlane/plugin/appcircle_testing_distribution/helper/TDAuthService.rb +49 -0
- data/lib/fastlane/plugin/appcircle_testing_distribution/helper/TDUploadService.rb +107 -0
- data/lib/fastlane/plugin/appcircle_testing_distribution/version.rb +1 -1
- metadata +19 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 56a980fcdde02bf5a93e7fd18711e5048365b997e4781590aca5c95ce5a0c438
|
4
|
+
data.tar.gz: 0ccaae94dc8a36a4c93951dc63bd6ad776686b0803ccce88e385c49573453af8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d108ba67106e1d9917f6c4b94791bc61f697f269400a1d4bf16df7cf42a6f6fe572f74bce80d55159774967e7941d015c95d2644e4544bee58c8d2226ffb6a8f
|
7
|
+
data.tar.gz: 2a452dd82232e4718adf69087fc2ed0be9ee0a0fe753bebe966d358c276880ffd1562f6dc52c8dc63f215c48e30f038d92b7927867183e223f753d9cc89b8c23
|
data/README.md
CHANGED
@@ -38,6 +38,19 @@ Testing distribution is the process of distributing test builds to designated te
|
|
38
38
|
|
39
39
|
Overall, using testing distribution in mobile DevOps significantly enhances the efficiency, security, and effectiveness of the software development process, leading to better products and faster delivery times.
|
40
40
|
|
41
|
+
## System Requirements
|
42
|
+
|
43
|
+
**Compatible Agents:**
|
44
|
+
|
45
|
+
- macOS 14.2, 14.5
|
46
|
+
|
47
|
+
**Supported Version:**
|
48
|
+
|
49
|
+
- Fastlane 2.222.0
|
50
|
+
- Ruby 3.2.2
|
51
|
+
|
52
|
+
Note: Currently, plugins are only compatible to use with **Appcircle Cloud**. **Self-hosted** support will be available in future releases.
|
53
|
+
|
41
54
|
## Testing Distribution
|
42
55
|
|
43
56
|
In order to share your builds with testers, you can create distribution profiles and assign testing groups to the distribution profiles.
|
@@ -66,17 +79,28 @@ fastlane add_plugin appcircle_testing_distribution
|
|
66
79
|
|
67
80
|
```yml
|
68
81
|
appcircle_testing_distribution(
|
69
|
-
|
70
|
-
|
82
|
+
personalAPIToken: "$(AC_PERSONAL_API_TOKEN)",
|
83
|
+
profileName: "$(AC_PROFILE_NAME)",
|
84
|
+
createProfileIfNotExists: Boolean,
|
71
85
|
appPath: "$(AC_APP_PATH)",
|
72
86
|
message: "$(AC_MESSAGE)",
|
73
87
|
)
|
74
88
|
```
|
75
89
|
|
90
|
+
- `personalAPIToken`: The Appcircle Personal API token is utilized to authenticate and secure access to Appcircle services, ensuring that only authorized users can perform actions within the platform.
|
91
|
+
- `profileName`: Specifies the profile that will be used for uploading the app.
|
92
|
+
- `createProfileIfNotExists`: Ensures that a user profile is automatically created if it does not already exist; if the profile name already exists, the app will be uploaded to that existing profile instead.
|
93
|
+
- `appPath`: Indicates the file path to the application that will be uploaded to Appcircle Testing Distribution Profile.
|
94
|
+
- `message`: Your message to testers, ensuring they receive important updates and information regarding the application.
|
95
|
+
|
76
96
|
### Leveraging Environment Variables
|
77
97
|
|
78
98
|
Utilize environment variables seamlessly by substituting the parameters with `$(VARIABLE_NAME)` in your task inputs. The extension automatically retrieves values from the specified environment variables within your pipeline.
|
79
99
|
|
100
|
+
**Ensure that this action is added after build steps have been completed.**
|
101
|
+
|
102
|
+
**If multiple workflows start simultaneously, the order in which versions are shared in the Testing Distribution is determined by the execution order of the publish step. The version that completes its build and triggers the publish plugin first will be shared first, followed by the others in sequence.**
|
103
|
+
|
80
104
|
Efficiently distribute test binaries or beta versions using Appcircle, featuring seamless IPA and APK distribution capabilities. Streamline your testing process with our versatile tool designed to optimize your distribution workflow. If you need support or more information, please [contact us](https://appcircle.io/contact?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
|
81
105
|
|
82
106
|
## Issues and Feedback
|
@@ -89,6 +113,6 @@ If you have trouble using plugins, check out the [Plugins Troubleshooting](https
|
|
89
113
|
|
90
114
|
## Reference
|
91
115
|
|
92
|
-
- For details on generating an Appcircle Personal
|
116
|
+
- For details on generating an Appcircle Personal API Token, visit [Generating/Managing Personal API Tokens](https://docs.appcircle.io/appcircle-api/api-authentication#generatingmanaging-the-personal-api-tokens?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
|
93
117
|
|
94
118
|
- To create or learn more about Appcircle testing and distribution profiles, please refer to [Creating or Selecting a Distribution Profile](https://docs.appcircle.io/distribute/create-or-select-a-distribution-profile?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
|
@@ -2,21 +2,32 @@ require 'fastlane/action'
|
|
2
2
|
require 'net/http'
|
3
3
|
require 'uri'
|
4
4
|
require 'json'
|
5
|
+
|
5
6
|
require_relative '../helper/appcircle_testing_distribution_helper'
|
7
|
+
require_relative '../helper/TDAuthService'
|
8
|
+
require_relative '../helper/TDUploadService'
|
6
9
|
|
7
10
|
module Fastlane
|
8
11
|
module Actions
|
9
12
|
class AppcircleTestingDistributionAction < Action
|
10
13
|
def self.run(params)
|
11
|
-
|
12
|
-
|
14
|
+
personalAPIToken = params[:personalAPIToken]
|
15
|
+
profileName = params[:profileName]
|
13
16
|
appPath = params[:appPath]
|
14
17
|
message = params[:message]
|
18
|
+
createProfileIfNotExists = params[:createProfileIfNotExists]
|
19
|
+
|
20
|
+
valid_extensions = ['.apk', '.aab', '.ipa', '.zip']
|
21
|
+
|
22
|
+
file_extension = File.extname(appPath).downcase
|
23
|
+
unless valid_extensions.include?(file_extension)
|
24
|
+
raise "Invalid file extension: #{file_extension}. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive)."
|
25
|
+
end
|
15
26
|
|
16
|
-
if
|
17
|
-
raise UI.error("
|
18
|
-
elsif
|
19
|
-
raise UI.error("Distribution profile
|
27
|
+
if personalAPIToken.nil?
|
28
|
+
raise UI.error("Personal API Token is required to authenticate connections to Appcircle services. Please provide a valid access token")
|
29
|
+
elsif profileName.nil?
|
30
|
+
raise UI.error("Distribution profile name is required to distribute applications. Please provide a distribution profile name")
|
20
31
|
elsif appPath.nil?
|
21
32
|
raise UI.error("Application file path is required to distribute applications. Please provide a valid application file path")
|
22
33
|
elsif message.nil?
|
@@ -24,32 +35,33 @@ module Fastlane
|
|
24
35
|
end
|
25
36
|
|
26
37
|
|
27
|
-
self.ac_login(
|
28
|
-
|
38
|
+
authToken = self.ac_login(personalAPIToken)
|
39
|
+
|
40
|
+
profileId = TDUploadService.get_profile_id(authToken, profileName, createProfileIfNotExists)
|
41
|
+
self.ac_upload(authToken, appPath, profileId, message)
|
29
42
|
end
|
30
43
|
|
31
|
-
def self.ac_login(
|
32
|
-
|
33
|
-
|
34
|
-
UI.success("
|
35
|
-
|
36
|
-
|
44
|
+
def self.ac_login(personalAPIToken)
|
45
|
+
begin
|
46
|
+
user = TDAuthService.get_ac_token(pat: personalAPIToken)
|
47
|
+
UI.success("Login is successful.")
|
48
|
+
return user.accessToken
|
49
|
+
rescue => e
|
50
|
+
puts "Login failed: #{e.message}"
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
40
|
-
def self.checkTaskStatus(taskId)
|
54
|
+
def self.checkTaskStatus(authToken, taskId)
|
41
55
|
uri = URI.parse("https://api.appcircle.io/task/v1/tasks/#{taskId}")
|
42
56
|
timeout = 1
|
43
|
-
jwtToken = `appcircle config get AC_ACCESS_TOKEN -o json`
|
44
|
-
apiAccessToken = JSON.parse(jwtToken)
|
45
57
|
|
46
|
-
response = self.send_request(uri,
|
58
|
+
response = self.send_request(uri, authToken)
|
47
59
|
if response.is_a?(Net::HTTPSuccess)
|
48
60
|
stateValue = JSON.parse(response.body)["stateValue"]
|
49
61
|
|
50
62
|
if stateValue == 1
|
51
63
|
sleep(1)
|
52
|
-
return checkTaskStatus(taskId)
|
64
|
+
return checkTaskStatus(authToken, taskId)
|
53
65
|
end
|
54
66
|
if stateValue == 3
|
55
67
|
return true
|
@@ -58,8 +70,7 @@ module Fastlane
|
|
58
70
|
raise "Upload could not completed successfully"
|
59
71
|
end
|
60
72
|
else
|
61
|
-
|
62
|
-
raise "Request failed"
|
73
|
+
raise "Upload failed with response code #{response.code} and message '#{response.message}'"
|
63
74
|
end
|
64
75
|
end
|
65
76
|
|
@@ -71,16 +82,16 @@ module Fastlane
|
|
71
82
|
http.request(request)
|
72
83
|
end
|
73
84
|
|
74
|
-
def self.ac_upload(appPath, profileID, message)
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
result = self.checkTaskStatus(taskId)
|
85
|
+
def self.ac_upload(token, appPath, profileID, message)
|
86
|
+
begin
|
87
|
+
response = TDUploadService.upload_artifact(token: token, message: message, app: appPath, dist_profile_id: profileID)
|
88
|
+
result = self.checkTaskStatus(token, response['taskId'])
|
79
89
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
90
|
+
if $?.success? and result
|
91
|
+
UI.success("#{appPath} Uploaded to profile id #{profileID} successfully 🎉")
|
92
|
+
end
|
93
|
+
rescue => e
|
94
|
+
UI.error("Upload failed with status code #{e.response.code}, with message '#{e.message}'")
|
84
95
|
end
|
85
96
|
end
|
86
97
|
|
@@ -103,17 +114,23 @@ module Fastlane
|
|
103
114
|
|
104
115
|
def self.available_options
|
105
116
|
[
|
106
|
-
FastlaneCore::ConfigItem.new(key: :
|
107
|
-
env_name: "
|
108
|
-
description: "Provide
|
117
|
+
FastlaneCore::ConfigItem.new(key: :personalAPIToken,
|
118
|
+
env_name: "AC_PERSONAL_API_TOKEN",
|
119
|
+
description: "Provide Personal API Token to authenticate connections to Appcircle services",
|
109
120
|
optional: false,
|
110
121
|
type: String),
|
111
|
-
|
112
|
-
FastlaneCore::ConfigItem.new(key: :
|
113
|
-
env_name: "
|
114
|
-
description: "Enter the
|
122
|
+
|
123
|
+
FastlaneCore::ConfigItem.new(key: :profileName,
|
124
|
+
env_name: "AC_PROFILE_NAME",
|
125
|
+
description: "Enter the profile name of the Appcircle distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
|
115
126
|
optional: false,
|
116
127
|
type: String),
|
128
|
+
|
129
|
+
FastlaneCore::ConfigItem.new(key: :createProfileIfNotExists,
|
130
|
+
env_name: "AC_CREATE_PROFILE_IF_NOT_EXISTS",
|
131
|
+
description: "If the profile does not exist, create a new profile with the given name",
|
132
|
+
optional: true,
|
133
|
+
type: Boolean),
|
117
134
|
|
118
135
|
FastlaneCore::ConfigItem.new(key: :appPath,
|
119
136
|
env_name: "AC_APP_PATH",
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'cgi'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
|
7
|
+
class UserResponse
|
8
|
+
attr_accessor :accessToken
|
9
|
+
|
10
|
+
def initialize(accessToken:)
|
11
|
+
@accessToken = accessToken
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module TDAuthService
|
16
|
+
def self.get_ac_token(pat:)
|
17
|
+
endpoint_url = 'https://auth.appcircle.io/auth/v2/token'
|
18
|
+
uri = URI(endpoint_url)
|
19
|
+
|
20
|
+
# Create HTTP request
|
21
|
+
request = Net::HTTP::Post.new(uri)
|
22
|
+
request.content_type = 'application/x-www-form-urlencoded'
|
23
|
+
request['Accept'] = 'application/json'
|
24
|
+
|
25
|
+
# Encode parameters
|
26
|
+
params = { pat: pat }
|
27
|
+
request.body = URI.encode_www_form(params)
|
28
|
+
|
29
|
+
# Make the HTTP request
|
30
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
31
|
+
http.request(request)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
# Check response
|
37
|
+
if response.is_a?(Net::HTTPSuccess)
|
38
|
+
response_data = JSON.parse(response.body)
|
39
|
+
|
40
|
+
user = UserResponse.new(
|
41
|
+
accessToken: response_data['access_token']
|
42
|
+
)
|
43
|
+
|
44
|
+
return user
|
45
|
+
else
|
46
|
+
raise "HTTP Request failed (#{response.code} #{response.message})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
require 'rest-client'
|
5
|
+
|
6
|
+
BASE_URL = "https://api.appcircle.io"
|
7
|
+
|
8
|
+
module TDUploadService
|
9
|
+
def self.upload_artifact(token:, message:, app:, dist_profile_id:)
|
10
|
+
url = "https://api.appcircle.io/distribution/v2/profiles/#{dist_profile_id}/app-versions"
|
11
|
+
headers = {
|
12
|
+
Authorization: "Bearer #{token}",
|
13
|
+
Message: message,
|
14
|
+
content_type: :multipart
|
15
|
+
}
|
16
|
+
payload = {
|
17
|
+
Message: message,
|
18
|
+
File: File.new(app, 'rb')
|
19
|
+
}
|
20
|
+
|
21
|
+
begin
|
22
|
+
response = RestClient.post(url, payload, headers)
|
23
|
+
JSON.parse(response.body) rescue response.body
|
24
|
+
rescue RestClient::ExceptionWithResponse => e
|
25
|
+
raise e
|
26
|
+
rescue StandardError => e
|
27
|
+
raise e
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.get_distribution_profiles(auth_token:)
|
32
|
+
url = "#{BASE_URL}/distribution/v2/profiles"
|
33
|
+
|
34
|
+
# Set up the headers with authentication
|
35
|
+
headers = {
|
36
|
+
Authorization: "Bearer #{auth_token}",
|
37
|
+
accept: 'application/json'
|
38
|
+
}
|
39
|
+
|
40
|
+
begin
|
41
|
+
response = RestClient.get(url, headers)
|
42
|
+
parsed_response = JSON.parse(response.body)
|
43
|
+
|
44
|
+
parsed_response
|
45
|
+
rescue RestClient::ExceptionWithResponse => e
|
46
|
+
raise e
|
47
|
+
rescue StandardError => e
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.create_distribution_profile(name:, auth_token:)
|
53
|
+
url = "#{BASE_URL}/distribution/v2/profiles"
|
54
|
+
headers = {
|
55
|
+
Authorization: "Bearer #{auth_token}",
|
56
|
+
content_type: :json,
|
57
|
+
accept: 'application/json'
|
58
|
+
}
|
59
|
+
payload = {
|
60
|
+
name: name
|
61
|
+
}.to_json
|
62
|
+
|
63
|
+
begin
|
64
|
+
response = RestClient.post(url, payload, headers)
|
65
|
+
JSON.parse(response.body)
|
66
|
+
rescue RestClient::ExceptionWithResponse => e
|
67
|
+
raise e
|
68
|
+
rescue StandardError => e
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.get_profile_id(authToken, profileName, createProfileIfNotExists)
|
74
|
+
profileId = nil
|
75
|
+
|
76
|
+
begin
|
77
|
+
profiles = TDUploadService.get_distribution_profiles(auth_token: authToken)
|
78
|
+
profiles.each do |profile|
|
79
|
+
if profile["name"] == profileName
|
80
|
+
profileId = profile['id']
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue => e
|
84
|
+
raise "Something went wrong while fetching profiles: #{e.message}"
|
85
|
+
end
|
86
|
+
|
87
|
+
if profileId.nil? && !createProfileIfNotExists
|
88
|
+
raise "Error: The test profile '#{profileName}' could not be found. The option 'createProfileIfNotExists' is set to false, so no new profile was created. To automatically create a new profile if it doesn't exist, set 'createProfileIfNotExists' to true."
|
89
|
+
end
|
90
|
+
|
91
|
+
if profileId.nil? && createProfileIfNotExists
|
92
|
+
begin
|
93
|
+
puts "The test profile '#{profileName}' could not be found. A new profile is being created..."
|
94
|
+
new_profile = TDUploadService.create_distribution_profile(name: profileName, auth_token: authToken)
|
95
|
+
if new_profile.nil?
|
96
|
+
raise "Error: The new profile could not be created."
|
97
|
+
end
|
98
|
+
profileId = new_profile['id']
|
99
|
+
rescue => e
|
100
|
+
raise "Something went wrong while creating a new profile: #{e.message}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
return profileId
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-appcircle_testing_distribution
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- appcircleio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-09-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
description:
|
14
28
|
email: cloud@appcircle.io
|
15
29
|
executables: []
|
@@ -23,6 +37,8 @@ files:
|
|
23
37
|
- images/extension-icon.png
|
24
38
|
- lib/fastlane/plugin/appcircle_testing_distribution.rb
|
25
39
|
- lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb
|
40
|
+
- lib/fastlane/plugin/appcircle_testing_distribution/helper/TDAuthService.rb
|
41
|
+
- lib/fastlane/plugin/appcircle_testing_distribution/helper/TDUploadService.rb
|
26
42
|
- lib/fastlane/plugin/appcircle_testing_distribution/helper/appcircle_testing_distribution_helper.rb
|
27
43
|
- lib/fastlane/plugin/appcircle_testing_distribution/version.rb
|
28
44
|
homepage: https://github.com/appcircleio/fastlane_plugin_appcircle_testing_distribution
|