sdr-client 2.12.0 → 2.13.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.rubocop_todo.yml +24 -20
- data/Gemfile.lock +11 -8
- data/exe/sdr_redesigned +10 -0
- data/lib/sdr_client/redesigned_client/authenticator.rb +40 -0
- data/lib/sdr_client/redesigned_client/cli/config.rb +32 -0
- data/lib/sdr_client/redesigned_client/cli/credentials.rb +35 -0
- data/lib/sdr_client/redesigned_client/cli/update.rb +186 -0
- data/lib/sdr_client/redesigned_client/cli.rb +198 -0
- data/lib/sdr_client/redesigned_client/create_resource.rb +71 -0
- data/lib/sdr_client/redesigned_client/deposit.rb +115 -0
- data/lib/sdr_client/redesigned_client/direct_upload_request.rb +45 -0
- data/lib/sdr_client/redesigned_client/direct_upload_response.rb +9 -0
- data/lib/sdr_client/redesigned_client/file.rb +100 -0
- data/lib/sdr_client/redesigned_client/file_set.rb +53 -0
- data/lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb +13 -0
- data/lib/sdr_client/redesigned_client/find.rb +42 -0
- data/lib/sdr_client/redesigned_client/image_file_set_strategy.rb +13 -0
- data/lib/sdr_client/redesigned_client/job_status.rb +74 -0
- data/lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb +19 -0
- data/lib/sdr_client/redesigned_client/metadata.rb +64 -0
- data/lib/sdr_client/redesigned_client/operations/md5.rb +16 -0
- data/lib/sdr_client/redesigned_client/operations/mime_type.rb +17 -0
- data/lib/sdr_client/redesigned_client/operations/sha1.rb +16 -0
- data/lib/sdr_client/redesigned_client/request_builder.rb +171 -0
- data/lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb +14 -0
- data/lib/sdr_client/redesigned_client/structural_grouper.rb +72 -0
- data/lib/sdr_client/redesigned_client/structural_metadata_builder.rb +51 -0
- data/lib/sdr_client/redesigned_client/unexpected_response.rb +25 -0
- data/lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb +35 -0
- data/lib/sdr_client/redesigned_client/update_resource.rb +61 -0
- data/lib/sdr_client/redesigned_client/upload_files.rb +71 -0
- data/lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb +40 -0
- data/lib/sdr_client/redesigned_client.rb +192 -0
- data/lib/sdr_client/version.rb +1 -1
- data/lib/sdr_client.rb +3 -1
- metadata +35 -3
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# The file uploading part of a deposit
|
6
|
+
class UploadFiles
|
7
|
+
# @param [Hash<String,DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
|
8
|
+
# @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
|
9
|
+
def self.upload(file_metadata:, filepath_map:)
|
10
|
+
new(file_metadata: file_metadata, filepath_map: filepath_map).upload
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [Hash<String,DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
|
14
|
+
# @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
|
15
|
+
def initialize(file_metadata:, filepath_map:)
|
16
|
+
@file_metadata = file_metadata
|
17
|
+
@filepath_map = filepath_map
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Array<DirectUploadResponse>] the responses from the server for the uploads
|
21
|
+
def upload
|
22
|
+
file_metadata.map do |filepath, metadata|
|
23
|
+
direct_upload(metadata.to_json).tap do |response|
|
24
|
+
# ActiveStorage modifies the filename provided in response, so setting here with the relative filename
|
25
|
+
response.filename = filepath
|
26
|
+
upload_file(response)
|
27
|
+
logger.info("Upload of #{filepath} complete")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :file_metadata, :filepath_map
|
35
|
+
|
36
|
+
def logger
|
37
|
+
SdrClient::RedesignedClient.config.logger
|
38
|
+
end
|
39
|
+
|
40
|
+
def client
|
41
|
+
SdrClient::RedesignedClient.instance
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
'/v1/direct_uploads'
|
46
|
+
end
|
47
|
+
|
48
|
+
def direct_upload(metadata_json)
|
49
|
+
logger.info("Starting an upload request: #{metadata_json}")
|
50
|
+
response = client.post(path: path, body: metadata_json)
|
51
|
+
|
52
|
+
logger.info("Response from server: #{response}")
|
53
|
+
DirectUploadResponse.new(response)
|
54
|
+
end
|
55
|
+
|
56
|
+
def upload_file(response)
|
57
|
+
logger.info("Uploading `#{response.filename}' to #{response.direct_upload.fetch('url')}")
|
58
|
+
|
59
|
+
client.put(
|
60
|
+
path: response.direct_upload.fetch('url'),
|
61
|
+
body: ::File.open(filepath_map[response.filename]),
|
62
|
+
headers: {
|
63
|
+
'content-type' => response.content_type,
|
64
|
+
'content-length' => response.byte_size.to_s
|
65
|
+
},
|
66
|
+
expected_status: 204
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
class RedesignedClient
|
5
|
+
# Collecting all the metadata about the files for a deposit
|
6
|
+
class UploadFilesMetadataBuilder
|
7
|
+
# @param [Array<String>] files a list of relative filepaths to upload
|
8
|
+
# @param [Hash<String,String>] mime_types a map of filenames to mime types
|
9
|
+
# @param [String] basepath path to which files are relative
|
10
|
+
# @return [Hash<String, DirectUploadRequest>] the metadata for uploading the files
|
11
|
+
def self.build(files:, mime_types:, basepath:)
|
12
|
+
new(files: files, mime_types: mime_types, basepath: basepath).build
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Array<String>] files a list of absolute filepaths to upload
|
16
|
+
# @param [Hash<String,String>] mime_types a map of filenames to mime types
|
17
|
+
# @param [String] basepath path to which files are relative
|
18
|
+
def initialize(files:, mime_types:, basepath:)
|
19
|
+
@files = files
|
20
|
+
@mime_types = mime_types
|
21
|
+
@basepath = basepath
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :files, :mime_types, :basepath
|
25
|
+
|
26
|
+
# @return [Hash<String, DirectUploadRequest>] the metadata for uploading the files
|
27
|
+
def build
|
28
|
+
files.each_with_object({}) do |filepath, obj|
|
29
|
+
obj[filepath] = DirectUploadRequest.from_file(absolute_filepath_for(filepath),
|
30
|
+
file_name: filepath,
|
31
|
+
content_type: mime_types[filepath])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def absolute_filepath_for(filepath)
|
36
|
+
::File.join(basepath, filepath)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'digest'
|
4
|
+
require 'logger'
|
5
|
+
require 'shellwords'
|
6
|
+
require 'singleton'
|
7
|
+
require 'timeout'
|
8
|
+
|
9
|
+
module SdrClient
|
10
|
+
# The SDR client reimagined, built using patterns successfully used in other client gems we maintain
|
11
|
+
class RedesignedClient
|
12
|
+
include Singleton
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
16
|
+
def configure(url:, email: nil, password: nil, token_refresher: nil, token: default_token,
|
17
|
+
request_options: default_request_options, logger: default_logger)
|
18
|
+
if email.blank? && password.blank? && !token_refresher.respond_to?(:call)
|
19
|
+
raise ArgumentError, 'email and password cannot be blank without a custom token refresher callable'
|
20
|
+
end
|
21
|
+
|
22
|
+
instance.config = Config.new(
|
23
|
+
token: token,
|
24
|
+
url: url,
|
25
|
+
email: email,
|
26
|
+
password: password,
|
27
|
+
request_options: request_options,
|
28
|
+
logger: logger,
|
29
|
+
token_refresher: token_refresher
|
30
|
+
)
|
31
|
+
|
32
|
+
instance
|
33
|
+
end
|
34
|
+
# rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
|
35
|
+
|
36
|
+
# For the initial token, use a dummy value to avoid hitting any APIs
|
37
|
+
# during configuration, allowing `with_token_refresh_when_unauthorized` to handle
|
38
|
+
# auto-magic token refreshing. Why not immediately get a valid token? Our apps
|
39
|
+
# commonly invoke client `.configure` methods in the initializer in all
|
40
|
+
# application environments, even those that are never expected to
|
41
|
+
# connect to production APIs, such as local development machines.
|
42
|
+
#
|
43
|
+
# NOTE: `nil` and blank string cannot be used as dummy values here as
|
44
|
+
# they lead to a malformed request to be sent, which triggers an
|
45
|
+
# exception not rescued by `with_token_refresh_when_unauthorized`
|
46
|
+
def default_token
|
47
|
+
'a temporary dummy token to avoid hitting the API before it is needed'
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_logger
|
51
|
+
Logger.new($stdout)
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_request_options
|
55
|
+
{
|
56
|
+
read_timeout: default_timeout,
|
57
|
+
timeout: default_timeout
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
# NOTE: This is the number of seconds it roughly takes for H2 to
|
62
|
+
# successfully shunt ~10GB files over to SDR API
|
63
|
+
def default_timeout
|
64
|
+
900
|
65
|
+
end
|
66
|
+
|
67
|
+
delegate :config, :connection, :deposit_model, :job_status, :find, :update_model, :build_and_deposit,
|
68
|
+
to: :instance
|
69
|
+
end
|
70
|
+
|
71
|
+
attr_accessor :config
|
72
|
+
|
73
|
+
def deposit_model(...)
|
74
|
+
Deposit.deposit_model(...)
|
75
|
+
end
|
76
|
+
|
77
|
+
def job_status(...)
|
78
|
+
JobStatus.new(...)
|
79
|
+
end
|
80
|
+
|
81
|
+
def find(...)
|
82
|
+
Find.run(...)
|
83
|
+
end
|
84
|
+
|
85
|
+
def update_model(...)
|
86
|
+
UpdateResource.run(...)
|
87
|
+
end
|
88
|
+
|
89
|
+
def build_and_deposit(...)
|
90
|
+
Metadata.deposit(...)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Send an authenticated GET request
|
94
|
+
# @param path [String] the path to the SDR API request
|
95
|
+
def get(path:)
|
96
|
+
response = with_token_refresh_when_unauthorized do
|
97
|
+
connection.get(path)
|
98
|
+
end
|
99
|
+
|
100
|
+
UnexpectedResponse.call(response) unless response.success?
|
101
|
+
|
102
|
+
return nil if response.body.blank?
|
103
|
+
|
104
|
+
JSON.parse(response.body).with_indifferent_access
|
105
|
+
end
|
106
|
+
|
107
|
+
# Send an authenticated POST request
|
108
|
+
# @param path [String] the path to the SDR API request
|
109
|
+
# @param body [String] the body of the SDR API request
|
110
|
+
# @param headers [Hash] extra headers to add to the SDR API request
|
111
|
+
# @param expected_status [Integer] override if all 2xx statuses aren't success conditions
|
112
|
+
def post(path:, body:, headers: {}, expected_status: nil) # rubocop:disable Metrics/MethodLength
|
113
|
+
response = with_token_refresh_when_unauthorized do
|
114
|
+
connection.post(path) do |request|
|
115
|
+
request.body = body
|
116
|
+
request.headers = default_headers.merge(headers)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
if expected_status
|
121
|
+
UnexpectedResponse.call(response) if response.status != expected_status
|
122
|
+
elsif !response.success?
|
123
|
+
UnexpectedResponse.call(response)
|
124
|
+
end
|
125
|
+
|
126
|
+
return nil if response.body.blank?
|
127
|
+
|
128
|
+
JSON.parse(response.body).with_indifferent_access
|
129
|
+
end
|
130
|
+
|
131
|
+
# Send an authenticated PUT request
|
132
|
+
# @param path [String] the path to the SDR API request
|
133
|
+
# @param body [String] the body of the SDR API request
|
134
|
+
# @param headers [Hash] extra headers to add to the SDR API request
|
135
|
+
# @param params [Hash] query parameters to add to the SDR API request
|
136
|
+
# @param expected_status [Integer] override if all 2xx statuses aren't success conditions
|
137
|
+
def put(path:, body:, headers: {}, params: {}, expected_status: nil) # rubocop:disable Metrics/MethodLength
|
138
|
+
response = with_token_refresh_when_unauthorized do
|
139
|
+
connection.put(path) do |request|
|
140
|
+
request.body = body
|
141
|
+
request.headers = default_headers.merge(headers)
|
142
|
+
request.params = params if params.present?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
if expected_status
|
147
|
+
UnexpectedResponse.call(response) if response.status != expected_status
|
148
|
+
elsif !response.success?
|
149
|
+
UnexpectedResponse.call(response)
|
150
|
+
end
|
151
|
+
|
152
|
+
return nil if response.body.blank?
|
153
|
+
|
154
|
+
JSON.parse(response.body).with_indifferent_access
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
Config = Struct.new(:url, :email, :password, :token, :logger,
|
160
|
+
:request_options, :token_refresher, keyword_init: true)
|
161
|
+
|
162
|
+
def connection
|
163
|
+
Faraday.new(
|
164
|
+
url: SdrClient::RedesignedClient.config.url,
|
165
|
+
headers: default_headers,
|
166
|
+
request: SdrClient::RedesignedClient.config.request_options
|
167
|
+
) do |conn|
|
168
|
+
conn.adapter :net_http
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def default_headers
|
173
|
+
{
|
174
|
+
accept: 'application/json',
|
175
|
+
content_type: 'application/json',
|
176
|
+
Authorization: "Bearer #{config.token}"
|
177
|
+
}
|
178
|
+
end
|
179
|
+
|
180
|
+
def with_token_refresh_when_unauthorized
|
181
|
+
response = yield
|
182
|
+
|
183
|
+
# if unauthorized, token has likely expired. try to get a new token and then retry the same request(s).
|
184
|
+
if response.status == 401
|
185
|
+
config.token = config.token_refresher ? config.token_refresher.call : Authenticator.token
|
186
|
+
response = yield
|
187
|
+
end
|
188
|
+
|
189
|
+
response
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/lib/sdr_client/version.rb
CHANGED
data/lib/sdr_client.rb
CHANGED
@@ -12,7 +12,9 @@ loader = Zeitwerk::Loader.for_gem
|
|
12
12
|
loader.ignore(
|
13
13
|
"#{__dir__}/sdr-client.rb",
|
14
14
|
"#{__dir__}/sdr_client/cli.rb",
|
15
|
-
"#{__dir__}/sdr_client/cli/config.rb"
|
15
|
+
"#{__dir__}/sdr_client/cli/config.rb",
|
16
|
+
"#{__dir__}/sdr_client/redesigned_client/cli.rb",
|
17
|
+
"#{__dir__}/sdr_client/redesigned_client/cli/config.rb"
|
16
18
|
)
|
17
19
|
loader.inflector.inflect(
|
18
20
|
'md5' => 'MD5',
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sdr-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.13.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -115,6 +115,7 @@ email:
|
|
115
115
|
executables:
|
116
116
|
- remove_w3cdtf_encoding_from_event_dates
|
117
117
|
- sdr
|
118
|
+
- sdr_redesigned
|
118
119
|
extensions: []
|
119
120
|
extra_rdoc_files: []
|
120
121
|
files:
|
@@ -134,6 +135,7 @@ files:
|
|
134
135
|
- config/settings.yml
|
135
136
|
- exe/remove_w3cdtf_encoding_from_event_dates
|
136
137
|
- exe/sdr
|
138
|
+
- exe/sdr_redesigned
|
137
139
|
- lib/sdr-client.rb
|
138
140
|
- lib/sdr_client.rb
|
139
141
|
- lib/sdr_client/background_job_results.rb
|
@@ -166,6 +168,36 @@ files:
|
|
166
168
|
- lib/sdr_client/find.rb
|
167
169
|
- lib/sdr_client/login.rb
|
168
170
|
- lib/sdr_client/login_prompt.rb
|
171
|
+
- lib/sdr_client/redesigned_client.rb
|
172
|
+
- lib/sdr_client/redesigned_client/authenticator.rb
|
173
|
+
- lib/sdr_client/redesigned_client/cli.rb
|
174
|
+
- lib/sdr_client/redesigned_client/cli/config.rb
|
175
|
+
- lib/sdr_client/redesigned_client/cli/credentials.rb
|
176
|
+
- lib/sdr_client/redesigned_client/cli/update.rb
|
177
|
+
- lib/sdr_client/redesigned_client/create_resource.rb
|
178
|
+
- lib/sdr_client/redesigned_client/deposit.rb
|
179
|
+
- lib/sdr_client/redesigned_client/direct_upload_request.rb
|
180
|
+
- lib/sdr_client/redesigned_client/direct_upload_response.rb
|
181
|
+
- lib/sdr_client/redesigned_client/file.rb
|
182
|
+
- lib/sdr_client/redesigned_client/file_set.rb
|
183
|
+
- lib/sdr_client/redesigned_client/file_type_file_set_strategy.rb
|
184
|
+
- lib/sdr_client/redesigned_client/find.rb
|
185
|
+
- lib/sdr_client/redesigned_client/image_file_set_strategy.rb
|
186
|
+
- lib/sdr_client/redesigned_client/job_status.rb
|
187
|
+
- lib/sdr_client/redesigned_client/matching_file_grouping_strategy.rb
|
188
|
+
- lib/sdr_client/redesigned_client/metadata.rb
|
189
|
+
- lib/sdr_client/redesigned_client/operations/md5.rb
|
190
|
+
- lib/sdr_client/redesigned_client/operations/mime_type.rb
|
191
|
+
- lib/sdr_client/redesigned_client/operations/sha1.rb
|
192
|
+
- lib/sdr_client/redesigned_client/request_builder.rb
|
193
|
+
- lib/sdr_client/redesigned_client/single_file_grouping_strategy.rb
|
194
|
+
- lib/sdr_client/redesigned_client/structural_grouper.rb
|
195
|
+
- lib/sdr_client/redesigned_client/structural_metadata_builder.rb
|
196
|
+
- lib/sdr_client/redesigned_client/unexpected_response.rb
|
197
|
+
- lib/sdr_client/redesigned_client/update_dro_with_file_identifiers.rb
|
198
|
+
- lib/sdr_client/redesigned_client/update_resource.rb
|
199
|
+
- lib/sdr_client/redesigned_client/upload_files.rb
|
200
|
+
- lib/sdr_client/redesigned_client/upload_files_metadata_builder.rb
|
169
201
|
- lib/sdr_client/unexpected_response.rb
|
170
202
|
- lib/sdr_client/update.rb
|
171
203
|
- lib/sdr_client/version.rb
|
@@ -192,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
224
|
- !ruby/object:Gem::Version
|
193
225
|
version: '0'
|
194
226
|
requirements: []
|
195
|
-
rubygems_version: 3.5.
|
227
|
+
rubygems_version: 3.5.7
|
196
228
|
signing_key:
|
197
229
|
specification_version: 4
|
198
230
|
summary: The CLI for https://github.com/sul-dlss/sdr-api
|