fastlane-plugin-sapfire 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bdfb427e29ec64079d6c3a1331150a804195515abbc9a912ddee60ef702fb41
4
- data.tar.gz: 7e7c780539d1764e6e69c9d1a1772b78240b8befafd5c0c8b391d9470505e915
3
+ metadata.gz: 1acba89a24594d75771baf331f21ee7edb48214dd46a7ac3d156a9cb69cb916d
4
+ data.tar.gz: '083e0a2bf42c6b47e5cd25498fce324ca8f148ad8731488971fca1fd105ab3f7'
5
5
  SHA512:
6
- metadata.gz: 884d81bcc44c1c460b24e002e949c03cbd23183ec3c6750a1404f06d4ced9cdff3db6a3aed44e154bacab7ceab4624e44ca2097984c594952bac1f1b450c4128
7
- data.tar.gz: 3aec682d1d9832129eae53ee8327ac4097b0f158950c7f6d0385a99c3e978af07be452d106fef132df5cbd6571a1becea813b42b94c5512b42148c4b562f84b1
6
+ metadata.gz: 0f6132a34df4a89c0e1d36e8a64c6fe51210741e762e9fa14f48d752e34823a2079f5a26fa1b8e77f9473bdb11c051f1d4023ed171dd5b6e64df7c35627c78ae
7
+ data.tar.gz: ea7ec418dc4687a371f58b64edfbcbc72511e801bbb26836c3d1d3590802cc557110e3e365562110642613ec62c35a2d44f440014cdfce8210a3a187f9fc873d
data/README.md CHANGED
@@ -34,9 +34,13 @@ but also can be used independently via command-line interface.
34
34
  **Microsoft Store** is a digital distribution platform for Windows.
35
35
 
36
36
  ## Getting started
37
- To get started working with plugin, add it to your `fastlane/Pluginfile`:
37
+ To get started working with plugin, add it to your project by running:
38
+ ```shell
39
+ fastlane add_plugin sapfire
40
+ ```
41
+ Or, if you want to pick the latest commit from the repository, add this to `fastlane/Pluginfile`:
38
42
  ```ruby
39
- gem "fastlane-plugin-sapfire", git: "https://github.com/fastlane/fastlane-plugin-sapfire"
43
+ gem "fastlane-plugin-sapfire", git: "https://github.com/fastlane/fastlane-plugin-sapfire.git"
40
44
  ```
41
45
 
42
46
  ## Usage
@@ -2,10 +2,15 @@ require "uri"
2
2
  require "net/http"
3
3
  require "json"
4
4
  require "fastlane/action"
5
+ require_relative "../helper/ms_credentials"
5
6
 
6
7
  module Fastlane
7
8
  module Actions
8
9
  class AssociateMsStoreAction < Action
10
+ @token = ""
11
+ @vsapi_host = ""
12
+ @vsapi_endpoint = ""
13
+
9
14
  XML_NAME = "Package.StoreAssociation.xml".freeze
10
15
 
11
16
  def self.run(params)
@@ -14,10 +19,11 @@ module Fastlane
14
19
  begin
15
20
  UI.message("Creating #{XML_NAME}...")
16
21
 
17
- token = acquire_authorization_token
18
- developer_info = get_developer_info(token)
19
- app_info = get_app_info(token, params[:app_id])
20
- create_xml(params[:manifest], developer_info, app_info)
22
+ acquire_authorization_token
23
+ acquire_vsapi_location
24
+ ms_developer_info = developer_info
25
+ ms_app_info = app_info(params[:app_id])
26
+ create_xml(params[:manifest], ms_developer_info, ms_app_info)
21
27
 
22
28
  UI.message("#{XML_NAME} successfully created")
23
29
  rescue StandardError => ex
@@ -26,50 +32,46 @@ module Fastlane
26
32
  end
27
33
 
28
34
  def self.create_xml(manifest_path, developer_info, app_info)
29
- app_identity = app_info[:identity]
30
- app_names = app_info[:names]
31
- publisher = developer_info[:publisher]
32
- publisher_display_name = developer_info[:display_name]
33
35
  appxmanifest_xml = get_appxmanifest_xml(manifest_path)
34
36
 
35
- UI.message("Set identity name: #{app_identity}")
37
+ UI.message("Set identity name: #{app_info.identity}")
36
38
 
37
39
  begin
38
40
  identity_entry = appxmanifest_xml.elements["Package"]
39
41
  .elements["Identity"]
