fastlane-plugin-appcircle_testing_distribution 0.2.2 → 0.4.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: 7b0f60660f2ea2b25bf7e0088c9e2a1dd70d009b6d0b63bc1d508bb6721d377e
4
- data.tar.gz: '0586725d636a2e56a87fc604f6a6c1fb284e22d04d771ba5e85d79a6e341b169'
3
+ metadata.gz: e01adda887a2e03213c82a2f1dd4c73e068599c6f2a72107663bc40b0246259d
4
+ data.tar.gz: 551d74f7d9d2ba2f9683fbc20c69c7e06c8897c7a711d98a6d636ce50fab7d82
5
5
  SHA512:
6
- metadata.gz: 7cab5c11069fb4c70abcfdc0ec747670ec043f9efaf9f0635dc7f896fd6a3188ae80ecc68eabc9d401bd4d789ee02c535a32a826569423cfd8d3643462f1dcd6
7
- data.tar.gz: 173f6f699f45551e550b98a2585ea72c8d14bb8c1023b66cfa890d573584379b3e73521f1bcfcec51a7d1fa4e2541f45c94b3a491a28efc7f696f10d74344dad
6
+ metadata.gz: d77f4bf600f1281ac845c37c5a37cbe76c2ffffa3630f6d27d12e793912c80c01197ec746c2ef8d15f2c4c7a75e2798faeae10bad0485bb3e1ee5d65fbd5c472
7
+ data.tar.gz: fa09b51a8528c0693bf8a4371b29fd3ba1a87e42185069599744f28d0d06eab8430f8f38af5198fce624b9483ca0e20b6ad51df9f8cdc1565dcec150c7015ff1
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2024 Guven Karanfil <guven.karanfil@smartface.io>
3
+ Copyright (c) 2024 Appcircle, Inc. <info@appcircle.io>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ## Appcircle Testing Distribution
1
+ # Appcircle Testing Distribution
2
2
 
3
3
  [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-appcircle_testing_distribution)
4
4
 
@@ -38,24 +38,11 @@ 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
41
+ <!-- ## Testing Distribution
42
42
 
