Class: Fastlane::Actions::AppcircleTestingDistributionAction

Inherits:
Action
  • Object
show all
Defined in:
lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb

Constant Summary collapse

VALID_EXTENSIONS =
['.apk', '.aab', '.ipa', '.zip']
AUTH_TYPE_MAPPING =
{
  'none' => 1, # None
  'static' => 3, # Static Username and Password
  'ldap' => 4, # LDAP Login
  'sso' => 5 # SSO Login
}

Class Method Summary collapse

Class Method Details

.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 70

def self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
  begin
    profileId = TDUploadService.get_profile_id(authToken, profileName)

    if profileId
      UI.message("Profile '#{profileName}' found with ID: #{profileId}.")
      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

    elsif profileId.nil? && !createProfileIfNotExists
      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.")
    elsif profileId.nil? && createProfileIfNotExists
      UI.message("Profile '#{profileName}' not found. Creating the new profile...")
      profileId = TDUploadService.create_profile(authToken, profileName, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
    end

    return profileId
    
  rescue => e
    UI.user_error!("Couldn't get the profile: \"#{e.message}\".")
  end
end

.ac_login(personalAPIToken, subOrganizationName) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 48

def self.(personalAPIToken, subOrganizationName)
  begin
    token = ''

    user = TDAuthService.get_ac_token(pat: personalAPIToken)
    UI.success("Login is successful.")
    token = user.accessToken
    
    if subOrganizationName
      organization_id = TDAuthService.get_organization_id(access_token: token, name: subOrganizationName)
      user = TDAuthService.get_ac_token(pat: personalAPIToken, sub_organization_id: organization_id)
      UI.message("Switched to sub-organization: #{subOrganizationName}")
      token = user.accessToken
    end
    
    return token

  rescue => e
    UI.user_error!("Login failed: \"#{e.message}\".")
  end
end

.ac_upload(token, appPath, profileID, profileName, message) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 92

def self.ac_upload(token, appPath, profileID, profileName, message)
  begin
    UI.message("Upload started.")
    response = TDUploadService.upload_artifact(token: token, message: message, app: appPath, dist_profile_id: profileID)
    result = self.checkTaskStatus(token, response['taskId'])

    if result
      UI.success("#{appPath} uploaded to profile '#{profileName}' successfully  🎉")
    end
  rescue => e
    status_code = e.respond_to?(:response) && e.response ? e.response.code : 'unknown'
    UI.user_error!("Upload failed with status code '#{status_code}', with message \"#{e.message}\".")
  end
end

.authorsObject



149
150
151
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 149

def self.authors
  ["appcircleio"]
end

.available_optionsObject



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 162

def self.available_options
  [
    FastlaneCore::ConfigItem.new(key: :personalAPIToken,
                                 description: "Provide Personal API Token to authenticate connections to Appcircle services",
                                 optional: false,
                                 type: String,
                                 verify_block: proc do |value|
                                   UI.user_error!("Personal API Token cannot be empty. Please provide a valid access token.") unless value && !value.empty?
                                 end),

    FastlaneCore::ConfigItem.new(key: :subOrganizationName,
                                 description: "Optional: Sub-organization name for app distribution. Profiles will be created under root organization if not provided",
                                 optional: true,
                                 type: String),

    FastlaneCore::ConfigItem.new(key: :profileName,
                                 description: "Enter the profile name of the Appcircle testing distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
                                 optional: false,
                                 type: String,
                                 verify_block: proc do |value|
                                   UI.user_error!("Profile name cannot be empty. Please provide a testing distribution profile name.") unless value && !value.empty?
                                 end),

    FastlaneCore::ConfigItem.new(key: :createProfileIfNotExists,
                                 description: "Optional: If the profile does not exist, create a new profile with the given name",
                                 optional: true,
                                 type: Boolean),

    FastlaneCore::ConfigItem.new(key: :profileCreationSettings,
                                 description: "Optional: Profile creation settings for the testing distribution profile",
                                 optional: true,
                                 type: Hash,
                                 verify_block: proc do |value|
                                   # Parse and Validate
                                   if value[:authType] && !value[:authType].empty?
                                     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])

                                     if value[:authType] == 'static'
                                       UI.user_error!("username must be a String and at least 6 characters long.") unless value[:username].kind_of?(String) && value[:username].length >= 6
                                       UI.user_error!("password must be a String and at least 6 characters long.") unless value[:password].kind_of?(String) && value[:password].length >= 6
                                     else
                                       value[:username] = nil
                                       value[:password] = nil
                                     end
                                   end

                                   if value[:testingGroupNames] && !value[:testingGroupNames].empty?
                                     value[:testingGroupNames] = value[:testingGroupNames].to_s.split(",").map(&:strip)
                                     UI.user_error!("testingGroupNames must be a string array. Ex: 'group1, group2, group3'.") unless value[:testingGroupNames].kind_of?(Array)
                                   end
                                 end),

    FastlaneCore::ConfigItem.new(key: :appPath,
                                 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",
                                 optional: false,
                                 type: String,
                                 verify_block: proc do |value|
                                   UI.user_error!("Application file path cannot be empty. Please provide a valid application file path.") unless value && !value.empty?

                                   file_extension = File.extname(value).downcase
                                   unless VALID_EXTENSIONS.include?(file_extension)
                                     UI.user_error!("Invalid file extension: '#{file_extension}'. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive).")
                                   end
                                 end),

    FastlaneCore::ConfigItem.new(key: :message,
                                 description: "Message to include with the distribution to provide additional information to testers or users receiving the build",
                                 optional: false,
                                 type: String,
                                 verify_block: proc do |value|
                                   UI.user_error!("Message field cannot be empty. Please provide a message.") unless value && !value.empty?
                                 end)
  ]