40
- identity_entry.attributes["Name"] = app_identity
41
- identity_entry.attributes["Publisher"] = publisher
42
+ identity_entry.attributes["Name"] = app_info.identity
43
+ identity_entry.attributes["Publisher"] = developer_info.publisher
42
44
 
43
45
  properties_entry = appxmanifest_xml.elements["Package"]
44
46
  .elements["Properties"]
45
- properties_entry.elements["DisplayName"].text = app_names[0] unless app_names.empty?
46
- properties_entry.elements["PublisherDisplayName"].text = publisher_display_name
47
+ properties_entry.elements["DisplayName"].text = app_info.names[0] unless app_info.names.empty?
48
+ properties_entry.elements["PublisherDisplayName"].text = developer_info.display_name
47
49
  rescue StandardError => ex
48
50
  UI.user_error!("Can't update app manifest: #{ex}")
49
51
  end
50
52
 
51
53
  save_xml(appxmanifest_xml, manifest_path)
52
54
 
53
- UI.message("Set publisher data: #{publisher}")
54
- UI.message("Set publisher display name: #{publisher_display_name}")
55
+ UI.message("Set publisher data: #{developer_info.publisher}")
56
+ UI.message("Set publisher display name: #{developer_info.display_name}")
55
57
 
56
58
  document = REXML::Document.new
57
59
  document.xml_decl.version = "1.0"
58
60
  document.xml_decl.encoding = "utf-8"
59
-
60
- store_association = document.add_element("StoreAssociation", {
61
+ xmlns_args = {
61
62
  "xmlns" => "http://schemas.microsoft.com/appx/2010/storeassociation"
62
- })
63
- store_association.add_element("Publisher").text = publisher
64
- store_association.add_element("PublisherDisplayName").text = publisher_display_name
63
+ }
64
+ store_association = document.add_element("StoreAssociation", xmlns_args)
65
+ store_association.add_element("Publisher").text = developer_info.publisher
66
+ store_association.add_element("PublisherDisplayName").text = developer_info.display_name
65
67
  store_association.add_element("DeveloperAccountType").text = "WSA"
66
68
  store_association.add_element("GeneratePackageHash").text = "http://www.w3.org/2001/04/xmlenc#sha256"
67
69
 
68
70
  product_reserved_info = store_association.add_element("ProductReservedInfo")
69
- product_reserved_info.add_element("MainPackageIdentityName").text = app_identity
71
+ product_reserved_info.add_element("MainPackageIdentityName").text = app_info.identity
70
72
 
71
73
  reserved_names = product_reserved_info.add_element("ReservedNames")
72
- app_names.each do |x|
74
+ app_info.names.each do |x|
73
75
  reserved_names.add_element("ReservedName").text = x
74
76
  end
75
77
 
@@ -96,72 +98,61 @@ module Fastlane
96
98
  file.close
97
99
  end
98
100
 
99
- def self.get_developer_info(token)
101
+ def self.developer_info
100
102
  UI.message("Obtaining developer info ...")
101
103
 
102
104
  headers = {
103
- "Authorization": "Bearer #{token}",
105
+ "Authorization": "Bearer #{@token}",
104
106
  "Accept": "application/json",
105
107
  "MS-Contract-Version": "1"
106
108
  }
107
109
  query = {
108
110
  setvar: "fltaad:1"
109
111
  }
110
-
111
- uri = URI("https://developer.microsoft.com/vsapi/developer")
112
- uri.query = URI.encode_www_form(query)
113
- https = Net::HTTP.new(uri.host, uri.port)
114
- https.use_ssl = true
115
- request = Net::HTTP::Get.new(uri, headers)
116
- response = https.request(request)
112
+ connection = Faraday.new(@vsapi_host)
117
113
 
118
114
  begin
119
- data = JSON.parse(response.body)
115
+ response = connection.get("#{@vsapi_endpoint}/developer", query, headers)
116
+
117
+ if response.status == 200
118
+ data = JSON.parse(response.body)
119
+ developer_info = DeveloperInfo.new(data["PublisherDisplayName"], data["Publisher"])
120
120
 
121
- if response.code == "200"
122
121
  UI.message("Developer info was obtained")
123
- return {
124
- display_name: data["PublisherDisplayName"],
125
- publisher: data["Publisher"]
126
- }
122
+
123
+ return developer_info
127
124
  end
128
125
 
129
- UI.user_error!("Request returned the error: #{response.code}")
126
+ UI.user_error!("Request returned the error: #{response.status}")
130
127
  rescue StandardError => ex
131
128
  UI.user_error!("Developer info request failed: #{ex}")
132
129
  end
133
130
  end
134
131
 