43
- **Compatible Agents:**
43
+ In order to share your builds with testers, you can create testing distribution profiles and assign testing groups to the profiles.
44
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
-
54
- ## Testing Distribution
55
-
56
- In order to share your builds with testers, you can create distribution profiles and assign testing groups to the distribution profiles.
57
-
58
- ![Distribution Profile](<https://cdn.appcircle.io/docs/assets/image%20(152).png>)
45
+ ![Testing Distribution Profile](<https://cdn.appcircle.io/docs/assets/image%20(152).png>)
59
46
 
60
47
  ## Generating/Managing the Personal API Tokens
61
48
 
@@ -65,11 +52,11 @@ To generate a Personal API Token, follow these steps:
65
52
  2. You'll find the Personal API Token section in the top right corner.
66
53
  3. Press the "Generate Token" button to generate your first token.
67
54
 
68
- ![Token Generation](<https://cdn.appcircle.io/docs/assets/image%20(164).png>)
55
+ ![Token Generation](<https://cdn.appcircle.io/docs/assets/image%20(164).png>) -->
69
56
 
70
57
  ## Getting Started with the Extension: Usage Guide
71
58
 
72
- To share your builds with testers, you can create distribution profiles and assign testing groups to these profiles.
59
+ To share your builds with testers, you can create testing distribution profiles and assign testing groups to these profiles.
73
60
 
74
61
  This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-appcircle_testing_distribution`, add it to your project by running:
75
62
 
@@ -77,31 +64,48 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To
77
64
  fastlane add_plugin appcircle_testing_distribution
78
65
  ```
79
66
 
80
- ```yml
67
+ ```ruby
81
68
  appcircle_testing_distribution(
82
- personalAPIToken: "$(AC_PERSONAL_API_TOKEN)",
83
- profileName: "$(AC_PROFILE_NAME)",
84
- createProfileIfNotExists: Boolean,
85
- appPath: "$(AC_APP_PATH)",
86
- message: "$(AC_MESSAGE)",
69
+ personalAPIToken: ENV["AC_PERSONAL_API_TOKEN"],
70
+ subOrganizationName: ENV["AC_SUB_ORGANIZATION_NAME"],
71
+ profileName: ENV["AC_PROFILE_NAME"],
72
+ createProfileIfNotExists: ENV["AC_CREATE_PROFILE_IF_NOT_EXISTS"],
73
+ profileCreationSettings: {
74
+ authType: ENV["AC_PROFILE_AUTH_TYPE"],
75
+ username: ENV["AC_PROFILE_USERNAME"],
76
+ password: ENV["AC_PROFILE_PASSWORD"],
77
+ testingGroupNames: ENV["AC_PROFILE_TESTING_GROUP_NAMES"]
78
+ },
79
+ appPath: ENV["AC_APP_PATH"],
80
+ message: ENV["AC_MESSAGE"]
87
81
  )
88
82
  ```
89
83
 
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.
84
+ - `personalAPIToken`: The Appcircle Personal API token used to authenticate and authorize access to Appcircle services within this plugin.
85
+ - `subOrganizationName` (optional): Required when the Root Organization's `personalAPIToken` is used, and you want to create the profile under a sub-organization. In this case, provide the name of the sub-organization in this field. If you directly used the sub-organization's `personalAPIToken`, this parameter is not needed.
91
86
  - `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.
87
+ - `createProfileIfNotExists` (optional): Ensures that a testing distribution 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.
88
+ - `profileCreationSettings` (optional): If `createProfileIfNotExists` is `true` and a new profile being created, the profile will be configured with these settings.
89
+ - `authType`: Authentication type of the profile. `none`: None, `static`: Static Username and Password, `ldap`: LDAP Login, `sso`: SSO Login.
90
+ - `username`: The username for the profile if authentication type set to `static` (Static Username and Password).
91
+ - `password`: The password for the profile if authentication type set to `static` (Static Username and Password).
92
+ - `testingGroupNames`: Uploaded versions will be automatically shared with these testing groups. Example format: `group1, group2, group3`.
93
+ - `appPath`: Indicates the file path to the application package that will be uploaded to Appcircle Testing Distribution Profile.
94
94
  - `message`: Your message to testers, ensuring they receive important updates and information regarding the application.
95
95
 
96
- ### Leveraging Environment Variables
96
+ ## Further Details
97
97
 
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.
98
+ For more information please refer to the documentation.
99
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
-
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)
100
+ - [Setting Up Appcircle Testing Distribution Plugin](https://docs.appcircle.io/marketplace/fastlane/testing-distribution)
101
+ - [Discover Action](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#discover-action)
102
+ - [System Requirements](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#system-requirements)
103
+ - [User Permission Requirements](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#user-permission-requirements)
104
+ - [How to Add the Appcircle Distribute Action to Your Pipeline](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#how-to-add-the-appcircle-distribute-action-to-your-pipeline)
105
+ - [CLI Usage](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#cli-usage)
106
+ - [Distributing to Sub-Organizations](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#distributing-to-sub-organizations)
107
+ - [Leveraging Environment Variables](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#leveraging-environment-variables)
108
+ - [References](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#references)
105
109
 
106
110
  ## Issues and Feedback
107
111
 
@@ -110,9 +114,3 @@ For any other issues and feedback about this plugin, please submit it to this re
110
114
  ## Troubleshooting
111
115
 
112
116
  If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
113
-
114
- ## Reference
115
-
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)
117
-
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)
@@ -10,67 +10,127 @@ require_relative '../helper/TDUploadService'
10
10
  module Fastlane
11
11
  module Actions
12
12
  class AppcircleTestingDistributionAction < Action
13
+ VALID_EXTENSIONS = ['.apk', '.aab', '.ipa', '.zip']
14
+ AUTH_TYPE_MAPPING = {
15
+ 'none' => 1, # None
16
+ 'static' => 3, # Static Username and Password
17
+ 'ldap' => 4, # LDAP Login
18
+ 'sso' => 5 # SSO Login
19
+ }
20
+
13
21
  def self.run(params)
14
22
  personalAPIToken = params[:personalAPIToken]
23
+ subOrganizationName = params[:subOrganizationName]
15
24
  profileName = params[:profileName]
25
+ createProfileIfNotExists = params[:createProfileIfNotExists] || false
26
+ #
27
+ profileCreationSettings = params[:profileCreationSettings]
28
+ profileAuthType = profileCreationSettings&.dig(:authType)
29
+ profileUsername = profileCreationSettings&.dig(:username)
30
+ profilePassword = profileCreationSettings&.dig(:password)
31
+ profileTestingGroupNames= profileCreationSettings&.dig(:testingGroupNames)
32
+ #
16
33
  appPath = params[:appPath]
17
34
  message = params[:message]
18
- createProfileIfNotExists = params[:createProfileIfNotExists]
35
+
36
+ profileAuthType = AUTH_TYPE_MAPPING[profileAuthType] # map input to API values
19
37
 
20
- valid_extensions = ['.apk', '.aab', '.ipa', '.zip']
38
+ # Auth
39
+ authToken = self.ac_login(personalAPIToken, subOrganizationName)
21
40
 
22
- file_extension = File.extname(appPath).downcase
23
- unless valid_extensions.include?(file_extension)
24
- UI.user_error!("Invalid file extension: #{file_extension}. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive).")
25
- end
41
+ # Get or create profile
42
+ profileId = self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
43
+
44
+ # Upload package
45
+ self.ac_upload(authToken, appPath, profileId, profileName, message)
46
+ end
47
+
48
+ def self.ac_login(personalAPIToken, subOrganizationName)
49
+ begin
50
+ token = ''
51
+
52
+ user = TDAuthService.get_ac_token(pat: personalAPIToken)
53
+ UI.success("Login is successful.")
54
+ token = user.accessToken
55
+
56
+ if subOrganizationName
57
+ organization_id = TDAuthService.get_organization_id(access_token: token, name: subOrganizationName)
58
+ user = TDAuthService.get_ac_token(pat: personalAPIToken, sub_organization_id: organization_id)
59
+ UI.message("Switched to sub-organization: #{subOrganizationName}")
60
+ token = user.accessToken
61
+ end
62
+
63
+ return token
26
64
 
27
- if personalAPIToken.nil?
28
- UI.user_error!("Personal API Token is required to authenticate connections to Appcircle services. Please provide a valid access token")
29
- elsif profileName.nil?
30
- UI.user_error!("Distribution profile name is required to distribute applications. Please provide a distribution profile name")
31
- elsif appPath.nil?
32
- UI.user_error!("Application file path is required to distribute applications. Please provide a valid application file path")
33
- elsif message.nil?
34
- UI.user_error!("Message field is required. Please provide a valid message")
65
+ rescue => e
66
+ UI.user_error!("Login failed: \"#{e.message}\".")
35
67
  end
68
+ end
69
+
70
+ def self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
71
+ begin
72
+ profileId = TDUploadService.get_profile_id(authToken, profileName)
36
73
 
74
+ if profileId
75
+ UI.message("Profile '#{profileName}' found with ID: #{profileId}.")
76
+ UI.important("Warning: Profile '#{profileName}' already exists, so the provided profile creation settings will be ignored. To update the profile settings, please use the Appcircle web interface.") if profileCreationSettings
37
77
 
38
- authToken = self.ac_login(personalAPIToken)
78
+ elsif profileId.nil? && !createProfileIfNotExists
79
+ UI.user_error!("Error: Profile '#{profileName}' not found. The option 'createProfileIfNotExists' is set to false, so a new profile was not created. To automatically create a new profile when it doesn't exist, set 'createProfileIfNotExists' to true.")
80
+ elsif profileId.nil? && createProfileIfNotExists
81
+ UI.message("Profile '#{profileName}' not found. Creating the new profile...")
82
+ profileId = TDUploadService.create_profile(authToken, profileName, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
83
+ end
39
84
 
40
- profileId = TDUploadService.get_profile_id(authToken, profileName, createProfileIfNotExists)
41
- self.ac_upload(authToken, appPath, profileId, message)
85
+ return profileId
86
+
87
+ rescue => e
88
+ UI.user_error!("Couldn't get the profile: \"#{e.message}\".")
89
+ end
42
90
  end
43
91
 
44
- def self.ac_login(personalAPIToken)
92
+ def self.ac_upload(token, appPath, profileID, profileName, message)
45
93
  begin
46
- user = TDAuthService.get_ac_token(pat: personalAPIToken)
47
- UI.success("Login is successful.")
48
- return user.accessToken
94
+ UI.message("Upload started.")
95
+ response = TDUploadService.upload_artifact(token: token, message: message, app: appPath, dist_profile_id: profileID)
96
+ result = self.checkTaskStatus(token, response['taskId'])
97
+
98
+ if result
99
+ UI.success("#{appPath} uploaded to profile '#{profileName}' successfully 🎉")
100
+ end
49
101
  rescue => e
50
- UI.user_error!("Login failed: #{e.message}")
102
+ status_code = e.respond_to?(:response) && e.response ? e.response.code : 'unknown'
103
+ UI.user_error!("Upload failed with status code '#{status_code}', with message \"#{e.message}\".")
51
104
  end
52
105
  end
53
-
106
+
54
107
  def self.checkTaskStatus(authToken, taskId)
55
108
  uri = URI.parse("https://api.appcircle.io/task/v1/tasks/#{taskId}")
56
- timeout = 1
57
109
 
58
- response = self.send_request(uri, authToken)
59
- if response.is_a?(Net::HTTPSuccess)
60
- stateValue = JSON.parse(response.body)["stateValue"]
61
-
62
- if stateValue == 1
63
- sleep(1)
64
- return checkTaskStatus(authToken, taskId)
65
- end
66
- if stateValue == 3
67
- return true
110
+ check_interval = 1
111
+ # timeout = 2 * 60 * 60 # 2 hours in seconds
112
+ # start_time = Time.now
113
+
114
+ loop do
115
+ response = self.send_request(uri, authToken)
116
+ if response.is_a?(Net::HTTPSuccess)
117
+ stateValue = JSON.parse(response.body)["stateValue"]
118
+
119
+ if stateValue == 1
120
+ sleep(check_interval)
121
+ elsif stateValue == 3
122
+ return true
123
+ else
124
+ UI.error("Task Id #{taskId} failed with state value #{stateValue}.")
125
+ UI.user_error!("Upload could not be completed successfully.")
126
+ end
68
127
  else
69
- UI.error("Task Id #{taskId} failed with state value #{stateValue}")
70
- raise "Upload could not completed successfully"
128
+ UI.user_error!("Upload failed with response code #{response.code} and message '#{response.message}'.")
71
129
  end
72
- else
73
- raise "Upload failed with response code #{response.code} and message '#{response.message}'"
130
+
131
+ # if Time.now - start_time > timeout
132
+ # UI.user_error!("Task Id #{taskId} timed out after 2 hours.")
133
+ # end
74
134
  end
75
135
  end
76
136
 
@@ -82,20 +142,6 @@ module Fastlane
82
142
  http.request(request)
83
143
  end
84
144
 
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'])
89
-
90
- if result
91
- UI.success("#{appPath} Uploaded to profile id #{profileID} successfully 🎉")
92
- end
93
- rescue => e
94
- status_code = e.respond_to?(:response) && e.response ? e.response.code : 'unknown'
95
- UI.user_error!("Upload failed with status code #{status_code}, with message '#{e.message}'")
96
- end
97
- end
98
-
99
145
  def self.description
100
146
  "Efficiently distribute application builds to users or testing groups using Appcircle's robust platform."
101
147
  end
@@ -110,40 +156,81 @@ module Fastlane
110
156
 
111
157
  def self.details
112
158
  # Optional:
113
- "Appcircle simplifies the distribution of builds to test teams with an extensive platform for managing and tracking applications, versions, testers, and teams. Appcircle integrates with enterprise authentication mechanisms such as LDAP and SSO, ensuring secure distribution of testing packages. Learn more about Appcircle testing distribution"
159
+ "Appcircle simplifies the distribution of builds to test teams with an extensive platform for managing and tracking applications, versions, testers, and teams. Appcircle integrates with enterprise authentication mechanisms such as LDAP and SSO, ensuring secure distribution of testing packages. Learn more about Appcircle testing distribution."
114
160
  end
115
161
 
116
162
  def self.available_options
117
163
  [
118
164
  FastlaneCore::ConfigItem.new(key: :personalAPIToken,
119
- env_name: "AC_PERSONAL_API_TOKEN",
120
165
  description: "Provide Personal API Token to authenticate connections to Appcircle services",
121
166
  optional: false,
167
+ type: String,
168
+ verify_block: proc do |value|
169
+ UI.user_error!("Personal API Token cannot be empty. Please provide a valid access token.") unless value && !value.empty?
170
+ end),
171
+
172
+ FastlaneCore::ConfigItem.new(key: :subOrganizationName,
173
+ description: "Optional: Sub-organization name for app distribution. Profiles will be created under root organization if not provided",
174
+ optional: true,
122
175
  type: String),
123
-
176
+
124
177
  FastlaneCore::ConfigItem.new(key: :profileName,
125
- env_name: "AC_PROFILE_NAME",
126
- description: "Enter the profile name of the Appcircle distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
178
+ description: "Enter the profile name of the Appcircle testing distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
127
179
  optional: false,
128
- type: String),
129
-
180
+ type: String,
181
+ verify_block: proc do |value|
182
+ UI.user_error!("Profile name cannot be empty. Please provide a testing distribution profile name.") unless value && !value.empty?
183
+ end),
184
+
130
185
  FastlaneCore::ConfigItem.new(key: :createProfileIfNotExists,
131
- env_name: "AC_CREATE_PROFILE_IF_NOT_EXISTS",
132
- description: "If the profile does not exist, create a new profile with the given name",
186
+ description: "Optional: If the profile does not exist, create a new profile with the given name",
133
187
  optional: true,
134
188
  type: Boolean),
135
189
 
190
+ FastlaneCore::ConfigItem.new(key: :profileCreationSettings,
191
+ description: "Optional: Profile creation settings for the testing distribution profile",
192
+ optional: true,
193
+ type: Hash,
194
+ verify_block: proc do |value|
195
+ # Parse and Validate
196
+ if value[:authType] && !value[:authType].empty?
197
+ UI.user_error!("Invalid authType: '#{value[:authType]}'. Options: 'none' (None), 'static' (Static Username and Password), 'ldap' (LDAP Login), 'sso' (SSO Login).") unless AUTH_TYPE_MAPPING.key?(value[:authType])
198
+
199
+ if value[:authType] == 'static'
200
+ UI.user_error!("username must be a String and at least 6 characters long.") unless value[:username].kind_of?(String) && value[:username].length >= 6
201
+ UI.user_error!("password must be a String and at least 6 characters long.") unless value[:password].kind_of?(String) && value[:password].length >= 6
202
+ else
203
+ value[:username] = nil
204
+ value[:password] = nil
205
+ end
206
+ end
207
+
208
+ if value[:testingGroupNames] && !value[:testingGroupNames].empty?
209
+ value[:testingGroupNames] = value[:testingGroupNames].to_s.split(",").map(&:strip)
210
+ UI.user_error!("testingGroupNames must be a string array. Ex: 'group1, group2, group3'.") unless value[:testingGroupNames].kind_of?(Array)
211
+ end
212
+ end),
213
+
136
214
  FastlaneCore::ConfigItem.new(key: :appPath,
137
- env_name: "AC_APP_PATH",
138
215
  description: "Specify the path to your application file. For iOS, this can be a .ipa or .xcarchive file path. For Android, specify the .apk or .appbundle file path",
139
216
  optional: false,
140
- type: String),
217
+ type: String,
218
+ verify_block: proc do |value|
219
+ UI.user_error!("Application file path cannot be empty. Please provide a valid application file path.") unless value && !value.empty?
220
+
221
+ file_extension = File.extname(value).downcase
222
+ unless VALID_EXTENSIONS.include?(file_extension)
223
+ UI.user_error!("Invalid file extension: '#{file_extension}'. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive).")
224
+ end
225
+ end),
141
226
 
142
227
  FastlaneCore::ConfigItem.new(key: :message,
143
- env_name: "AC_MESSAGE",
144
- description: "Optional message to include with the distribution to provide additional information to testers or users receiving the build",
228
+ description: "Message to include with the distribution to provide additional information to testers or users receiving the build",
145
229
  optional: false,
146
- type: String)
230
+ type: String,
231
+ verify_block: proc do |value|
232
+ UI.user_error!("Message field cannot be empty. Please provide a message.") unless value && !value.empty?
233
+ end)
147
234
  ]
148
235
  end
149
236
 
@@ -13,7 +13,7 @@ class UserResponse
13
13
  end
14
14
 
15
15
  module TDAuthService
16
- def self.get_ac_token(pat:)
16
+ def self.get_ac_token(pat:, sub_organization_id: nil)
17
17
  endpoint_url = 'https://auth.appcircle.io/auth/v2/token'
18
18
  uri = URI(endpoint_url)
19
19
 
@@ -24,6 +24,7 @@ module TDAuthService
24
24
 
25
25
  # Encode parameters
26
26
  params = { pat: pat }
27
+ params[:subOrganizationId] = sub_organization_id if sub_organization_id
27
28
  request.body = URI.encode_www_form(params)
28
29
 
29
30
  # Make the HTTP request
@@ -31,8 +32,6 @@ module TDAuthService
31
32
  http.request(request)
32
33
  end
33
34
 
34
-
35
-
36
35
  # Check response
37
36
  if response.is_a?(Net::HTTPSuccess)
38
37
  response_data = JSON.parse(response.body)
@@ -43,7 +42,35 @@ module TDAuthService
43
42
 
44
43
  return user
45
44
  else
46
- raise "HTTP Request failed (#{response.code} #{response.message})"
45
+ raise "Error: (#{response.code} #{response.message})."
46
+ end
47
+ end
48
+
49
+ def self.get_organization_id(access_token:, name:)
50
+ endpoint_url = 'https://api.appcircle.io/identity/v1/organizations'
51
+ uri = URI(endpoint_url)
52
+
53
+ # Create HTTP request
54
+ request = Net::HTTP::Get.new(uri)
55
+ request['Authorization'] = "Bearer #{access_token}"
56
+ request['Accept'] = 'application/json'
57
+
58
+ # Make the HTTP request
59
+ response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
60
+ http.request(request)
61
+ end
62
+
63
+ # Check response
64
+ if response.is_a?(Net::HTTPSuccess)
65
+ response_data = JSON.parse(response.body)
66
+ organizations = response_data['data']
67
+ organization = organizations.find { |org| org['name'] == name }
68
+
69
+ raise "Organization with name '#{name}' not found" unless organization
70
+ return organization['id']
71
+
72
+ else
73
+ raise "Error: (#{response.code} #{response.message})"
47
74
  end
48
75
  end
49
76
  end
@@ -9,12 +9,12 @@ module TDUploadService
9
9
  def self.upload_artifact(token:, message:, app:, dist_profile_id:)
10
10
  url = "https://api.appcircle.io/distribution/v2/profiles/#{dist_profile_id}/app-versions"
11
11
  headers = {
12
- Authorization: "Bearer #{token}",
13
- content_type: :multipart # multipart/form-data
12
+ Authorization: "Bearer #{token}"
14
13
  }
15
14
  payload = {
16
15
  Message: message,
17
- File: File.new(app, 'rb')
16
+ File: File.new(app, 'rb'),
17
+ multipart: true # Force multipart encoding for RestClient
18
18
  }
19
19
 
20
20
  begin
@@ -38,9 +38,26 @@ module TDUploadService
38
38
 
39
39
  begin
40
40
  response = RestClient.get(url, headers)
41
- parsed_response = JSON.parse(response.body)
41
+ JSON.parse(response.body)
42
+ rescue RestClient::ExceptionWithResponse => e
43
+ raise e
44
+ rescue StandardError => e
45
+ raise e
46
+ end
47
+ end
48
+
49
+ def self.get_testing_groups(auth_token:)
50
+ url = "#{BASE_URL}/distribution/v2/testing-groups"
42
51
 
43
- parsed_response
52
+ # Set up the headers with authentication
53
+ headers = {
54
+ Authorization: "Bearer #{auth_token}",
55
+ accept: 'application/json'
56
+ }
57
+
58
+ begin
59
+ response = RestClient.get(url, headers)
60
+ JSON.parse(response.body)
44
61
  rescue RestClient::ExceptionWithResponse => e
45
62
  raise e
46
63
  rescue StandardError => e
@@ -69,7 +86,40 @@ module TDUploadService
69
86
  end
70
87
  end
71
88
 
72
- def self.get_profile_id(authToken, profileName, createProfileIfNotExists)
89
+ def self.update_distribution_profile(profile_id:, auth_type:, username:, password:, testing_group_ids:, auth_token:)
90
+ url = "#{BASE_URL}/distribution/v2/profiles/#{profile_id}"
91
+ headers = {
92
+ Authorization: "Bearer #{auth_token}",
93
+ content_type: :json,
94
+ accept: 'application/json-patch+json'
95
+ }
96
+
97
+ ### Construct the payload
98
+ payload = {}
99
+
100
+ settings_payload = {
101
+ authenticationType: auth_type,
102
+ username: username,
103
+ password: password
104
+ }.compact
105
+
106
+ payload[:settings] = settings_payload unless settings_payload.empty?
107
+ payload[:testingGroupIds] = testing_group_ids unless testing_group_ids&.empty?
108
+
109
+ payload = payload.compact.to_json
110
+ ###
111
+
112
+ begin
113
+ response = RestClient.patch(url, payload, headers)
114
+ JSON.parse(response.body)
115
+ rescue RestClient::ExceptionWithResponse => e
116
+ raise e
117
+ rescue StandardError => e
118
+ raise e
119
+ end
120
+ end
121
+
122
+ def self.get_profile_id(authToken, profileName)
73
123
  profileId = nil
74
124
 
75
125
  begin
@@ -77,27 +127,75 @@ module TDUploadService
77
127
  profiles.each do |profile|
78
128
  if profile["name"] == profileName
79
129
  profileId = profile['id']
130
+ break
80
131
  end
81
132
  end
82
133
  rescue => e
83
- raise "Something went wrong while fetching profiles: #{e.message}"
84
- end
85
-
86
- if profileId.nil? && !createProfileIfNotExists
87
- 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."
134
+ raise "Something went wrong while fetching profiles: #{e.message}."
88
135
  end
89
136
 
90
- if profileId.nil? && createProfileIfNotExists
91
- begin
92
- puts "The test profile '#{profileName}' could not be found. A new profile is being created..."
93
- new_profile = TDUploadService.create_distribution_profile(name: profileName, auth_token: authToken)
94
- if new_profile.nil?
95
- raise "Error: The new profile could not be created."
137
+ return profileId
138
+ end
139
+
140
+ def self.get_testing_group_ids(authToken, testingGroupNames)
141
+ testingGroupIds = []
142
+ remainingGroupNames = Set.new(testingGroupNames)
143
+
144
+ begin
145
+ groups = TDUploadService.get_testing_groups(auth_token: authToken)
146
+
147
+ groups.each do |group|
148
+ if remainingGroupNames.include?(group["name"])
149
+ testingGroupIds.push(group['id'])
150
+ remainingGroupNames.delete(group["name"])
96
151
  end
97
- profileId = new_profile['id']
98
- rescue => e
99
- raise "Something went wrong while creating a new profile: #{e.message}"
100
152
  end
153
+ rescue => e
154
+ raise "Something went wrong while fetching testing groups: #{e.message}."
155
+ end
156
+
157
+ raise "Following testing groups couldn't be found: '#{remainingGroupNames.to_a.join(', ')}'. Aborting profile creation..." unless remainingGroupNames.empty?
158
+
159
+ return testingGroupIds
160
+ end
161
+
162
+ def self.create_profile(authToken, profileName, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
163
+ # Get testing group IDs
164
+ if !profileTestingGroupNames&.empty?
165
+ profileTestingGroupIds = TDUploadService.get_testing_group_ids(authToken, profileTestingGroupNames)
166
+ end
167
+
168
+ # Create
169
+ begin
170
+ new_profile = TDUploadService.create_distribution_profile(
171
+ name: profileName,
172
+ auth_token: authToken
173
+ )
174
+ if new_profile.nil?
175
+ raise "Error: The new profile could not be created."
176
+ end
177
+ profileId = new_profile['id']
178
+ rescue => e
179
+ raise "Something went wrong while creating a new profile: #{e.message}."
180
+ end
181
+
182
+ # Configure
183
+ begin
184
+ Fastlane::UI.message("Configuring the profile...")
185
+ configured_profile = TDUploadService.update_distribution_profile(
186
+ profile_id: profileId,
187
+ auth_type: profileAuthType,
188
+ username: profileUsername,
189
+ password: profilePassword,
190
+ testing_group_ids: profileTestingGroupIds,
191
+ auth_token: authToken
192
+ )
193
+ if configured_profile.nil?
194
+ raise "Error: The new profile could not be configured."
195
+ end
196
+ profileId = configured_profile['id'] # Should be the same as before
197
+ rescue => e
198
+ raise "Something went wrong while configuring the new profile: #{e.message}."
101
199
  end
102
200
 
103
201
  return profileId
@@ -8,9 +8,6 @@ module Fastlane
8
8
  # class methods that you define here become available in your action
9
9
  # as `Helper::AppcircleTestingDistributionHelper.your_method`
10
10
  #
11
- def self.show_message
12
- UI.message("Hello from the appcircle_testing_distribution plugin helper!")
13
- end
14
11
  end
15
12
  end
16
13
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AppcircleTestingDistribution
3
- VERSION = "0.2.2"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-appcircle_testing_distribution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.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-10-03 00:00:00.000000000 Z
11
+ date: 2024-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -32,8 +32,6 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - LICENSE
34
34
  - README.md
35
- - images/PAT.png
36
- - images/distribution-start.png
37
35
  - images/extension-icon.png
38
36
  - lib/fastlane/plugin/appcircle_testing_distribution.rb
39
37
  - lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb
data/images/PAT.png DELETED
Binary file
Binary file