globus_client 0.12.1 → 0.13.0
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 +4 -4
- data/Gemfile.lock +3 -3
- data/api_test.rb +2 -2
- data/lib/globus_client/authenticator.rb +2 -0
- data/lib/globus_client/endpoint.rb +47 -89
- data/lib/globus_client/identity.rb +9 -25
- data/lib/globus_client/unexpected_response.rb +3 -3
- data/lib/globus_client/version.rb +1 -1
- data/lib/globus_client.rb +144 -37
- metadata +3 -4
- data/lib/globus_client/token_wrapper.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9129e55c5e5e6f4f62fc0d8912eca9310eff036e1c0095d24cae07367947054c
|
4
|
+
data.tar.gz: 1265f2f6c1b68bb6d76634f7bd636ed920b1095fd3bfd5ab6702525d994bf295
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 407e55c907f990f0492898e636525c8238221167fabf6d0b4bccef0c9eb163f0d75b5bb3b05b58130be46bb823b809a524490f762cbd5b3c56143e2abf9fd7c4
|
7
|
+
data.tar.gz: e7300d3adbaa6b75d43cafe1faae5eccfc212f558ce7d38889438d3ceaac99becc55242d2a10f2ec3eba759d2038649fe2b80585e97cba4e58ebd215b4767594
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
globus_client (0.
|
4
|
+
globus_client (0.13.0)
|
5
5
|
activesupport (>= 4.2, < 8)
|
6
6
|
faraday
|
7
7
|
faraday-retry
|
@@ -76,14 +76,14 @@ GEM
|
|
76
76
|
unicode-display_width (>= 2.4.0, < 3.0)
|
77
77
|
rubocop-ast (1.29.0)
|
78
78
|
parser (>= 3.2.1.0)
|
79
|
-
rubocop-capybara (2.
|
79
|
+
rubocop-capybara (2.19.0)
|
80
80
|
rubocop (~> 1.41)
|
81
81
|
rubocop-factory_bot (2.24.0)
|
82
82
|
rubocop (~> 1.33)
|
83
83
|
rubocop-performance (1.19.1)
|
84
84
|
rubocop (>= 1.7.0, < 2.0)
|
85
85
|
rubocop-ast (>= 0.4.0)
|
86
|
-
rubocop-rspec (2.24.
|
86
|
+
rubocop-rspec (2.24.1)
|
87
87
|
rubocop (~> 1.33)
|
88
88
|
rubocop-capybara (~> 2.17)
|
89
89
|
rubocop-factory_bot (~> 2.22)
|
data/api_test.rb
CHANGED
@@ -27,7 +27,7 @@ Benchmark.bm(20) do |benchmark|
|
|
27
27
|
|
28
28
|
benchmark.report("before_perms:") do
|
29
29
|
# Not part of the public API but this allows us to test access changes
|
30
|
-
@before_permissions = GlobusClient::Endpoint.new(GlobusClient.
|
30
|
+
@before_permissions = GlobusClient::Endpoint.new(GlobusClient.instance, user_id:, path:).send(:access_rule)["permissions"]
|
31
31
|
end
|
32
32
|
|
33
33
|
benchmark.report("has_files?:") do
|
@@ -48,7 +48,7 @@ Benchmark.bm(20) do |benchmark|
|
|
48
48
|
|
49
49
|
benchmark.report("after_perms:") do
|
50
50
|
# Not part of the public API but this allows us to test access changes
|
51
|
-
@after_permissions = GlobusClient::Endpoint.new(GlobusClient.
|
51
|
+
@after_permissions = GlobusClient::Endpoint.new(GlobusClient.instance, user_id:, path:).send(:access_rule)["permissions"]
|
52
52
|
end
|
53
53
|
|
54
54
|
puts "User #{user_id} exists: #{@user_exists}"
|
@@ -7,11 +7,11 @@ class GlobusClient
|
|
7
7
|
|
8
8
|
FileInfo = Struct.new(:name, :size)
|
9
9
|
|
10
|
-
# @param
|
10
|
+
# @param client [GlobusClient] a configured instance of the GlobusClient
|
11
11
|
# @param path [String] the path to operate on
|
12
12
|
# @param user_id [String] a Globus user ID (e.g., a @stanford.edu email address)
|
13
|
-
def initialize(
|
14
|
-
@
|
13
|
+
def initialize(client, path:, user_id:)
|
14
|
+
@client = client
|
15
15
|
@user_id = user_id
|
16
16
|
@path = path
|
17
17
|
end
|
@@ -30,23 +30,12 @@ class GlobusClient
|
|
30
30
|
def mkdir
|
31
31
|
# transfer API does not support recursive directory creation
|
32
32
|
paths.each do |path|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
next if response.success?
|
42
|
-
|
43
|
-
# Ignore error if directory already exists
|
44
|
-
if response.status == 502
|
45
|
-
error = JSON.parse(response.body)
|
46
|
-
next if error["code"] == "ExternalError.MkdirFailed.Exists"
|
47
|
-
end
|
48
|
-
|
49
|
-
UnexpectedResponse.call(response)
|
33
|
+
client.post(
|
34
|
+
base_url: client.config.transfer_url,
|
35
|
+
path: "#{transfer_path}/mkdir",
|
36
|
+
body: {DATA_TYPE: "mkdir", path:},
|
37
|
+
expected_response: ->(resp) { resp.status == 502 && JSON.parse(resp.body)["code"] == "ExternalError.MkdirFailed.Exists" }
|
38
|
+
)
|
50
39
|
end
|
51
40
|
end
|
52
41
|
|
@@ -64,35 +53,18 @@ class GlobusClient
|
|
64
53
|
def delete_access_rule
|
65
54
|
raise(StandardError, "Access rule not found for #{path}") if !access_rule_id
|
66
55
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
56
|
+
client.delete(
|
57
|
+
base_url: client.config.transfer_url,
|
58
|
+
path: "#{access_path}/#{access_rule_id}"
|
59
|
+
)
|
71
60
|
end
|
72
61
|
|
73
62
|
private
|
74
63
|
|
75
|
-
attr_reader :
|
76
|
-
|
77
|
-
def connection
|
78
|
-
# faraday/retry is used here to catch Faraday::ConnectionFailed exceptions
|
79
|
-
# see: https://github.com/sul-dlss/happy-heron/issues/3008
|
80
|
-
@connection ||= Faraday.new(
|
81
|
-
url: config.transfer_url,
|
82
|
-
headers: {Authorization: "Bearer #{config.token}"}
|
83
|
-
) do |faraday|
|
84
|
-
faraday.request :retry, {
|
85
|
-
max: 10,
|
86
|
-
interval: 0.05,
|
87
|
-
interval_randomness: 0.5,
|
88
|
-
backoff_factor: 2,
|
89
|
-
exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
|
90
|
-
}
|
91
|
-
end
|
92
|
-
end
|
64
|
+
attr_reader :client, :path, :user_id
|
93
65
|
|
94
66
|
def globus_identity_id
|
95
|
-
Identity.new(
|
67
|
+
Identity.new(client).get_identity_id(user_id)
|
96
68
|
end
|
97
69
|
|
98
70
|
# Builds up a path from a list of path elements. E.g., input would look like:
|
@@ -102,7 +74,7 @@ class GlobusClient
|
|
102
74
|
def paths
|
103
75
|
@paths ||= path_segments.map.with_index do |_segment, index|
|
104
76
|
File
|
105
|
-
.join(config.uploads_directory, path_segments.slice(..index))
|
77
|
+
.join(client.config.uploads_directory, path_segments.slice(..index))
|
106
78
|
.concat(PATH_SEPARATOR)
|
107
79
|
end
|
108
80
|
end
|
@@ -123,18 +95,21 @@ class GlobusClient
|
|
123
95
|
# @param files [Array<FileInfo>] an array of FileInfo structs, each of which has a name and a size
|
124
96
|
# @param return_presence [Boolean] if true, return a boolean to indicate if any files at all are present, short-circuiting the recursive operation
|
125
97
|
def ls_path(filepath, files, return_presence: false)
|
126
|
-
response =
|
127
|
-
|
98
|
+
response = client.get(
|
99
|
+
base_url: client.config.transfer_url,
|
100
|
+
path: "#{transfer_path}/ls",
|
101
|
+
params: {path: filepath}
|
102
|
+
)
|
128
103
|
|
129
|
-
|
130
|
-
data
|
104
|
+
response["DATA"]
|
131
105
|
.select { |object| object["type"] == "file" }
|
132
106
|
.each do |file|
|
133
107
|
return true if return_presence
|
134
108
|
|
135
109
|
files << FileInfo.new("#{filepath}#{file["name"]}", file["size"])
|
136
110
|
end
|
137
|
-
|
111
|
+
|
112
|
+
response["DATA"]
|
138
113
|
.select { |object| object["type"] == "dir" }
|
139
114
|
.each do |dir|
|
140
115
|
# NOTE: This allows the recursive method to short-circuit iff ls_path
|
@@ -149,62 +124,45 @@ class GlobusClient
|
|
149
124
|
end
|
150
125
|
|
151
126
|
def access_request(permissions:)
|
152
|
-
|
153
|
-
|
154
|
-
req.body = {
|
155
|
-
DATA_TYPE: "access",
|
156
|
-
permissions:
|
157
|
-
}.to_json
|
158
|
-
req.headers["Content-Type"] = "application/json"
|
159
|
-
end
|
127
|
+
if access_rule_id
|
128
|
+
update_access_request(permissions:)
|
160
129
|
else
|
161
|
-
|
162
|
-
|
130
|
+
client.post(
|
131
|
+
base_url: client.config.transfer_url,
|
132
|
+
path: access_path,
|
133
|
+
body: {
|
163
134
|
DATA_TYPE: "access",
|
164
135
|
principal_type: "identity",
|
165
136
|
principal: globus_identity_id,
|
166
137
|
path: full_path,
|
167
138
|
permissions:,
|
168
139
|
notify_email: user_id
|
169
|
-
}
|
170
|
-
|
171
|
-
end
|
140
|
+
}
|
141
|
+
)
|
172
142
|
end
|
173
|
-
|
174
|
-
return true if response.success?
|
175
|
-
|
176
|
-
UnexpectedResponse.call(response)
|
177
143
|
end
|
178
144
|
|
179
145
|
def update_access_request(permissions:)
|
180
146
|
raise(StandardError, "Access rule not found for #{path}") if !access_rule_id
|
181
147
|
|
182
|
-
|
183
|
-
|
148
|
+
client.put(
|
149
|
+
base_url: client.config.transfer_url,
|
150
|
+
path: "#{access_path}/#{access_rule_id}",
|
151
|
+
body: {
|
184
152
|
DATA_TYPE: "access",
|
185
153
|
permissions:
|
186
|
-
}
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
return true if response.success?
|
191
|
-
|
192
|
-
UnexpectedResponse.call(response)
|
154
|
+
}
|
155
|
+
)
|
193
156
|
end
|
194
157
|
|
195
158
|
def access_rule
|
196
|
-
response =
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
response_body = JSON.parse(response.body)
|
202
|
-
|
203
|
-
UnexpectedResponse.call(response, message: "Response is missing DATA in: #{response_body}") unless response.success? && response_body.key?("DATA")
|
159
|
+
response = client.get(
|
160
|
+
base_url: client.config.transfer_url,
|
161
|
+
path: access_list_path,
|
162
|
+
content_type: "application/json"
|
163
|
+
)
|
204
164
|
|
205
|
-
|
206
|
-
.parse(response.body)["DATA"]
|
207
|
-
.find { |acl| acl["path"] == full_path }
|
165
|
+
response.fetch("DATA").find { |acl| acl["path"] == full_path }
|
208
166
|
end
|
209
167
|
|
210
168
|
def access_rule_id
|
@@ -212,15 +170,15 @@ class GlobusClient
|
|
212
170
|
end
|
213
171
|
|
214
172
|
def transfer_path
|
215
|
-
"/v0.10/operation/endpoint/#{config.transfer_endpoint_id}"
|
173
|
+
"/v0.10/operation/endpoint/#{client.config.transfer_endpoint_id}"
|
216
174
|
end
|
217
175
|
|
218
176
|
def access_path
|
219
|
-
"/v0.10/endpoint/#{config.transfer_endpoint_id}/access"
|
177
|
+
"/v0.10/endpoint/#{client.config.transfer_endpoint_id}/access"
|
220
178
|
end
|
221
179
|
|
222
180
|
def access_list_path
|
223
|
-
"/v0.10/endpoint/#{config.transfer_endpoint_id}/access_list"
|
181
|
+
"/v0.10/endpoint/#{client.config.transfer_endpoint_id}/access_list"
|
224
182
|
end
|
225
183
|
end
|
226
184
|
end
|
@@ -3,19 +3,20 @@
|
|
3
3
|
class GlobusClient
|
4
4
|
# Lookup of a Globus identity ID
|
5
5
|
class Identity
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(client)
|
7
|
+
@client = client
|
8
8
|
end
|
9
9
|
|
10
10
|
# @param user_id [String] the username in the form of an email addresss
|
11
11
|
# @return [Hash] id and status of Globus identity
|
12
12
|
def get_identity(user_id)
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
response = client.get(
|
14
|
+
base_url: client.config.auth_url,
|
15
|
+
path: "/v2/api/identities",
|
16
|
+
params: {usernames: user_id}
|
17
|
+
)
|
16
18
|
|
17
|
-
|
18
|
-
extract_id(data)
|
19
|
+
response["identities"].find { |id| id["username"] == user_id }
|
19
20
|
end
|
20
21
|
|
21
22
|
# @param user_id [String] the username in the form of an email addresss
|
@@ -32,23 +33,6 @@ class GlobusClient
|
|
32
33
|
|
33
34
|
private
|
34
35
|
|
35
|
-
attr_reader :
|
36
|
-
|
37
|
-
def connection
|
38
|
-
Faraday.new(url: config.auth_url)
|
39
|
-
end
|
40
|
-
|
41
|
-
def lookup_identity
|
42
|
-
id_endpoint = "/v2/api/identities"
|
43
|
-
connection.get(id_endpoint) do |req|
|
44
|
-
req.params["usernames"] = @email
|
45
|
-
req.headers["Authorization"] = "Bearer #{config.token}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def extract_id(data)
|
50
|
-
identities = data["identities"]
|
51
|
-
identities.find { |id| id["username"] == @email }
|
52
|
-
end
|
36
|
+
attr_reader :client
|
53
37
|
end
|
54
38
|
end
|
@@ -26,7 +26,7 @@ class GlobusClient
|
|
26
26
|
# https://docs.globus.org/api/transfer/file_operations/#errors
|
27
27
|
# https://docs.globus.org/api/transfer/acl/#common_errors
|
28
28
|
# https://docs.globus.org/api/auth/reference/
|
29
|
-
def self.call(response
|
29
|
+
def self.call(response)
|
30
30
|
case response.status
|
31
31
|
when 400
|
32
32
|
raise BadRequestError, "Invalid path or another error with the request: #{response.body}"
|
@@ -37,11 +37,11 @@ class GlobusClient
|
|
37
37
|
when 404
|
38
38
|
raise ResourceNotFound, "Endpoint ID not found or resource does not exist: #{response.body}"
|
39
39
|
when 502
|
40
|
-
raise EndpointError, "Other error with endpoint: #{response.status} #{response.body}.
|
40
|
+
raise EndpointError, "Other error with endpoint: #{response.status} #{response.body}."
|
41
41
|
when 503
|
42
42
|
raise ServiceUnavailable, "The service is down for maintenance."
|
43
43
|
else
|
44
|
-
raise StandardError, "Unexpected response: #{response.status} #{response.body}.
|
44
|
+
raise StandardError, "Unexpected response: #{response.status} #{response.body}."
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
data/lib/globus_client.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/module/delegation"
|
4
|
+
require "active_support/core_ext/object/blank"
|
4
5
|
require "faraday"
|
5
6
|
require "faraday/retry"
|
6
7
|
require "ostruct"
|
@@ -24,15 +25,15 @@ class GlobusClient
|
|
24
25
|
def configure(client_id:, client_secret:, uploads_directory:, transfer_endpoint_id:, transfer_url: default_transfer_url, auth_url: default_auth_url)
|
25
26
|
instance.config = OpenStruct.new(
|
26
27
|
# For the initial token, use a dummy value to avoid hitting any APIs
|
27
|
-
# during configuration, allowing
|
28
|
-
# token refreshing. Why not immediately get a valid token? Our apps
|
28
|
+
# during configuration, allowing `with_token_refresh_when_unauthorized` to handle
|
29
|
+
# auto-magic token refreshing. Why not immediately get a valid token? Our apps
|
29
30
|
# commonly invoke client `.configure` methods in the initializer in all
|
30
31
|
# application environments, even those that are never expected to
|
31
32
|
# connect to production APIs, such as local development machines.
|
32
33
|
#
|
33
34
|
# NOTE: `nil` and blank string cannot be used as dummy values here as
|
34
35
|
# they lead to a malformed request to be sent, which triggers an
|
35
|
-
# exception not rescued by `
|
36
|
+
# exception not rescued by `with_token_refresh_when_unauthorized`
|
36
37
|
token: "a temporary dummy token to avoid hitting the API before it is needed",
|
37
38
|
client_id:,
|
38
39
|
client_secret:,
|
@@ -46,7 +47,7 @@ class GlobusClient
|
|
46
47
|
end
|
47
48
|
|
48
49
|
delegate :config, :disallow_writes, :delete_access_rule, :file_count, :list_files, :mkdir, :total_size,
|
49
|
-
:user_valid?, :get_filenames, :has_files?, to: :instance
|
50
|
+
:user_valid?, :get_filenames, :has_files?, :delete, :get, :post, :put, to: :instance
|
50
51
|
|
51
52
|
def default_transfer_url
|
52
53
|
"https://transfer.api.globusonline.org"
|
@@ -59,70 +60,176 @@ class GlobusClient
|
|
59
60
|
|
60
61
|
attr_accessor :config
|
61
62
|
|
63
|
+
# Send an authenticated GET request
|
64
|
+
# @param base_url [String] the base URL of the Globus API
|
65
|
+
# @param path [String] the path to the Globus API request
|
66
|
+
# @param params [Hash] params to get to the API
|
67
|
+
def get(base_url:, path:, params: {}, content_type: nil)
|
68
|
+
response = with_token_refresh_when_unauthorized do
|
69
|
+
connection(base_url).get(path, params) do |request|
|
70
|
+
request.headers["Authorization"] = "Bearer #{config.token}"
|
71
|
+
request.headers["Content-Type"] = content_type if content_type
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
UnexpectedResponse.call(response) unless response.success?
|
76
|
+
|
77
|
+
return nil if response.body.blank?
|
78
|
+
|
79
|
+
JSON.parse(response.body)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Send an authenticated POST request
|
83
|
+
# @param base_url [String] the base URL of the Globus API
|
84
|
+
# @param path [String] the path to the Globus API request
|
85
|
+
# @param body [String] the body of the Globus API request
|
86
|
+
# @param expected_response [#call] an expected response handler to allow short-circuiting the unexpected response
|
87
|
+
def post(base_url:, path:, body:, expected_response: ->(resp) { false })
|
88
|
+
response = with_token_refresh_when_unauthorized do
|
89
|
+
connection(base_url).post(path) do |request|
|
90
|
+
request.headers["Authorization"] = "Bearer #{config.token}"
|
91
|
+
request.headers["Content-Type"] = "application/json"
|
92
|
+
request.body = body.to_json
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
UnexpectedResponse.call(response) unless response.success? || expected_response.call(response)
|
97
|
+
|
98
|
+
return nil if response.body.blank?
|
99
|
+
|
100
|
+
JSON.parse(response.body)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Send an authenticated PUT request
|
104
|
+
# @param base_url [String] the base URL of the Globus API
|
105
|
+
# @param path [String] the path to the Globus API request
|
106
|
+
# @param body [String] the body of the Globus API request
|
107
|
+
def put(base_url:, path:, body:)
|
108
|
+
response = with_token_refresh_when_unauthorized do
|
109
|
+
connection(base_url).put(path) do |request|
|
110
|
+
request.headers["Authorization"] = "Bearer #{config.token}"
|
111
|
+
request.headers["Content-Type"] = "application/json"
|
112
|
+
request.body = body.to_json
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
UnexpectedResponse.call(response) unless response.success?
|
117
|
+
|
118
|
+
return nil if response.body.blank?
|
119
|
+
|
120
|
+
JSON.parse(response.body)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Send an authenticated DELETE request
|
124
|
+
# @param base_url [String] the base URL of the Globus API
|
125
|
+
# @param path [String] the path to the Globus API request
|
126
|
+
def delete(base_url:, path:)
|
127
|
+
response = with_token_refresh_when_unauthorized do
|
128
|
+
connection(base_url).delete(path) do |request|
|
129
|
+
request.headers["Authorization"] = "Bearer #{config.token}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
UnexpectedResponse.call(response) unless response.success?
|
134
|
+
|
135
|
+
return nil if response.body.blank?
|
136
|
+
|
137
|
+
JSON.parse(response.body)
|
138
|
+
end
|
139
|
+
|
62
140
|
def mkdir(...)
|
63
|
-
|
64
|
-
endpoint = Endpoint.new(config, ...)
|
141
|
+
Endpoint.new(self, ...).tap do |endpoint|
|
65
142
|
endpoint.mkdir
|
66
143
|
endpoint.allow_writes
|
67
144
|
end
|
68
145
|
end
|
69
146
|
|
70
147
|
def disallow_writes(...)
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
148
|
+
Endpoint
|
149
|
+
.new(self, ...)
|
150
|
+
.disallow_writes
|
75
151
|
end
|
76
152
|
|
77
153
|
def delete_access_rule(...)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
154
|
+
Endpoint
|
155
|
+
.new(self, ...)
|
156
|
+
.delete_access_rule
|
82
157
|
end
|
83
158
|
|
84
159
|
# NOTE: Can't use the `...` (argument forwarding) operator here because we
|
85
160
|
# want to route the keyword args to `Endpoint#new` and the block arg to
|
86
161
|
# `Endpoint#list_files`
|
87
162
|
def list_files(**keywords, &block)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
163
|
+
Endpoint
|
164
|
+
.new(self, **keywords)
|
165
|
+
.list_files(&block)
|
92
166
|
end
|
93
167
|
|
94
168
|
def file_count(...)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
169
|
+
Endpoint
|
170
|
+
.new(self, ...)
|
171
|
+
.list_files { |files| return files.count }
|
99
172
|
end
|
100
173
|
|
101
174
|
def total_size(...)
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
175
|
+
Endpoint
|
176
|
+
.new(self, ...)
|
177
|
+
.list_files { |files| return files.sum(&:size) }
|
106
178
|
end
|
107
179
|
|
108
180
|
def get_filenames(...)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
181
|
+
Endpoint
|
182
|
+
.new(self, ...)
|
183
|
+
.list_files { |files| return files.map(&:name) }
|
113
184
|
end
|
114
185
|
|
115
186
|
def has_files?(...)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
187
|
+
Endpoint
|
188
|
+
.new(self, ...)
|
189
|
+
.has_files?
|
120
190
|
end
|
121
191
|
|
122
192
|
def user_valid?(...)
|
123
|
-
|
124
|
-
|
125
|
-
|
193
|
+
Identity
|
194
|
+
.new(self)
|
195
|
+
.valid?(...)
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def connection(base_url)
|
201
|
+
Faraday.new(url: base_url) do |conn|
|
202
|
+
conn.request :retry, {
|
203
|
+
max: 10,
|
204
|
+
interval: 0.05,
|
205
|
+
interval_randomness: 0.5,
|
206
|
+
backoff_factor: 2,
|
207
|
+
exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS + [Faraday::ConnectionFailed]
|
208
|
+
}
|
126
209
|
end
|
127
210
|
end
|
211
|
+
|
212
|
+
# Wraps API operations to request new access token if expired.
|
213
|
+
# @yieldreturn response [Faraday::Response] the response to inspect
|
214
|
+
#
|
215
|
+
# @note You likely want to make sure you're wrapping a _single_ HTTP request in this
|
216
|
+
# method, because 1) all calls in the block will be retried from the top if there's
|
217
|
+
# an authN failure detected, and 2) only the response returned by the block will be
|
218
|
+
# inspected for authN failure.
|
219
|
+
# Related: consider that the client instance and its token will live across many
|
220
|
+
# invocations of the GlobusClient methods once the client is configured by a consuming application,
|
221
|
+
# since this class is a Singleton. Thus, a token may expire between any two calls (i.e. it
|
222
|
+
# isn't necessary for a set of operations to collectively take longer than the token lifetime for
|
223
|
+
# expiry to fall in the middle of that related set of HTTP calls).
|
224
|
+
def with_token_refresh_when_unauthorized
|
225
|
+
response = yield
|
226
|
+
|
227
|
+
# if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
|
228
|
+
if response.status == 401
|
229
|
+
config.token = Authenticator.token(config.client_id, config.client_secret, config.auth_url)
|
230
|
+
response = yield
|
231
|
+
end
|
232
|
+
|
233
|
+
response
|
234
|
+
end
|
128
235
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: globus_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Collier
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2023-09-
|
13
|
+
date: 2023-09-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
@@ -183,7 +183,6 @@ files:
|
|
183
183
|
- lib/globus_client/authenticator.rb
|
184
184
|
- lib/globus_client/endpoint.rb
|
185
185
|
- lib/globus_client/identity.rb
|
186
|
-
- lib/globus_client/token_wrapper.rb
|
187
186
|
- lib/globus_client/unexpected_response.rb
|
188
187
|
- lib/globus_client/version.rb
|
189
188
|
homepage: https://github.com/sul-dlss/globus_client
|
@@ -208,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
207
|
- !ruby/object:Gem::Version
|
209
208
|
version: '0'
|
210
209
|
requirements: []
|
211
|
-
rubygems_version: 3.4.
|
210
|
+
rubygems_version: 3.4.19
|
212
211
|
signing_key:
|
213
212
|
specification_version: 4
|
214
213
|
summary: Interface for interacting with the Globus API.
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class GlobusClient
|
4
|
-
# Wraps API operations to request new access token if expired
|
5
|
-
class TokenWrapper
|
6
|
-
def self.refresh(config)
|
7
|
-
yield
|
8
|
-
rescue UnexpectedResponse::UnauthorizedError
|
9
|
-
config.token = Authenticator.token(config.client_id, config.client_secret, config.auth_url)
|
10
|
-
yield
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|