preservation-client 7.4.0 → 8.1.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/.circleci/config.yml +1 -1
- data/Gemfile.lock +20 -22
- data/README.md +6 -1
- data/lib/preservation/client/objects.rb +16 -10
- data/lib/preservation/client/version.rb +1 -1
- data/lib/preservation/client/versioned_api_service.rb +4 -3
- data/lib/preservation/client.rb +42 -6
- data/preservation-client.gemspec +1 -0
- metadata +16 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52679fa86a95f4bcf47a2cad9aee26613bae86439a4ef3a0bb0cae6a2a392f4d
|
|
4
|
+
data.tar.gz: 54c90dd6cc933702c4b4e13f66af27cef44883ac4170b374e9dd79371577b6a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aac08ec82147c622ab03f32f61138ca9dcf9ac3bb5b8823d8037b685768f492e33ce57164656ed5f667696a02821769175bf9ce424988aec29900d72e7d6f669
|
|
7
|
+
data.tar.gz: 15e40540086779cb3d93786ba6c0b0d17d86c8c316ea66e5785afd4f85e6dced4d01cc2a00bf5ebe263ecb2a6a05c73533f53ff05a45e5798f74cd606342f54c
|
data/.circleci/config.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
preservation-client (
|
|
4
|
+
preservation-client (8.1.0)
|
|
5
5
|
activesupport (>= 4.2)
|
|
6
6
|
faraday (~> 2.0)
|
|
7
|
+
faraday-retry (~> 2.0)
|
|
7
8
|
moab-versioning (>= 5.0.0, < 7)
|
|
8
9
|
zeitwerk (~> 2.1)
|
|
9
10
|
|
|
@@ -31,27 +32,28 @@ GEM
|
|
|
31
32
|
byebug (13.0.0)
|
|
32
33
|
reline (>= 0.6.0)
|
|
33
34
|
coderay (1.1.3)
|
|
34
|
-
concurrent-ruby (1.3.
|
|
35
|
+
concurrent-ruby (1.3.7)
|
|
35
36
|
connection_pool (3.0.2)
|
|
36
37
|
crack (1.0.1)
|
|
37
38
|
bigdecimal
|
|
38
39
|
rexml
|
|
39
40
|
diff-lcs (1.6.2)
|
|
40
|
-
docile (1.4.1)
|
|
41
41
|
drb (2.2.3)
|
|
42
42
|
druid-tools (3.0.0)
|
|
43
|
-
faraday (2.14.
|
|
43
|
+
faraday (2.14.3)
|
|
44
44
|
faraday-net_http (>= 2.0, < 3.5)
|
|
45
45
|
json
|
|
46
46
|
logger
|
|
47
|
-
faraday-net_http (3.4.
|
|
47
|
+
faraday-net_http (3.4.4)
|
|
48
48
|
net-http (~> 0.5)
|
|
49
|
+
faraday-retry (2.4.0)
|
|
50
|
+
faraday (~> 2.0)
|
|
49
51
|
hashdiff (1.2.1)
|
|
50
|
-
i18n (1.
|
|
52
|
+
i18n (1.15.2)
|
|
51
53
|
concurrent-ruby (~> 1.0)
|
|
52
54
|
io-console (0.8.2)
|
|
53
|
-
json (2.
|
|
54
|
-
language_server-protocol (3.17.0.
|
|
55
|
+
json (2.21.1)
|
|
56
|
+
language_server-protocol (3.17.0.6)
|
|
55
57
|
lint_roller (1.1.0)
|
|
56
58
|
logger (1.7.0)
|
|
57
59
|
method_source (1.1.0)
|
|
@@ -65,11 +67,11 @@ GEM
|
|
|
65
67
|
nokogiri-happymapper
|
|
66
68
|
net-http (0.9.1)
|
|
67
69
|
uri (>= 0.11.1)
|
|
68
|
-
nokogiri (1.19.
|
|
70
|
+
nokogiri (1.19.4-arm64-darwin)
|
|
69
71
|
racc (~> 1.4)
|
|
70
|
-
nokogiri (1.19.
|
|
72
|
+
nokogiri (1.19.4-x86_64-darwin)
|
|
71
73
|
racc (~> 1.4)
|
|
72
|
-
nokogiri (1.19.
|
|
74
|
+
nokogiri (1.19.4-x86_64-linux-gnu)
|
|
73
75
|
racc (~> 1.4)
|
|
74
76
|
nokogiri-happymapper (0.10.1)
|
|
75
77
|
nokogiri (~> 1.5)
|
|
@@ -106,7 +108,7 @@ GEM
|
|
|
106
108
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
107
109
|
rspec-support (~> 3.13.0)
|
|
108
110
|
rspec-support (3.13.7)
|
|
109
|
-
rubocop (1.
|
|
111
|
+
rubocop (1.88.2)
|
|
110
112
|
json (~> 2.3)
|
|
111
113
|
language_server-protocol (~> 3.17.0.2)
|
|
112
114
|
lint_roller (~> 1.1.0)
|
|
@@ -117,23 +119,19 @@ GEM
|
|
|
117
119
|
rubocop-ast (>= 1.49.0, < 2.0)
|
|
118
120
|
ruby-progressbar (~> 1.7)
|
|
119
121
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
120
|
-
rubocop-ast (1.
|
|
122
|
+
rubocop-ast (1.50.0)
|
|
121
123
|
parser (>= 3.3.7.2)
|
|
122
124
|
prism (~> 1.7)
|
|
123
125
|
rubocop-rake (0.7.1)
|
|
124
126
|
lint_roller (~> 1.1)
|
|
125
127
|
rubocop (>= 1.72.1)
|
|
126
|
-
rubocop-rspec (3.
|
|
128
|
+
rubocop-rspec (3.10.2)
|
|
127
129
|
lint_roller (~> 1.1)
|
|
128
|
-
|
|
130
|
+
regexp_parser (>= 2.0)
|
|
131
|
+
rubocop (~> 1.86, >= 1.86.2)
|
|
129
132
|
ruby-progressbar (1.13.0)
|
|
130
133
|
securerandom (0.4.1)
|
|
131
|
-
simplecov (0.
|
|
132
|
-
docile (~> 1.1)
|
|
133
|
-
simplecov-html (~> 0.11)
|
|
134
|
-
simplecov_json_formatter (~> 0.1)
|
|
135
|
-
simplecov-html (0.13.2)
|
|
136
|
-
simplecov_json_formatter (0.1.4)
|
|
134
|
+
simplecov (1.0.0)
|
|
137
135
|
tzinfo (2.0.6)
|
|
138
136
|
concurrent-ruby (~> 1.0)
|
|
139
137
|
unicode-display_width (3.2.0)
|
|
@@ -144,7 +142,7 @@ GEM
|
|
|
144
142
|
addressable (>= 2.8.0)
|
|
145
143
|
crack (>= 0.3.2)
|
|
146
144
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
147
|
-
zeitwerk (2.
|
|
145
|
+
zeitwerk (2.8.2)
|
|
148
146
|
|
|
149
147
|
PLATFORMS
|
|
150
148
|
arm64-darwin-23
|
data/README.md
CHANGED
|
@@ -60,6 +60,11 @@ See https://github.com/sul-dlss/preservation_catalog#api for info on obtaining a
|
|
|
60
60
|
|
|
61
61
|
Note that the preservation service is behind a firewall.
|
|
62
62
|
|
|
63
|
+
### Retries
|
|
64
|
+
HTTP GET requests will be automatically retried for certain errors. The number of retries (`retries_max`) and interval between retries (`retry_interval`) can be specified as part of the configuration of the client.
|
|
65
|
+
|
|
66
|
+
Note that there is special retry behavior (cleaning up files on failure) for `content_to_file`, but it uses the same configuration.
|
|
67
|
+
|
|
63
68
|
## API Coverage
|
|
64
69
|
|
|
65
70
|
- druids may be with or without the "druid:" prefix - 'oo000oo0000' or 'druid:oo000oo0000'
|
|
@@ -110,7 +115,7 @@ Behavior notes:
|
|
|
110
115
|
|
|
111
116
|
### Validate the Moab
|
|
112
117
|
|
|
113
|
-
- `client.objects.validate_moab(druid: 'ooo000oo0000')` - validates that the Moab object, used by preservationWF to ensure we have a valid Moab before replicating to various preservation endpoints
|
|
118
|
+
- `client.objects.validate_moab(druid: 'ooo000oo0000', lane_id: 'high)` - validates that the Moab object, used by preservationWF to ensure we have a valid Moab before replicating to various preservation endpoints
|
|
114
119
|
|
|
115
120
|
### Get difference information between passed contentMetadata.xml and files in the Moab
|
|
116
121
|
|
|
@@ -11,6 +11,12 @@ module Preservation
|
|
|
11
11
|
class Client
|
|
12
12
|
# API calls that are about Preserved Objects
|
|
13
13
|
class Objects < VersionedApiService # rubocop:disable Metrics/ClassLength
|
|
14
|
+
def initialize(connection:, streaming_connection:, retry_max:, retry_interval:, api_version: DEFAULT_API_VERSION)
|
|
15
|
+
super(connection: connection, streaming_connection: streaming_connection, api_version: api_version)
|
|
16
|
+
@retry_max = retry_max
|
|
17
|
+
@retry_interval = retry_interval
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
# @param [String] druid - with or without prefix: 'druid:bb123cd4567' OR 'bb123cd4567'
|
|
15
21
|
# @return [Hash] the checksums and filesize for the druid
|
|
16
22
|
def checksum(druid:)
|
|
@@ -69,14 +75,14 @@ module Preservation
|
|
|
69
75
|
# @param [String] destination_filepath - absolute or relative path to desired destination file
|
|
70
76
|
# @param [String] version - the version of the file requested (defaults to nil for latest version)
|
|
71
77
|
# @param [String, nil] expected_md5 - optional expected md5 checksum for integrity validation
|
|
72
|
-
# @param [Integer]
|
|
73
|
-
# @param [Float]
|
|
78
|
+
# @param [Integer] max - number of retry attempts after the initial attempt
|
|
79
|
+
# @param [Float] interval - base delay in seconds for exponential retry backoff
|
|
74
80
|
# @raise [Preservation::Client::IntegrityError] if the expected_md5 is provided and does not match the actual md5
|
|
75
81
|
# @raise [Preservation::Client::NotFoundError] if the specified file is not found
|
|
76
82
|
# @raise [Preservation::Client::Error] for other errors encountered during download
|
|
77
83
|
def content_to_file(druid:, filepath:, destination_filepath:, version: nil, expected_md5: nil, # rubocop:disable Metrics/ParameterLists
|
|
78
|
-
|
|
79
|
-
with_retries(
|
|
84
|
+
max: nil, interval: nil)
|
|
85
|
+
with_retries(max: max || @retry_max, interval: interval || @retry_interval) do
|
|
80
86
|
temp_filepath = nil
|
|
81
87
|
|
|
82
88
|
begin
|
|
@@ -112,10 +118,11 @@ module Preservation
|
|
|
112
118
|
# calls the endpoint to queue a ValidateMoab job for a specific druid
|
|
113
119
|
# typically called by a preservationIngestWF robot
|
|
114
120
|
# @param [String] druid - with or without prefix: 'druid:ab123cd4567' or 'ab123cd4567'
|
|
121
|
+
# @param [String, nil] lane_id - low, default, or high
|
|
115
122
|
# @return [String] "ok" when job queued
|
|
116
123
|
# @raise [Preservation::Client::NotFoundError] when druid is not found
|
|
117
|
-
def validate_moab(druid:)
|
|
118
|
-
get("objects/#{druid}/validate_moab", {}, on_data: nil)
|
|
124
|
+
def validate_moab(druid:, lane_id: nil)
|
|
125
|
+
get("objects/#{druid}/validate_moab", { 'lane-id': lane_id }.compact, on_data: nil)
|
|
119
126
|
end
|
|
120
127
|
|
|
121
128
|
# convenience method for retrieving latest Moab::SignatureCatalog from a Moab object,
|
|
@@ -138,16 +145,15 @@ module Preservation
|
|
|
138
145
|
get("objects/#{druid}/file", { category: category, filepath: filepath, version: version }, on_data: on_data)
|
|
139
146
|
end
|
|
140
147
|
|
|
141
|
-
def with_retries(
|
|
148
|
+
def with_retries(max:, interval:)
|
|
142
149
|
attempt = 0
|
|
143
150
|
|
|
144
151
|
begin
|
|
145
152
|
yield
|
|
146
153
|
rescue StandardError => e
|
|
147
|
-
raise if !retryable_error?(e) || attempt >=
|
|
154
|
+
raise if !retryable_error?(e) || attempt >= max
|
|
148
155
|
|
|
149
|
-
|
|
150
|
-
sleep(sleep_seconds) unless sleep_seconds.nil?
|
|
156
|
+
sleep(interval.to_f * (Client::RETRY_BACKOFF_FACTOR**attempt))
|
|
151
157
|
attempt += 1
|
|
152
158
|
retry
|
|
153
159
|
end
|
|
@@ -4,14 +4,15 @@ module Preservation
|
|
|
4
4
|
class Client
|
|
5
5
|
# @abstract API calls to a versioned endpoint
|
|
6
6
|
class VersionedApiService
|
|
7
|
-
def initialize(connection:, api_version: DEFAULT_API_VERSION)
|
|
7
|
+
def initialize(connection:, api_version: DEFAULT_API_VERSION, streaming_connection: nil)
|
|
8
8
|
@connection = connection
|
|
9
9
|
@api_version = api_version
|
|
10
|
+
@streaming_connection = streaming_connection
|
|
10
11
|
end
|
|
11
12
|
|
|
12
13
|
private
|
|
13
14
|
|
|
14
|
-
attr_reader :connection, :api_version
|
|
15
|
+
attr_reader :connection, :api_version, :streaming_connection
|
|
15
16
|
|
|
16
17
|
# @param path [String] path to be appended to connection url (no leading slash)
|
|
17
18
|
def get_json(path, object_id)
|
|
@@ -42,7 +43,7 @@ module Preservation
|
|
|
42
43
|
return http_response(:get, path, params) unless on_data
|
|
43
44
|
|
|
44
45
|
req_url = "#{api_version}/#{path}"
|
|
45
|
-
connection.get("#{api_version}/#{path}", params) do |req|
|
|
46
|
+
(streaming_connection || connection).get("#{api_version}/#{path}", params) do |req|
|
|
46
47
|
req.options.on_data = proc do |chunk, size, env|
|
|
47
48
|
if env.status >= 300
|
|
48
49
|
errmsg = "Preservation::Client.#{caller_locations.first.label} " \
|
data/lib/preservation/client.rb
CHANGED
|
@@ -4,6 +4,7 @@ require 'active_support/core_ext/hash/indifferent_access'
|
|
|
4
4
|
require 'active_support/core_ext/module/delegation'
|
|
5
5
|
require 'active_support/core_ext/object/blank'
|
|
6
6
|
require 'faraday'
|
|
7
|
+
require 'faraday/retry'
|
|
7
8
|
require 'singleton'
|
|
8
9
|
require 'zeitwerk'
|
|
9
10
|
|
|
@@ -53,13 +54,18 @@ module Preservation
|
|
|
53
54
|
|
|
54
55
|
DEFAULT_API_VERSION = 'v1'
|
|
55
56
|
DEFAULT_TIMEOUT = 300
|
|
57
|
+
DEFAULT_RETRY_MAX = 3
|
|
58
|
+
DEFAULT_RETRY_INTERVAL = 0.5
|
|
59
|
+
RETRY_BACKOFF_FACTOR = 2
|
|
56
60
|
TOKEN_HEADER = 'Authorization'
|
|
57
61
|
|
|
58
62
|
include Singleton
|
|
59
63
|
|
|
60
64
|
# @return [Preservation::Client::Objects] an instance of the `Client::Objects` class
|
|
61
65
|
def objects
|
|
62
|
-
@objects ||= Objects.new(connection: connection,
|
|
66
|
+
@objects ||= Objects.new(connection: connection, streaming_connection: streaming_connection,
|
|
67
|
+
retry_max: retry_max, retry_interval: retry_interval,
|
|
68
|
+
api_version: DEFAULT_API_VERSION)
|
|
63
69
|
end
|
|
64
70
|
|
|
65
71
|
# @return [Preservation::Client::Catalog] an instance of the `Client::Catalog` class
|
|
@@ -71,13 +77,19 @@ module Preservation
|
|
|
71
77
|
# @param [String] url the endpoint URL
|
|
72
78
|
# @param [String] token a bearer token for HTTP authentication
|
|
73
79
|
# @param [Integer] read_timeout the value in seconds of the read timeout
|
|
74
|
-
|
|
80
|
+
# @param [Integer] retry_max number of retry attempts for GET requests
|
|
81
|
+
# @param [Float] retry_interval base delay in seconds between retries (exponential backoff)
|
|
82
|
+
def configure(url:, token:, read_timeout: DEFAULT_TIMEOUT,
|
|
83
|
+
retry_max: DEFAULT_RETRY_MAX, retry_interval: DEFAULT_RETRY_INTERVAL)
|
|
75
84
|
instance.url = url
|
|
76
85
|
instance.token = token
|
|
77
86
|
instance.read_timeout = read_timeout
|
|
87
|
+
instance.retry_max = retry_max
|
|
88
|
+
instance.retry_interval = retry_interval
|
|
78
89
|
|
|
79
|
-
# Force
|
|
90
|
+
# Force connections to be re-established when `.configure` is called
|
|
80
91
|
instance.connection = nil
|
|
92
|
+
instance.streaming_connection = nil
|
|
81
93
|
|
|
82
94
|
self
|
|
83
95
|
end
|
|
@@ -85,7 +97,7 @@ module Preservation
|
|
|
85
97
|
delegate :objects, :update, to: :instance
|
|
86
98
|
end
|
|
87
99
|
|
|
88
|
-
attr_writer :connection, :read_timeout, :token, :url
|
|
100
|
+
attr_writer :connection, :read_timeout, :retry_interval, :retry_max, :streaming_connection, :token, :url
|
|
89
101
|
|
|
90
102
|
delegate :update, to: :catalog
|
|
91
103
|
|
|
@@ -103,11 +115,35 @@ module Preservation
|
|
|
103
115
|
@read_timeout || raise(Error, 'read timeout has not been configured')
|
|
104
116
|
end
|
|
105
117
|
|
|
118
|
+
def retry_max
|
|
119
|
+
@retry_max || raise(Error, 'retry_max has not been configured')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def retry_interval
|
|
123
|
+
@retry_interval || raise(Error, 'retry_interval has not been configured')
|
|
124
|
+
end
|
|
125
|
+
|
|
106
126
|
def connection
|
|
107
|
-
@connection ||=
|
|
127
|
+
@connection ||= build_connection(with_retry: true)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def streaming_connection
|
|
131
|
+
@streaming_connection ||= build_connection(with_retry: false)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def build_connection(with_retry: true) # rubocop:disable Metrics/AbcSize
|
|
135
|
+
Faraday.new(url, request: { read_timeout: read_timeout }) do |builder|
|
|
108
136
|
builder.use ErrorFaradayMiddleware
|
|
137
|
+
if with_retry
|
|
138
|
+
builder.request :retry, max: retry_max,
|
|
139
|
+
interval: retry_interval,
|
|
140
|
+
backoff_factor: RETRY_BACKOFF_FACTOR,
|
|
141
|
+
methods: [:get],
|
|
142
|
+
exceptions: Faraday::Retry::Middleware::DEFAULT_EXCEPTIONS +
|
|
143
|
+
[Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ServerError]
|
|
144
|
+
end
|
|
109
145
|
builder.use Faraday::Request::UrlEncoded
|
|
110
|
-
builder.use Faraday::Response::RaiseError
|
|
146
|
+
builder.use Faraday::Response::RaiseError
|
|
111
147
|
builder.adapter Faraday.default_adapter
|
|
112
148
|
builder.headers[:user_agent] = user_agent
|
|
113
149
|
builder.headers[TOKEN_HEADER] = "Bearer #{token}"
|
data/preservation-client.gemspec
CHANGED
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
|
|
|
31
31
|
|
|
32
32
|
spec.add_dependency 'activesupport', '>= 4.2'
|
|
33
33
|
spec.add_dependency 'faraday', '~> 2.0'
|
|
34
|
+
spec.add_dependency 'faraday-retry', '~> 2.0'
|
|
34
35
|
spec.add_dependency 'moab-versioning', '>= 5.0.0', '< 7'
|
|
35
36
|
spec.add_dependency 'zeitwerk', '~> 2.1'
|
|
36
37
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: preservation-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 8.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Naomi Dushay
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-07-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: faraday-retry
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '2.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '2.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: moab-versioning
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|