sdr-client 2.12.0 → 2.13.0.beta1
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/.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
|