135
- def self.get_app_info(token, app_id)
132
+ def self.app_info(app_id)
136
133
  UI.message("Obtaining application info ...")
137
134
 
138
135
  headers = {
139
- "Authorization": "Bearer #{token}",
136
+ "Authorization": "Bearer #{@token}",
140
137
  "Accept": "application/json",
141
138
  "MS-Contract-Version": "1"
142
139
  }
143
140
  query = {
144
141
  setvar: "fltaad:1"
145
142
  }
146
-
147
- uri = URI("https://developer.microsoft.com/vsapi/applications")
148
- uri.query = URI.encode_www_form(query)
149
- https = Net::HTTP.new(uri.host, uri.port)
150
- https.use_ssl = true
151
- request = Net::HTTP::Get.new(uri, headers)
152
- response = https.request(request)
143
+ connection = Faraday.new(@vsapi_host)
153
144
 
154
145
  begin
155
- data = JSON.parse(response.body)
146
+ response = connection.get("#{@vsapi_endpoint}/applications", query, headers)
147
+
148
+ if response.status == 200
149
+ data = JSON.parse(response.body)
150
+ product = data["Products"].find { |x| x["LandingUrl"].include?(app_id) }
151
+ app_info = AppInfo.new(product["MainPackageIdentityName"], product["ReservedNames"])
156
152
 
157
- if response.code == "200"
158
153
  UI.message("Application info was obtained")
159
154
 
160
- product = data["Products"].find { |x| x["LandingUrl"].include?(app_id) }
161
- return {
162
- identity: product["MainPackageIdentityName"],
163
- names: product["ReservedNames"]
164
- }
155
+ return app_info
165
156
  end
166
157
 
167
158
  UI.user_error!("Request returned the error: #{response.code}")
@@ -173,40 +164,33 @@ module Fastlane
173
164
  def self.acquire_authorization_token
174
165
  UI.message("Acquiring authorization token ...")
175
166
 
176
- username = Actions.lane_context[SharedValues::SF_MS_USERNAME]
177
- password = Actions.lane_context[SharedValues::SF_MS_PASSWORD]
178
- tenant_id = Actions.lane_context[SharedValues::SF_MS_TENANT_ID]
179
- client_id = Actions.lane_context[SharedValues::SF_MS_CLIENT_ID]
180
- client_secret = Actions.lane_context[SharedValues::SF_MS_CLIENT_SECRET]
181
-
167
+ ms_credentials = Helper.ms_credentials
182
168
  body = {
183
- client_id: client_id,
184
- client_secret: client_secret,
169
+ client_id: ms_credentials.client_id,
170
+ client_secret: ms_credentials.client_secret,
185
171
  client_info: 1,
186
172
  grant_type: "password",
187
- scope: "https://management.azure.com/.default offline_access openid profile",
188
- username: username,
189
- password: password
173
+ scope: "https://graph.windows.net/.default offline_access openid profile",
174
+ username: ms_credentials.username,
175
+ password: ms_credentials.password
190
176
  }
191
177
  headers = {
192
- "x-anchormailbox": "upn:#{username}",
178
+ "x-anchormailbox": "upn:#{ms_credentials.username}",
193
179
  "x-client-sku": "fastlane-sapfire-plugin",
194
180
  "Accept": "application/json"
195
181
  }
196
-
197
- uri = URI("https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token")
198
- https = Net::HTTP.new(uri.host, uri.port)
199
- https.use_ssl = true
200
- request = Net::HTTP::Post.new(uri, headers)
182
+ connection = Faraday.new("https://login.microsoftonline.com")
201
183
  request_body = URI.encode_www_form(body)
202
- response = https.request(request, request_body)
203
184
 
204
185
  begin
186
+ response = connection.post("/#{ms_credentials.tenant_id}/oauth2/v2.0/token", request_body, headers)
205
187
  data = JSON.parse(response.body)
206
188
 
207
- if response.code == "200"
189
+ if response.status == 200
190
+ @token = data["access_token"]
208
191
  UI.message("Authorization token was obtained")
209
- return data["access_token"]
192
+
193
+ return
210
194
  end
211
195
 
212
196
  error = data["error"]
@@ -218,6 +202,34 @@ module Fastlane
218
202
  end
219
203
  end
220
204
 
