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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a02bb68865ce3d94c903651486aa0da0dd6ce65b1a031d49f9ae65dd1f280df3
4
- data.tar.gz: 7cf9e84c39f8dac3db289e273d15b4b458baf1a3c3c8307e1fa04ae483dcda07
3
+ metadata.gz: 56a980fcdde02bf5a93e7fd18711e5048365b997e4781590aca5c95ce5a0c438
4
+ data.tar.gz: 0ccaae94dc8a36a4c93951dc63bd6ad776686b0803ccce88e385c49573453af8
5
5
  SHA512:
6
- metadata.gz: 3c220a3b09805d02df2e3cab87bb4cb89714a00a27b598cfc83a51f61436b3ee84c709c4378effbf161fed1654b2fe2250dc95fcefff34a45c60896ed2a76c0e
7
- data.tar.gz: 387fc503a7975c0f8fa3f021e4f5c466c79a8cdcc11b9c38669c932bfe7d96469bb53b6a8181c3fcb6059f160ade2612f306f74518ec407cf966dd938f814e97
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
- accessToken: "$(AC_ACCESS_TOKEN)",
70
- profileID: "$(AC_PROFILE_ID)",
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 Access 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)
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
- accessToken = params[:accessToken]
12
- profileID = params[:profileID]
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 accessToken.nil?
17
- raise UI.error("Access token is required to authenticate connections to Appcircle services. Please provide a valid access token")
18
- elsif profileID.nil?
19
- raise UI.error("Distribution profile ID is required to distribute applications. Please provide a valid distribution profile ID")
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(accessToken)
28
- self.ac_upload(appPath, profileID, message)
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(accessToken)
32
- ac_login = `appcircle login --pat #{accessToken}`
33
- if $?.success?
34
- UI.success("Logged in to Appcircle successfully.")
35
- else
36
- raise "Error executing command of logging to Appcircle. Please make sure you have installed Appcircle CLI and provided a valid access token. For more information, please visit https://docs.appcircle.io/appcircle-api/api-authentication#generatingmanaging-the-personal-api-tokens #{ac_login}"
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, apiAccessToken["AC_ACCESS_TOKEN"])
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
- UI.error("Request failed with response code #{response.code} and message #{response.message}")
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
- ac_upload = `appcircle testing-distribution upload --app=#{appPath} --distProfileId=#{profileID} --message "#{message}" -o json`
76
- taskId = JSON.parse(ac_upload)["taskId"]
77
- UI.message("taskID #{taskId}")
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
- if $?.success? and result
81
- UI.success("#{appPath} Uploaded to Appcircle successfully.")
82
- else
83
- raise "Error executing command of uploading to Appcircle. Please make sure you have provide the valid app path and distribution profile id. For more information\n" + ac_upload
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: :accessToken,
107
- env_name: "AC_ACCESS_TOKEN",
108
- description: "Provide the Appcircle access token to authenticate connections to Appcircle services",
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: :profileID,
113
- env_name: "AC_PROFILE_ID",
114
- description: "Enter the ID of the Appcircle distribution profile. This ID uniquely identifies the profile under which your applications will be distributed",
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
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AppcircleTestingDistribution
3
- VERSION = "0.1.6"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  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.1.6
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-07-22 00:00:00.000000000 Z
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