globus_client 0.12.1 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|