205
+ def self.acquire_vsapi_location
206
+ query = {
207
+ LinkId: "875315" # VS API constant
208
+ }
209
+ connection = Faraday.new("https://go.microsoft.com")
210
+
211
+ begin
212
+ response = connection.get("/fwlink", query)
213
+
214
+ if response.status == 302
215
+ raise "'Location' header isn't presented" unless response.headers.include?("Location")
216
+
217
+ location = response.headers["Location"]
218
+ uri = URI(location)
219
+ @vsapi_host = "#{uri.scheme}://#{uri.host}"
220
+ @vsapi_endpoint = uri.path
221
+
222
+ UI.message("VS API endpoint: #{location}")
223
+
224
+ return
225
+ end
226
+
227
+ UI.user_error!("Request returned the error: #{response.status}")
228
+ rescue StandardError => ex
229
+ UI.user_error!("Failed to get VS API endpoint location: #{ex}")
230
+ end
231
+ end
232
+
221
233
  def self.description
222
234
  "Makes a local app manifest needed for Microsoft Store association"
223
235
  end
@@ -268,5 +280,23 @@ module Fastlane
268
280
  :project
269
281
  end
270
282
  end
283
+
284
+ class DeveloperInfo
285
+ attr_reader :display_name, :publisher
286
+
287
+ def initialize(display_name, publisher)
288
+ @display_name = display_name
289
+ @publisher = publisher
290
+ end
291
+ end
292
+
293
+ class AppInfo
294
+ attr_reader :identity, :names
295
+
296
+ def initialize(identity, names)
297
+ @identity = identity
298
+ @names = names
299
+ end
300
+ end
271
301
  end
272
302
  end
@@ -1,22 +1,19 @@
1
1
  require "fastlane/action"
2
+ require_relative "../helper/ms_credentials"
2
3
 
3
4
  module Fastlane
4
5
  module Actions
5
6
  module SharedValues
6
- SF_MS_USERNAME = :SF_MS_USERNAME
7
- SF_MS_PASSWORD = :SF_MS_PASSWORD
8
- SF_MS_TENANT_ID = :SF_MS_TENANT_ID
9
- SF_MS_CLIENT_ID = :SF_MS_CLIENT_ID
10
- SF_MS_CLIENT_SECRET = :SF_MS_CLIENT_SECRET
7
+ SF_MS_CREDENTIALS = :SF_MS_CREDENTIALS
11
8
  end
12
9
 
13
10
  class MsCredentialsAction < Action
14
11
  def self.run(params)
15
- Actions.lane_context[SharedValues::SF_MS_USERNAME] = params[:username]
16
- Actions.lane_context[SharedValues::SF_MS_PASSWORD] = params[:password]
17
- Actions.lane_context[SharedValues::SF_MS_TENANT_ID] = params[:tenant_id]
18
- Actions.lane_context[SharedValues::SF_MS_CLIENT_ID] = params[:client_id]
19
- Actions.lane_context[SharedValues::SF_MS_CLIENT_SECRET] = params[:client_secret]
12
+ Helper.ms_credentials.username = params[:username]
13
+ Helper.ms_credentials.password = params[:password]
14
+ Helper.ms_credentials.tenant_id = params[:tenant_id]
15
+ Helper.ms_credentials.client_id = params[:client_id]
16
+ Helper.ms_credentials.client_secret = params[:client_secret]
20
17
 
21
18
  UI.success("Credentials for Azure AD account is successfully saved for further actions")
22
19
  end
@@ -1,4 +1,5 @@
1
1
  require "fastlane/action"
2
+ require_relative "../helper/ms_credentials"
2
3
  require_relative "../helper/ms_devcenter_helper"
3
4
  require_relative "../msbuild/options"
4
5
 
@@ -8,18 +9,38 @@ module Fastlane
8
9
  DEFAULT_TIMEOUT = 300
9
10
 
10
11
  def self.run(params)
11
- tenant_id = Actions.lane_context[SharedValues::SF_MS_TENANT_ID]
12
- client_id = Actions.lane_context[SharedValues::SF_MS_CLIENT_ID]
13
- client_secret = Actions.lane_context[SharedValues::SF_MS_CLIENT_SECRET]
12
+ ms_credentials = Helper.ms_credentials
14
13
  app_id = params[:app_id]
15
14
  path = params[:path]
16
15
  timeout = params.values.include?(:timeout) && params[:timeout].positive? ? params[:timeout] : DEFAULT_TIMEOUT
17
16
 
18
17
  UI.message("Acquiring authorization token for DevCenter ...")
19
- auth_token = Helper::MsDevCenterHelper.acquire_authorization_token(tenant_id, client_id, client_secret, timeout)
18
+ auth_token = Helper::MsDevCenterHelper.acquire_authorization_token(ms_credentials.tenant_id,
19
+ ms_credentials.client_id,
20
+ ms_credentials.client_secret,
21
+ timeout)
20
22
  UI.message("Authorization token was obtained")