end

.checkTaskStatus(authToken, taskId) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 107

def self.checkTaskStatus(authToken, taskId)
  uri = URI.parse("https://api.appcircle.io/task/v1/tasks/#{taskId}")
  
  check_interval = 1
  # timeout = 2 * 60 * 60 # 2 hours in seconds
  # start_time = Time.now

  loop do
    response = self.send_request(uri, authToken)
    if response.is_a?(Net::HTTPSuccess)
      stateValue = JSON.parse(response.body)["stateValue"]

      if stateValue == 1
        sleep(check_interval)
      elsif stateValue == 3
        return true
      else
        UI.error("Task Id #{taskId} failed with state value #{stateValue}.")
        UI.user_error!("Upload could not be completed successfully.")
      end
    else
      UI.user_error!("Upload failed with response code #{response.code} and message '#{response.message}'.")
    end

    # if Time.now - start_time > timeout
    #   UI.user_error!("Task Id #{taskId} timed out after 2 hours.")
    # end
  end
end

.descriptionObject



145
146
147
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 145

def self.description
  "Efficiently distribute application builds to users or testing groups using Appcircle's robust platform."
end

.detailsObject



157
158
159
160
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 157

def self.details
  # Optional:
  "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."
end

.is_supported?(platform) ⇒ Boolean

Returns:

  • (Boolean)


237
238
239
240
241
242
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 237

def self.is_supported?(platform)
  # Adjust this if your plugin only works for a particular platform (iOS vs. Android, for example)
  # See: https://docs.fastlane.tools/advanced/#control-configuration-by-lane-and-by-platform
  #
  [:ios, :android].include?(platform)
end

.return_valueObject



153
154
155
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 153

def self.return_value
  # If your method provides a return value, you can describe here what it does
end

.run(params) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 21

def self.run(params)
  personalAPIToken = params[:personalAPIToken]
  subOrganizationName = params[:subOrganizationName]
  profileName = params[:profileName]
  createProfileIfNotExists = params[:createProfileIfNotExists] || false
  #
  profileCreationSettings = params[:profileCreationSettings]
  profileAuthType = profileCreationSettings&.dig(:authType)
  profileUsername = profileCreationSettings&.dig(:username)
  profilePassword = profileCreationSettings&.dig(:password)
  profileTestingGroupNames= profileCreationSettings&.dig(:testingGroupNames)
  #
  appPath = params[:appPath]
  message = params[:message]
  
  profileAuthType = AUTH_TYPE_MAPPING[profileAuthType] # map input to API values

  # Auth
  authToken = self.(personalAPIToken, subOrganizationName)

  # Get or create profile
  profileId = self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)

  # Upload package
  self.ac_upload(authToken, appPath, profileId, profileName, message)
end

.send_request(uri, access_token) ⇒ Object



137
138
139
140
141
142
143
# File 'lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb', line 137

def self.send_request(uri, access_token)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = (uri.scheme == "https")
  request = Net::HTTP::Get.new(uri.request_uri)
  request["Authorization"] = "Bearer #{access_token}"
  http.request(request)
end