21
23
 
22
24
  UI.message("Creating submission for app #{app_id} ...")
25
+ pending_submission = Helper::MsDevCenterHelper.non_published_submission(app_id, auth_token, timeout)
26
+
27
+ unless pending_submission.nil?
28
+ submission_id = pending_submission["id"]
29
+
30
+ if params.values.include?(:remove_pending_submission) &&
31
+ [true].include?(params[:remove_pending_submission])
32
+ UI.message("Pending submission #{submission_id} were found and scheduled for deletion due to 'remove_pending_submission' argument set to 'true'")
33
+ Helper::MsDevCenterHelper.remove_submission(app_id, submission_id, auth_token, timeout)
34
+ UI.message("Pending submission deleted")
35
+ else
36
+ UI.user_error!([
37
+ "There is a pending submission #{submission_id} has already been created.",
38
+ "You need to either proceed it or remove before creating a new one.",
39
+ "Set 'remove_pending_submission' argument to 'true' to do that automatically."
40
+ ].join(" "))
41
+ end
42
+ end
43
+
23
44
  submission_obj = Helper::MsDevCenterHelper.create_submission(app_id, auth_token, timeout)
24
45
  submission_id = submission_obj["id"]
25
46
  UI.message("Submission #{submission_id} created")
@@ -46,7 +67,6 @@ module Fastlane
46
67
  [true].include?(params[:skip_waiting_for_build_processing])
47
68
  UI.success("Submission passed, but build processing were skipped. Check the Dev Center page.")
48
69
  return
49
-
50
70
  end
51
71
 
52
72
  status = false
@@ -127,6 +147,13 @@ module Fastlane
127
147
  default_value: false,
128
148
  type: Fastlane::Boolean
129
149
  ),
150
+ FastlaneCore::ConfigItem.new(
151
+ key: :remove_pending_submission,
152
+ description: "If set to true, the pending submission halts - a new one will be created automatically",
153
+ optional: true,
154
+ default_value: false,
155
+ type: Fastlane::Boolean
156
+ ),
130
157
  FastlaneCore::ConfigItem.new(
131
158
  key: :path,
132
159
  env_name: "SF_PACKAGE",
@@ -156,8 +183,8 @@ module Fastlane
156
183
  end
157
184
 
158
185
  def self.create_blob_zip(package_path)
159
- File.delete(File.join(File.dirname(package_path), "blob.zip"))
160
186
  zip_path = File.join(File.dirname(package_path), "blob.zip")
187
+ File.delete(zip_path) if File.exist?(zip_path)
161
188
 
162
189
  Zip::File.open(zip_path, create: true) do |file|
163
190
  file.add(File.basename(package_path), package_path)
@@ -181,7 +208,14 @@ module Fastlane
181
208
  "minimumSystemRam": "None"
182
209
  }
183
210
 
184
- submission_obj[key] = [] if submission_obj[key].empty?
211
+ if submission_obj[key].empty?
212
+ submission_obj[key] = []
213
+ else
214
+ submission_obj[key].each do |existing_package|
215
+ existing_package["fileStatus"] = "PendingDelete"
216
+ end
217
+ end
218
+
185
219
  submission_obj[key].append(package)
186
220
  submission_obj
187
221
  end
@@ -0,0 +1,47 @@
1
+ module Fastlane
2
+ module Helper
3
+ class MsCredentials
4
+ attr_writer :username, :password, :tenant_id, :client_id, :client_secret
5
+
6
+ def username
7
+ check_value("username", @username)
8
+ @username
9
+ end
10
+
11
+ def password
12
+ check_value("password", @password)
13
+ @password
14
+ end
15
+
16
+ def tenant_id
17
+ check_value("tenant_id", @tenant_id)
18
+ @tenant_id
19
+ end
20
+
21
+ def client_id
22
+ check_value("client_id", @client_id)
23
+ @client_id
24
+ end
25
+
26
+ def client_secret
27
+ check_value("client_secret", @client_secret)
28
+ @client_secret
29
+ end
30
+
31
+ def check_value(name, value)
32
+ raise "Microsoft credential variable hasn't been set: #{name}. You must call ms_credentials action before." if
33
+ value.nil? || value.empty?
34
+ end
35
+
36
+ private(:check_value)
37
+ end
38
+
39
+ class << self
40
+ attr_accessor :ms_credentials
41
+ end
42
+
43
+ self.ms_credentials = MsCredentials.new
44
+
45
+ private_class_method(:ms_credentials=)
46
+ end
47
+ end
@@ -53,19 +53,24 @@ module Fastlane
53
53
  url_data = parse_upload_url(url)
54
54
  url_data[:query]["comp"] = "block"
55
55
  url_data[:query]["blockid"] = id
56
-
57
56
  connection = Faraday.new(url_data[:host])
58
- response = connection.put(url_data[:path]) do |req|
59
- req.headers = headers
60
- req.params = url_data[:query]
61
- req.body = bytes
62
- req.options.timeout = timeout if timeout.positive?
63
- end
64
57
 
65
- return true if response.status == 201
58
+ begin
59
+ response = connection.put(url_data[:path]) do |req|
60
+ req.headers = headers
61
+ req.params = url_data[:query]
62
+ req.body = bytes
63
+ req.options.timeout = timeout if timeout.positive?
64
+ end
65
+
66
+ return true if response.status == 201
67
+
68
+ error = response.body.to_s
69
+ UI.error("Upload request failed.\nCode: #{response.status}\nError: #{error}")
70
+ rescue StandardError => ex
71
+ UI.error("Upload request failed: #{ex}")
72
+ end
66
73
 
67
- error = response.body.to_s
68
- UI.error("Upload request failed.\nCode: #{response.status}\nError: #{error}")
69
74
  false
70
75
  end
71
76
 
@@ -81,18 +86,23 @@ module Fastlane
81
86
 
82
87
  url_data = parse_upload_url(url)
83
88
  url_data[:query]["comp"] = "blocklist"
84
-
85
89
  connection = Faraday.new(url_data[:host])
86
- response = connection.put(url_data[:path]) do |req|
87
- req.params = url_data[:query]
88
- req.body = document.to_s
89
- req.options.timeout = timeout if timeout.positive?
90
- end
91
90
 
92
- return true if response.status == 201
91
+ begin
92
+ response = connection.put(url_data[:path]) do |req|
93
+ req.params = url_data[:query]
94
+ req.body = document.to_s
95
+ req.options.timeout = timeout if timeout.positive?
96
+ end
97
+
98
+ return true if response.status == 201
99
+
100
+ error = response.body.to_s
101
+ UI.error("Upload block list request failed.\nCode: #{response.status}\nError: #{error}")
102
+ rescue StandardError => ex
103
+ UI.error("Upload block list request failed: #{ex}")
104
+ end
93
105
 
94
- error = response.body.to_s
95
- UI.error("Upload block list request failed.\nCode: #{response.status}\nError: #{error}")
96
106
  false
97
107
  end
98
108
 
@@ -100,16 +110,17 @@ module Fastlane
100
110
  check_app_id(app_id)
101
111
 
102
112
  connection = Faraday.new(HOST)
103
- response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}") do |req|
104
- req.headers = build_headers(auth_token)
105
- req.options.timeout = timeout if timeout.positive?
106
- end
107
113
 
108
114
  begin
115
+ response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}") do |req|
116
+ req.headers = build_headers(auth_token)
117
+ req.options.timeout = timeout if timeout.positive?
118
+ end
109
119
  data = JSON.parse(response.body)
120
+
110
121
  return data if response.status == 200
111
122
 
112
- UI.user_error!("Request returned the error.\nCode: #{response.status}")
123
+ UI.user_error!("Getting app request returned the error.\nCode: #{response.status}")
113
124
  rescue StandardError => ex
114
125
  UI.user_error!("Getting app info process failed: #{ex}")
115
126
  end
@@ -117,27 +128,22 @@ module Fastlane
117
128
 
118
129
  def self.create_submission(app_id, auth_token, timeout = 0)
119
130
  check_app_id(app_id)
120
- if non_published_submission?(app_id, auth_token, timeout)
121
- UI.user_error!([
122
- "There is a pending submission has already been created.",
123
- "You need to either proceed it or remove before creating a new one."
124
- ].join(" "))
125
- end
126
131
 
127
132
  connection = Faraday.new(HOST)
128
- response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions") do |req|
129
- req.headers = build_headers(auth_token)
130
- req.options.timeout = timeout if timeout.positive?
131
- end
132
133
 
133
134
  begin
135
+ response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions") do |req|
136
+ req.headers = build_headers(auth_token)
137
+ req.options.timeout = timeout if timeout.positive?
138
+ end
134
139
  data = JSON.parse(response.body)
140
+
135
141
  return data if response.status == 201
136
142
 
137
143
  code = data["code"]
138
144
  message = data["message"]
139
145
 
140
- UI.user_error!("Request returned the error.\nCode: #{response.status} #{code}.\nDescription: #{message}")
146
+ UI.user_error!("Creating submission request returned the error.\nCode: #{response.status} #{code}.\nDescription: #{message}")
141
147
  rescue StandardError => ex
142
148
  UI.user_error!("Creating submission process failed: #{ex}")
143
149
  end
@@ -149,17 +155,18 @@ module Fastlane
149
155
 
150
156
  submission_id = submission_obj["id"]
151
157
  connection = Faraday.new(HOST)
152
- response = connection.put("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}") do |req|
153
- req.headers = build_headers(auth_token)
154
- req.body = submission_obj.to_json
155
- req.options.timeout = timeout if timeout.positive?
156
- end
157
158
 
158
159
  begin
160
+ response = connection.put("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}") do |req|
161
+ req.headers = build_headers(auth_token)
162
+ req.body = submission_obj.to_json
163
+ req.options.timeout = timeout if timeout.positive?
164
+ end
159
165
  data = JSON.parse(response.body)
166
+
160
167
  return data if response.status == 200
161
168
 
162
- UI.user_error!("Request returned the error.\nCode: #{response.status}")
169
+ UI.user_error!("Updating submission request returned the error.\nCode: #{response.status}")
163
170
  rescue StandardError => ex
164
171
  UI.user_error!("Updating submission process failed: #{ex}")
165
172
  end
@@ -170,29 +177,75 @@ module Fastlane
170
177
  check_submission_id(submission_id)
171
178
 
172
179
  connection = Faraday.new(HOST)
173
- response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/commit") do |req|
174
- req.headers = build_headers(auth_token)
175
- req.options.timeout = timeout if timeout.positive?
180
+
181
+ begin
182
+ response = connection.post("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/commit") do |req|
183
+ req.headers = build_headers(auth_token)
184
+ req.options.timeout = timeout if timeout.positive?
185
+ end
186
+
187
+ UI.user_error!("Committing submission request returned the error.\nCode: #{response.status}") unless response.status == 202
188
+ rescue StandardError => ex
189
+ UI.user_error!("Committing submission process failed: #{ex}")
176
190
  end
191
+ end
192
+
193
+ def self.remove_submission(app_id, submission_id, auth_token, timeout = 0)
194
+ check_app_id(app_id)
195
+ check_submission_id(submission_id)
196
+
197
+ connection = Faraday.new(HOST)
177
198
 
178
- UI.user_error!("Committing submission request returned the error.\nCode: #{response.status}") unless response.status == 202
199
+ begin
200
+ response = connection.delete("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}") do |req|
201
+ req.headers = build_headers(auth_token)
202
+ req.options.timeout = timeout if timeout.positive?
203
+ end
204
+
205
+ UI.user_error!("Deleting submission request returned the error.\nCode: #{response.status}") unless response.status == 204
206
+ rescue StandardError => ex
207
+ UI.user_error!("Deleting submission process failed: #{ex}")
208
+ end
179
209
  end
180
210
 
181
211
  def self.get_submission_status(app_id, submission_id, auth_token, timeout = 0)
182
212
  check_app_id(app_id)
183
213
  check_submission_id(submission_id)
184
214
 
185
- connection = Faraday.new(HOST)
186
- response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/status") do |req|
187
- req.headers = build_headers(auth_token)
188
- req.options.timeout = timeout if timeout.positive?
215
+ response = get_submission_status_internal(app_id, submission_id, auth_token, timeout)
216
+
217
+ # Sometimes MS can return internal server error code (500) that is not directly related to uploading process.
218
+ # Once it happens, retry 3 times until we'll get a success response.
219
+ if response[:status] == 500
220
+ server_error_500_retry_counter = 0
221
+
222
+ until server_error_500_retry_counter < 2
223
+ server_error_500_retry_counter += 1
224
+ response = get_submission_status_internal(app_id, submission_id, auth_token, timeout)
225
+ break if response.nil? || response[:status] == 200
226
+ end
189
227
  end
190
228
 
229
+ return response[:data] if !response.nil? && response[:status] == 200
230
+
231
+ UI.user_error!("Submission status obtaining request returned the error.\nCode: #{response[:status]}")
232
+ end
233
+
234
+ def self.get_submission_status_internal(app_id, submission_id, auth_token, timeout = 0)
235
+ connection = Faraday.new(HOST)
236
+
191
237
  begin
192
- data = JSON.parse(response.body)
193
- return data if response.status == 200
238
+ response = connection.get("/#{API_VERSION}/#{API_ROOT}/#{app_id}/submissions/#{submission_id}/status") do |req|
239
+ req.headers = build_headers(auth_token)
240
+ req.options.timeout = timeout if timeout.positive?
241
+ end
194
242
 
195
- UI.user_error!("Request returned the error.\nCode: #{response.status}")
243
+ UI.error("Submission status obtaining request returned the error.\nCode: #{response.status}") if response.status != 200
244
+ data = response.status == 200 ? JSON.parse(response.body) : nil
245
+ {
246
+ "data": data,
247
+ "status": response.status
248
+ }
196
249
  rescue StandardError => ex
197
250
  UI.user_error!("Submission status obtaining process failed: #{ex}")
198
251
  end
@@ -208,31 +261,31 @@ module Fastlane
208
261
  headers = {
209
262
  "Content-Type": "application/x-www-form-urlencoded"
210
263
  }.merge(REQUEST_HEADERS)
211
-
212
264
  connection = Faraday.new("https://login.microsoftonline.com")
213
- response = connection.post("/#{tenant_id}/oauth2/token") do |req|
214
- req.headers = headers
215
- req.body = body
216
- req.options.timeout = timeout if timeout.positive?
217
- end
218
265
 
219
266
  begin
267
+ response = connection.post("/#{tenant_id}/oauth2/token") do |req|
268
+ req.headers = headers
269
+ req.body = body
270
+ req.options.timeout = timeout if timeout.positive?
271
+ end
220
272
  data = JSON.parse(response.body)
273
+
221
274
  return data["access_token"] if response.status == 200
222
275
 
223
276
  error = data["error"]
224
277
  error_description = data["error_description"]
225
278
 
226
- UI.user_error!("Request returned the error.\nCode: #{error}.\nDescription: #{error_description}")
279
+ UI.user_error!("Authorization request returned the error.\nCode: #{error}.\nDescription: #{error_description}")
227
280
  rescue StandardError => ex
228
281
  UI.user_error!("Authorization failed: #{ex}")
229
282
  end
230
283
  end
231
284
 
232
- def self.non_published_submission?(app_id, auth_token, timeout = 0)
285
+ def self.non_published_submission(app_id, auth_token, timeout = 0)
233
286
  check_app_id(app_id)
234
287
  app_info = get_app_info(app_id, auth_token, timeout)
235
- app_info.key?("pendingApplicationSubmission")
288
+ app_info["pendingApplicationSubmission"]
236
289
  end
237
290
 
238
291
  def self.build_headers(auth_token)
@@ -270,16 +323,18 @@ module Fastlane
270
323
  public_class_method(:create_submission)
271
324
  public_class_method(:update_submission)
272
325
  public_class_method(:commit_submission)
326
+ public_class_method(:remove_submission)
273
327
  public_class_method(:get_submission_status)
274
328
  public_class_method(:acquire_authorization_token)
329
+ public_class_method(:non_published_submission)
275
330
 
276
331
  private_class_method(:upload_block)
277
332
  private_class_method(:upload_block_list)
278
- private_class_method(:non_published_submission?)
279
333
  private_class_method(:build_headers)
280
334
  private_class_method(:parse_upload_url)
281
335
  private_class_method(:check_app_id)
282
336
  private_class_method(:check_submission_id)
337
+ private_class_method(:get_submission_status_internal)
283
338
 
284
339
  private_constant(:HOST)
285
340
  private_constant(:API_VERSION)
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Sapfire
3
- VERSION = "1.1.0"
3
+ VERSION = "1.2.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-sapfire
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - CheeryLee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-05 00:00:00.000000000 Z
11
+ date: 2024-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -145,6 +145,7 @@ files:
145
145
  - lib/fastlane/plugin/sapfire/actions/upload_ms_store_action.rb
146
146
  - lib/fastlane/plugin/sapfire/actions/upload_nuget_action.rb
147
147
  - lib/fastlane/plugin/sapfire/actions_base/msbuild_action_base.rb
148
+ - lib/fastlane/plugin/sapfire/helper/ms_credentials.rb
148
149
  - lib/fastlane/plugin/sapfire/helper/ms_devcenter_helper.rb
149
150
  - lib/fastlane/plugin/sapfire/helper/sapfire_helper.rb
150
151
  - lib/fastlane/plugin/sapfire/msbuild/module.rb
@@ -175,7 +176,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
176
  - !ruby/object:Gem::Version
176
177
  version: '0'
177
178
  requirements: []
178
- rubygems_version: 3.4.10
179
+ rubygems_version: 3.3.26
179
180
  signing_key:
180
181
  specification_version: 4
181
182
  summary: A bunch of fastlane actions to work with MSBuild, NuGet and Microsoft Store