sdr-client 0.97.0 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c562c90ba48ff8d6168c1c7488537bde46faab61ec81963de5917776ffa9f079
4
- data.tar.gz: 1e22559c270a5312f975a382c87daac40af750c24755f62c02873775d07c0855
3
+ metadata.gz: 1889b3e5a4bda3ce260dd4a0d9671f8ce3ab0db9ae7f05584d18f9f556e7bd0a
4
+ data.tar.gz: aa6ad237bd7febc4c74d40fdff148faa9ede2c0a560b010bce7d42b3cb295589
5
5
  SHA512:
6
- metadata.gz: 5aa3b0ff99dfedf0751c5c0f9782d5c17b0092a5aa98a222e9f15c0011945f6fa67403f82334bcdfc6d405883e49682032dee0b498a1a0f56d7b84a52cf36756
7
- data.tar.gz: ed9acb2561e273f516804b60b734d3a4c900b79c3648bfa62cde33d3164ab42e27d13d675233ca45f3f1f0d75314cca3702810a0a9ed3a9f7b68359332f12a89
6
+ metadata.gz: 9d5f381820d655416d1c8da19db0911af0a016d83fe4970f1a64d03bc8f9f2709f93c05ff0f367ad9bcb1e94f44fed86ef61798312a98660e3bf0e8ca2f1dfbc
7
+ data.tar.gz: 8b5dadcc030463318a87444611294571af60c18bd35d52fe5d0c89ddf7dbd71e4ecf66ec9df3bb21a60a4846cedf3f9e3b6d77835a14a959ec7268d75a17f618
data/.rubocop.yml CHANGED
@@ -27,6 +27,9 @@ Naming/FileName:
27
27
  Exclude:
28
28
  - 'lib/sdr-client.rb'
29
29
 
30
+ RSpec/MultipleMemoizedHelpers:
31
+ Enabled: false
32
+
30
33
  Gemspec/DeprecatedAttributeAssignment: # new in 1.10
31
34
  Enabled: true
32
35
  Gemspec/RequireMFA: # new in 1.23
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sdr-client (0.97.0)
4
+ sdr-client (2.0.0)
5
5
  activesupport
6
6
  cocina-models (~> 0.86.0)
7
7
  dry-monads
@@ -18,7 +18,7 @@ GEM
18
18
  addressable (2.8.1)
19
19
  public_suffix (>= 2.0.2, < 6.0)
20
20
  ast (2.4.2)
21
- attr_extras (6.2.5)
21
+ attr_extras (7.0.0)
22
22
  byebug (11.1.3)
23
23
  cocina-models (0.86.0)
24
24
  activesupport
@@ -37,73 +37,94 @@ GEM
37
37
  zeitwerk (~> 2.1)
38
38
  commonmarker (0.23.6)
39
39
  concurrent-ruby (1.1.10)
40
+ config (4.1.0)
41
+ deep_merge (~> 1.2, >= 1.2.1)
42
+ dry-validation (~> 1.0, >= 1.0.0)
40
43
  crack (0.4.5)
41
44
  rexml
45
+ deep_merge (1.2.2)
42
46
  deprecation (1.1.0)
43
47
  activesupport
44
48
  diff-lcs (1.5.0)
45
49
  docile (1.4.0)
46
- dry-container (0.11.0)
50
+ dry-configurable (1.0.1)
51
+ dry-core (~> 1.0, < 2)
52
+ zeitwerk (~> 2.6)
53
+ dry-core (1.0.0)
47
54
  concurrent-ruby (~> 1.0)
48
- dry-core (0.9.1)
55
+ zeitwerk (~> 2.6)
56
+ dry-inflector (1.0.0)
57
+ dry-initializer (3.1.1)
58
+ dry-logic (1.5.0)
49
59
  concurrent-ruby (~> 1.0)
60
+ dry-core (~> 1.0, < 2)
50
61
  zeitwerk (~> 2.6)
51
- dry-inflector (0.3.0)
52
- dry-logic (1.3.0)
62
+ dry-monads (1.6.0)
53
63
  concurrent-ruby (~> 1.0)
54
- dry-core (~> 0.9, >= 0.9)
64
+ dry-core (~> 1.0, < 2)
55
65
  zeitwerk (~> 2.6)
56
- dry-monads (1.5.0)
66
+ dry-schema (1.13.0)
57
67
  concurrent-ruby (~> 1.0)
58
- dry-core (~> 0.9, >= 0.9)
68
+ dry-configurable (~> 1.0, >= 1.0.1)
69
+ dry-core (~> 1.0, < 2)
70
+ dry-initializer (~> 3.0)
71
+ dry-logic (>= 1.5, < 2)
72
+ dry-types (>= 1.7, < 2)
59
73
  zeitwerk (~> 2.6)
60
- dry-struct (1.5.2)
61
- dry-core (~> 0.9, >= 0.9)
62
- dry-types (~> 1.6)
74
+ dry-struct (1.6.0)
75
+ dry-core (~> 1.0, < 2)
76
+ dry-types (>= 1.7, < 2)
63
77
  ice_nine (~> 0.11)
64
78
  zeitwerk (~> 2.6)
65
- dry-types (1.6.1)
79
+ dry-types (1.7.0)
80
+ concurrent-ruby (~> 1.0)
81
+ dry-core (~> 1.0, < 2)
82
+ dry-inflector (~> 1.0, < 2)
83
+ dry-logic (>= 1.4, < 2)
84
+ zeitwerk (~> 2.6)
85
+ dry-validation (1.10.0)
66
86
  concurrent-ruby (~> 1.0)
67
- dry-container (~> 0.3)
68
- dry-core (~> 0.9, >= 0.9)
69
- dry-inflector (~> 0.1, >= 0.1.2)
70
- dry-logic (~> 1.3, >= 1.3)
87
+ dry-core (~> 1.0, < 2)
88
+ dry-initializer (~> 3.0)
89
+ dry-schema (>= 1.12, < 2)
71
90
  zeitwerk (~> 2.6)
72
91
  edtf (3.1.0)
73
92
  activesupport (>= 3.0, < 8.0)
74
93
  equivalent-xml (0.6.0)
75
94
  nokogiri (>= 1.4.3)
76
- faraday (2.6.0)
95
+ faraday (2.7.1)
77
96
  faraday-net_http (>= 2.0, < 3.1)
78
97
  ruby2_keywords (>= 0.0.4)
79
- faraday-net_http (3.0.1)
98
+ faraday-net_http (3.0.2)
80
99
  hashdiff (1.0.1)
81
100
  i18n (1.12.0)
82
101
  concurrent-ruby (~> 1.0)
83
102
  ice_nine (0.11.2)
84
- json (2.6.2)
103
+ json (2.6.3)
85
104
  jsonpath (1.1.2)
86
105
  multi_json
106
+ launchy (2.5.0)
107
+ addressable (~> 2.7)
87
108
  minitest (5.16.3)
88
109
  multi_json (1.15.0)
89
- nokogiri (1.13.9-x86_64-darwin)
110
+ nokogiri (1.13.10-x86_64-darwin)
90
111
  racc (~> 1.4)
91
- nokogiri (1.13.9-x86_64-linux)
112
+ nokogiri (1.13.10-x86_64-linux)
92
113
  racc (~> 1.4)
93
114
  openapi3_parser (0.9.2)
94
115
  commonmarker (~> 0.17)
95
116
  openapi_parser (0.15.0)
96
117
  optimist (3.0.1)
97
118
  parallel (1.22.1)
98
- parser (3.1.2.1)
119
+ parser (3.1.3.0)
99
120
  ast (~> 2.4.1)
100
121
  patience_diff (1.2.0)
101
122
  optimist (~> 3.0)
102
- public_suffix (5.0.0)
103
- racc (1.6.0)
123
+ public_suffix (5.0.1)
124
+ racc (1.6.1)
104
125
  rainbow (3.1.1)
105
126
  rake (13.0.6)
106
- regexp_parser (2.6.0)
127
+ regexp_parser (2.6.1)
107
128
  rexml (3.2.5)
108
129
  rspec (3.12.0)
109
130
  rspec-core (~> 3.12.0)
@@ -114,7 +135,7 @@ GEM
114
135
  rspec-expectations (3.12.0)
115
136
  diff-lcs (>= 1.2.0, < 2.0)
116
137
  rspec-support (~> 3.12.0)
117
- rspec-mocks (3.12.0)
138
+ rspec-mocks (3.12.1)
118
139
  diff-lcs (>= 1.2.0, < 2.0)
119
140
  rspec-support (~> 3.12.0)
120
141
  rspec-support (3.12.0)
@@ -122,7 +143,7 @@ GEM
122
143
  rspec-core (>= 2, < 4, != 2.12.0)
123
144
  rss (0.2.9)
124
145
  rexml
125
- rubocop (1.37.1)
146
+ rubocop (1.40.0)
126
147
  json (~> 2.3)
127
148
  parallel (~> 1.10)
128
149
  parser (>= 3.1.2.1)
@@ -132,11 +153,11 @@ GEM
132
153
  rubocop-ast (>= 1.23.0, < 2.0)
133
154
  ruby-progressbar (~> 1.7)
134
155
  unicode-display_width (>= 1.4.0, < 3.0)
135
- rubocop-ast (1.23.0)
156
+ rubocop-ast (1.24.0)
136
157
  parser (>= 3.1.1.0)
137
158
  rubocop-rake (0.6.0)
138
159
  rubocop (~> 1.0)
139
- rubocop-rspec (2.14.2)
160
+ rubocop-rspec (2.15.0)
140
161
  rubocop (~> 1.33)
141
162
  ruby-progressbar (1.11.0)
142
163
  ruby2_keywords (0.0.5)
@@ -158,7 +179,7 @@ GEM
158
179
  addressable (>= 2.8.0)
159
180
  crack (>= 0.3.2)
160
181
  hashdiff (>= 0.4.0, < 2.0.0)
161
- zeitwerk (2.6.4)
182
+ zeitwerk (2.6.6)
162
183
 
163
184
  PLATFORMS
164
185
  x86_64-darwin-19
@@ -168,6 +189,8 @@ PLATFORMS
168
189
  DEPENDENCIES
169
190
  bundler (~> 2.0)
170
191
  byebug
192
+ config
193
+ launchy
171
194
  rake (~> 13.0)
172
195
  rspec (~> 3.0)
173
196
  rspec_junit_formatter
@@ -0,0 +1,4 @@
1
+ authentication_proxy_url:
2
+ 'https://sdr-api-prod.stanford.edu': 'https://argo.stanford.edu/settings/tokens'
3
+ 'https://sdr-api-qa.stanford.edu': 'https://argo-qa.stanford.edu/settings/tokens'
4
+ 'https://sdr-api-stage.stanford.edu': 'https://argo-stage.stanford.edu/settings/tokens'
data/exe/sdr CHANGED
@@ -4,5 +4,6 @@
4
4
  $LOAD_PATH.unshift 'lib'
5
5
 
6
6
  require 'sdr_client'
7
+ require 'sdr_client/cli'
7
8
 
8
9
  SdrClient::CLI.start(ARGV)
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'config'
4
+
5
+ Config.setup do |config|
6
+ # Name of the constant exposing loaded settings
7
+ config.const_name = 'Settings'
8
+
9
+ # Load environment variables from the `ENV` object and override any
10
+ # settings defined in files.
11
+ config.use_env = true
12
+
13
+ # Define ENV variable prefix deciding which variables to load into
14
+ # config.
15
+ config.env_prefix = 'SETTINGS'
16
+
17
+ # What string to use as level separator for settings loaded from ENV
18
+ # variables. Default value of '.' works well with Heroku, but you might
19
+ # want to change it for example for '__' to easy override settings from
20
+ # command line, where using dots in variable names might not be allowed
21
+ # (eg. Bash).
22
+ config.env_separator = '__'
23
+
24
+ # Ability to process variables names:
25
+ # * nil - no change
26
+ # * :downcase - convert to lower case
27
+ config.env_converter = :downcase
28
+ end
29
+
30
+ Config.load_and_set_settings('config/settings.yml')
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'launchy'
3
4
  require 'thor'
5
+ require_relative 'cli/config'
4
6
 
5
7
  module SdrClient
6
8
  # The SDR command-line interface
@@ -31,32 +33,19 @@ module SdrClient
31
33
 
32
34
  desc 'get DRUID', 'Retrieve an object from the SDR'
33
35
  def get(druid)
34
- say SdrClient::Find.run(druid, url: options[:url], logger: Logger.new($stderr))
35
- rescue SdrClient::Credentials::NoCredentialsError
36
- say_error 'Log in first'
37
- exit(1)
36
+ rescue_expected_exceptions do
37
+ say Find.run(druid, url: options[:url], logger: Logger.new($stderr))
38
+ end
38
39
  end
39
40
 
40
- desc 'login', 'Prompt for email & password and create a login token (saved in ~/.sdr/token)'
41
+ desc 'login', 'Open authentication proxy UI, or prompt for username and password, and then prompt for token (saved in ~/.sdr/credentials)'
41
42
  def login
42
- status = SdrClient::Login.run(
43
- url: options[:url],
44
- login_service: lambda do
45
- {
46
- email: ask('Email:'),
47
- password: ask('Password:', echo: false)
48
- }
49
- end
50
- )
51
- return puts unless status.failure?
52
-
53
- say_error status.failure
54
- exit(1)
43
+ authentication_proxy_url ? login_via_proxy : login_via_credentials
55
44
  end
56
45
 
57
46
  desc 'version', 'Display the SDR CLI version'
58
47
  def version
59
- say SdrClient::VERSION
48
+ say VERSION
60
49
  end
61
50
 
62
51
  desc 'update DRUID', 'Update an object in the SDR'
@@ -72,13 +61,13 @@ module SdrClient
72
61
  option :cdl, type: :boolean, default: false, desc: 'Controlled digital lending'
73
62
  option :cocina_file, desc: 'Path to a file containing Cocina JSON'
74
63
  option :cocina_pipe, type: :boolean, default: false, desc: 'Indicate Cocina JSON is being piped in'
64
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
75
65
  def update(druid)
76
- validate_druid!(druid)
77
- job_id = SdrClient::Update.run(druid, **options)
78
- poll_for_job_complete(job_id: job_id, url: options[:url])
79
- rescue SdrClient::Credentials::NoCredentialsError
80
- say_error 'Log in first'
81
- exit(1)
66
+ rescue_expected_exceptions do
67
+ validate_druid!(druid)
68
+ job_id = Update.run(druid, **options)
69
+ poll_for_job_complete(job_id: job_id, url: options[:url])
70
+ end
82
71
  end
83
72
 
84
73
  desc 'deposit OPTIONAL_FILES', 'Deposit (accession) an object into the SDR'
@@ -95,6 +84,7 @@ module SdrClient
95
84
  option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
96
85
  option :files_metadata, desc: 'JSON string representing per-file metadata'
97
86
  option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
87
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
98
88
  def deposit(*files)
99
89
  register_or_deposit(files: files, accession: true)
100
90
  end
@@ -113,22 +103,70 @@ module SdrClient
113
103
  option :view, enum: %w[world stanford location-based citation-only dark], desc: 'Access view level for the object'
114
104
  option :files_metadata, desc: 'JSON string representing per-file metadata'
115
105
  option :grouping_strategy, enum: %w[default filename], desc: 'Strategy for grouping files into filesets'
106
+ option :basepath, default: Dir.getwd, desc: 'Base path for the files'
116
107
  def register(*files)
117
108
  register_or_deposit(files: files, accession: false)
118
109
  end
119
110
 
120
111
  private
121
112
 
122
- def register_or_deposit(files:, accession:)
123
- opts = munge_options(options, files)
124
- skip_polling = opts.delete(:skip_polling)
125
- job_id = SdrClient::Deposit.run(accession: accession, **opts)
126
- return if skip_polling
127
-
128
- poll_for_job_complete(job_id: job_id, url: opts[:url])
129
- rescue SdrClient::Credentials::NoCredentialsError
130
- say_error 'Log in first'
113
+ def rescue_expected_exceptions
114
+ yield
115
+ rescue UnexpectedResponse::TokenExpired
116
+ say_error 'Token has expired! Please log in again.'
131
117
  exit(1)
118
+ rescue Credentials::NoCredentialsError
119
+ say_error 'No token found! Please log in first.'
120
+ exit(1)
121
+ end
122
+
123
+ def login_via_proxy
124
+ say 'Opened the configured authentication proxy in your browser. Once there, generate a new token and copy the entire value.'
125
+ Launchy.open(authentication_proxy_url)
126
+ # Some CLI environments will pop up a message about opening the URL in
127
+ # an existing browse. Since this is OS-dependency, and not something
128
+ # we can control via Launchy, just wait a bit before rendering the
129
+ # `ask` prompt so it's clearer to the user what's happening
130
+ sleep 0.5
131
+ token_string = ask('Paste token here:')
132
+ Credentials.write(token_string)
133
+ expiry = JSON.parse(token_string)['exp']
134
+ say "You are now authenticated for #{options[:url]} until #{expiry}"
135
+ rescue StandardError => e
136
+ say_error "Error logging in via proxy: #{e}"
137
+ exit(1)
138
+ end
139
+
140
+ def login_via_credentials
141
+ status = Login.run(
142
+ url: options[:url],
143
+ login_service: lambda do
144
+ {
145
+ email: ask('Email:'),
146
+ password: ask('Password:', echo: false)
147
+ }
148
+ end
149
+ )
150
+
151
+ return puts unless status.failure?
152
+
153
+ say_error status.failure
154
+ exit(1)
155
+ end
156
+
157
+ def authentication_proxy_url
158
+ @authentication_proxy_url ||= Settings.authentication_proxy_url[options[:url]]
159
+ end
160
+
161
+ def register_or_deposit(files:, accession:)
162
+ rescue_expected_exceptions do
163
+ opts = munge_options(options, files)
164
+ skip_polling = opts.delete(:skip_polling)
165
+ job_id = Deposit.run(accession: accession, **opts)
166
+ return if skip_polling
167
+
168
+ poll_for_job_complete(job_id: job_id, url: opts[:url])
169
+ end
132
170
  end
133
171
 
134
172
  def munge_options(options, files)
@@ -138,9 +176,9 @@ module SdrClient
138
176
  opts[:files_metadata] = JSON.parse(options[:files_metadata]) if options[:files_metadata]
139
177
  if options[:grouping_strategy]
140
178
  opts[:grouping_strategy] = if options[:grouping_strategy] == 'filename'
141
- SdrClient::Deposit::MatchingFileGroupingStrategy
179
+ Deposit::MatchingFileGroupingStrategy
142
180
  else
143
- SdrClient::Deposit::SingleFileGroupingStrategy
181
+ Deposit::SingleFileGroupingStrategy
144
182
  end
145
183
  end
146
184
  end
@@ -158,7 +196,7 @@ module SdrClient
158
196
  say('SDR is processing your request.', nil, false)
159
197
  result = nil
160
198
  1.upto(60) do
161
- result = SdrClient::BackgroundJobResults.show(url: url, job_id: job_id)
199
+ result = BackgroundJobResults.show(url: url, job_id: job_id)
162
200
  break unless %w[pending processing].include?(result['status'])
163
201
 
164
202
  # the extra args to `say` prevent appending a newline
@@ -18,25 +18,26 @@ module SdrClient
18
18
 
19
19
  # @param (see #initialize)
20
20
  # @return (see #build)
21
- def self.build(files:, files_metadata:)
22
- new(files: files, files_metadata: files_metadata.dup).build
21
+ def self.build(files:, files_metadata:, basepath:)
22
+ new(files: files, files_metadata: files_metadata.dup, basepath: basepath).build
23
23
  end
24
24
 
25
- # @param [Array<String>] files the list of files for which to generate metadata
26
- def initialize(files:, files_metadata:)
25
+ # @param [Array<String>] files the list of relative filepaths for which to generate metadata
26
+ def initialize(files:, files_metadata:, basepath:)
27
27
  @files = files
28
28
  @files_metadata = files_metadata
29
+ @basepath = basepath
29
30
  end
30
31
 
31
- # @return [Hash<String, Hash<String, String>>]
32
+ # @return [Hash<String, Hash<String, String>>] a map of relative filepaths to a map of metadata
32
33
  def build
33
- files.each do |file_path|
34
+ files.each do |filepath|
34
35
  OPERATIONS.each do |operation|
35
- result = operation.for(file_path: file_path)
36
+ result = operation.for(filepath: absolute_filepath_for(filepath))
36
37
  next if result.nil?
37
38
 
38
- files_metadata[file_path] ||= {}
39
- files_metadata[file_path][operation::NAME] = result
39
+ files_metadata[filepath] ||= {}
40
+ files_metadata[filepath][operation::NAME] = result
40
41
  end
41
42
  end
42
43
  files_metadata
@@ -44,7 +45,11 @@ module SdrClient
44
45
 
45
46
  private
46
47
 
47
- attr_reader :files, :files_metadata
48
+ attr_reader :files, :files_metadata, :basepath
49
+
50
+ def absolute_filepath_for(filepath)
51
+ ::File.join(basepath, filepath)
52
+ end
48
53
  end
49
54
  end
50
55
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # MD5 for this file.
9
9
  class MD5
10
10
  NAME = 'md5'
11
- def self.for(file_path:, **)
12
- Digest::MD5.file(file_path).hexdigest
11
+ def self.for(filepath:, **)
12
+ Digest::MD5.file(filepath).hexdigest
13
13
  end
14
14
  end
15
15
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # Mime-type for this file.
9
9
  class MimeType
10
10
  NAME = 'mime_type'
11
- def self.for(file_path:, **)
12
- argv = Shellwords.escape(file_path)
11
+ def self.for(filepath:, **)
12
+ argv = Shellwords.escape(filepath)
13
13
  `file --mime-type -b #{argv}`.chomp
14
14
  end
15
15
  end
@@ -8,8 +8,8 @@ module SdrClient
8
8
  # SHA1 for this file.
9
9
  class SHA1
10
10
  NAME = 'sha1'
11
- def self.for(file_path:, **)
12
- Digest::SHA1.file(file_path).hexdigest
11
+ def self.for(filepath:, **)
12
+ Digest::SHA1.file(filepath).hexdigest
13
13
  end
14
14
  end
15
15
  end
@@ -11,17 +11,20 @@ module SdrClient
11
11
  # @param [Boolean] accession should the accessionWF be started
12
12
  # @param [String] priority (nil) what processing priority should be used
13
13
  # either 'low' or 'default'
14
- # @param [Array<String>] files a list of file names to upload
14
+ # @param [Array<String>] files a list of relative filepaths to upload
15
+ # @param [String] basepath filepath to which filepaths are relative
15
16
  # @param [Boolean] assign_doi should a DOI be assigned to this item
16
17
  # @param [Logger] logger the logger to use
17
18
  def initialize(request_dro:, # rubocop:disable Metrics/ParameterLists
18
19
  connection:,
19
20
  accession:,
21
+ basepath:,
20
22
  priority: nil,
21
23
  files: [],
22
24
  assign_doi: false,
23
25
  logger: Logger.new($stdout))
24
26
  @files = files
27
+ @basepath = basepath
25
28
  @connection = connection
26
29
  @request_dro = request_dro
27
30
  @logger = logger
@@ -34,10 +37,14 @@ module SdrClient
34
37
  check_files_exist
35
38
  child_files_match
36
39
 
37
- file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types)
40
+ # file_metadata is a map of relative filepaths to Files::DirectUploadRequests
41
+ file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types, basepath: basepath)
42
+ # upload_response is an array of Files::DirectUploadResponse
38
43
  upload_responses = UploadFiles.upload(file_metadata: file_metadata,
44
+ filepath_map: filepath_map,
39
45
  logger: logger,
40
46
  connection: connection)
47
+
41
48
  new_request_dro = UpdateDroWithFileIdentifiers.update(request_dro: request_dro, upload_responses: upload_responses)
42
49
  CreateResource.run(accession: @accession,
43
50
  priority: @priority,
@@ -49,12 +56,12 @@ module SdrClient
49
56
 
50
57
  private
51
58
 
52
- attr_reader :request_dro, :files, :logger, :connection
59
+ attr_reader :request_dro, :files, :logger, :connection, :basepath
53
60
 
54
61
  def check_files_exist
55
62
  logger.info('checking to see if files exist')
56
- files.each do |file_name|
57
- raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
63
+ files.each do |filepath|
64
+ raise Errno::ENOENT, filepath unless ::File.exist?(absolute_filepath_for(filepath))
58
65
  end
59
66
  end
60
67
 
@@ -70,7 +77,7 @@ module SdrClient
70
77
  end
71
78
  end
72
79
 
73
- # Map of filenames to mimetypes
80
+ # Map of relative filepaths to mimetypes
74
81
  def mime_types
75
82
  @mime_types ||=
76
83
  request_files.transform_values do |file|
@@ -78,7 +85,7 @@ module SdrClient
78
85
  end
79
86
  end
80
87
 
81
- # Map of filenames to request files
88
+ # Map of absolute filepaths to Cocina::Models::RequestFiles
82
89
  def request_files
83
90
  @request_files ||= begin
84
91
  return {} unless request_dro.structural
@@ -90,6 +97,16 @@ module SdrClient
90
97
  end.flatten(1).to_h
91
98
  end
92
99
  end
100
+
101
+ def absolute_filepath_for(filename)
102
+ ::File.join(basepath, filename)
103
+ end
104
+
105
+ def filepath_map
106
+ @filepath_map ||= files.each_with_object({}) do |filepath, obj|
107
+ obj[filepath] = absolute_filepath_for(filepath)
108
+ end
109
+ end
93
110
  end
94
111
  end
95
112
  end
@@ -13,7 +13,8 @@ module SdrClient
13
13
  # either 'low' or 'default'
14
14
  # @param [Class] grouping_strategy class whose run method groups an array of uploads
15
15
  # @param [Class] file_set_type_strategy class whose run method determines file_set type
16
- # @param [Array<String>] files a list of file names to upload
16
+ # @param [Array<String>] files a list of relative filepaths to upload
17
+ # @param [String] basepath filepath to which filepaths are relative
17
18
  # @param [Boolean] assign_doi should a DOI be assigned to this item
18
19
  # @param [Logger] logger the logger to use
19
20
  #
@@ -21,6 +22,7 @@ module SdrClient
21
22
  def initialize(metadata:,
22
23
  connection:,
23
24
  accession:,
25
+ basepath:,
24
26
  priority: nil,
25
27
  grouping_strategy: SingleFileGroupingStrategy,
26
28
  file_set_type_strategy: FileTypeFileSetStrategy,
@@ -36,6 +38,7 @@ module SdrClient
36
38
  @accession = accession
37
39
  @priority = priority
38
40
  @assign_doi = assign_doi
41
+ @basepath = basepath
39
42
  end
40
43
  # rubocop:enable Metrics/ParameterLists
41
44
 
@@ -44,8 +47,9 @@ module SdrClient
44
47
  def run
45
48
  check_files_exist
46
49
 
47
- file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types)
50
+ file_metadata = UploadFilesMetadataBuilder.build(files: files, mime_types: mime_types, basepath: basepath)
48
51
  upload_responses = UploadFiles.upload(file_metadata: file_metadata,
52
+ filepath_map: filepath_map,
49
53
  logger: logger,
50
54
  connection: connection)
51
55
  metadata_builder = MetadataBuilder.new(metadata: metadata,
@@ -65,12 +69,12 @@ module SdrClient
65
69
 
66
70
  private
67
71
 
68
- attr_reader :metadata, :files, :connection, :logger, :grouping_strategy, :file_set_type_strategy
72
+ attr_reader :metadata, :files, :connection, :logger, :grouping_strategy, :file_set_type_strategy, :basepath
69
73
 
70
74
  def check_files_exist
71
75
  logger.info('checking to see if files exist')
72
- files.each do |file_name|
73
- raise Errno::ENOENT, file_name unless ::File.exist?(file_name)
76
+ files.each do |filepath|
77
+ raise Errno::ENOENT, filepath unless ::File.exist?(absolute_filepath_for(filepath))
74
78
  end
75
79
  end
76
80
 
@@ -80,6 +84,16 @@ module SdrClient
80
84
  [filepath, metadata.for(filepath)['mime_type']]
81
85
  end
82
86
  end
87
+
88
+ def filepath_map
89
+ @filepath_map ||= files.each_with_object({}) do |filepath, obj|
90
+ obj[filepath] = absolute_filepath_for(filepath)
91
+ end
92
+ end
93
+
94
+ def absolute_filepath_for(filepath)
95
+ ::File.join(basepath, filepath)
96
+ end
83
97
  end
84
98
  end
85
99
  end
@@ -8,40 +8,43 @@ module SdrClient
8
8
  class UploadFiles
9
9
  BLOB_PATH = '/v1/direct_uploads'
10
10
 
11
- # @param [Hash<String,Files::DirectUploadRequest>] the metadata for uploading the files
11
+ # @param [Hash<String,Files::DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
12
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
12
13
  # @param [Logger] logger the logger to use
13
14
  # @param [Connection] connection
14
- def self.upload(file_metadata:, logger:, connection:)
15
- new(file_metadata: file_metadata, logger: logger, connection: connection).run
15
+ def self.upload(file_metadata:, filepath_map:, logger:, connection:)
16
+ new(file_metadata: file_metadata, filepath_map: filepath_map, logger: logger, connection: connection).run
16
17
  end
17
18
 
18
- # @param [Hash<String,Files::DirectUploadRequest>] the metadata for uploading the files
19
+ # @param [Hash<String,Files::DirectUploadRequest>] file_metadata map of relative filepaths to file metadata
20
+ # @param [Hash<String,String>] filepath_map map of relative filepaths to absolute filepaths
19
21
  # @param [Logger] logger the logger to use
20
22
  # @param [Connection] connection
21
- def initialize(file_metadata:, logger:, connection:)
23
+ def initialize(file_metadata:, filepath_map:, logger:, connection:)
22
24
  @file_metadata = file_metadata
25
+ @filepath_map = filepath_map
23
26
  @logger = logger
24
27
  @connection = connection
25
28
  end
26
29
 
27
30
  # @return [Array<Files::DirectUploadResponse>] the responses from the server for the uploads
28
31
  def run
29
- file_metadata.map do |filename, metadata|
32
+ file_metadata.map do |filepath, metadata|
30
33
  direct_upload(metadata.to_json).tap do |response|
31
- # ActiveStorage modifies the filename provided in response, so setting here.
32
- response.filename = filename
33
- upload_file(filename: filename,
34
+ # ActiveStorage modifies the filename provided in response, so setting here with the relative filename
35
+ response.filename = filepath
36
+ upload_file(filename: filepath,
34
37
  url: response.direct_upload.fetch('url'),
35
38
  content_type: response.content_type,
36
39
  content_length: response.byte_size)
37
- logger.info("Upload of #{filename} complete")
40
+ logger.info("Upload of #{filepath} complete")
38
41
  end
39
42
  end
40
43
  end
41
44
 
42
45
  private
43
46
 
44
- attr_reader :logger, :connection, :file_metadata
47
+ attr_reader :logger, :connection, :file_metadata, :filepath_map
45
48
 
46
49
  def direct_upload(metadata_json)
47
50
  logger.info("Starting an upload request: #{metadata_json}")
@@ -63,7 +66,7 @@ module SdrClient
63
66
  logger.info("Uploading `#{filename}' to #{url}")
64
67
 
65
68
  upload_response = connection.put(url) do |req|
66
- req.body = ::File.open(filename)
69
+ req.body = ::File.open(filepath_map[filename])
67
70
  req.headers['Content-Type'] = content_type
68
71
  req.headers['Content-Length'] = content_length.to_s
69
72
  end
@@ -6,39 +6,36 @@ module SdrClient
6
6
  module Deposit
7
7
  # Collecting all the metadata about the files for a deposit
8
8
  class UploadFilesMetadataBuilder
9
- # @param [Array<String>] files a list of filepaths to upload
9
+ # @param [Array<String>] files a list of relative filepaths to upload
10
10
  # @param [Hash<String,String>] mime_types a map of filenames to mime types
11
+ # @param [String] basepath path to which files are relative
11
12
  # @return [Hash<String, Files::DirectUploadRequest>] the metadata for uploading the files
12
- def self.build(files:, mime_types:)
13
- new(files: files, mime_types: mime_types).build
13
+ def self.build(files:, mime_types:, basepath:)
14
+ new(files: files, mime_types: mime_types, basepath: basepath).build
14
15
  end
15
16
 
16
- # @param [Array<String>] files a list of filepaths to upload
17
+ # @param [Array<String>] files a list of absolute filepaths to upload
17
18
  # @param [Hash<String,String>] mime_types a map of filenames to mime types
18
- def initialize(files:, mime_types:)
19
+ # @param [String] basepath path to which files are relative
20
+ def initialize(files:, mime_types:, basepath:)
19
21
  @files = files
20
22
  @mime_types = mime_types
23
+ @basepath = basepath
21
24
  end
22
25
 
23
- attr_reader :files, :mime_types
26
+ attr_reader :files, :mime_types, :basepath
24
27
 
25
28
  # @return [Hash<String, Files::DirectUploadRequest>] the metadata for uploading the files
26
29
  def build
27
- files.each_with_object({}) do |path, obj|
28
- obj[path] = Files::DirectUploadRequest.from_file(path,
29
- file_name: filename_for(path),
30
- content_type: mime_type_for(path))
30
+ files.each_with_object({}) do |filepath, obj|
31
+ obj[filepath] = Files::DirectUploadRequest.from_file(absolute_filepath_for(filepath),
32
+ file_name: filepath,
33
+ content_type: mime_types[filepath])
31
34
  end
32
35
  end
33
36
 
34
- # This can be overridden in the case that the file on disk has a different
35
- # name than we want to repo to know about.
36
- def filename_for(file_path)
37
- file_path
38
- end
39
-
40
- def mime_type_for(file_path)
41
- mime_types[filename_for(file_path)]
37
+ def absolute_filepath_for(filepath)
38
+ ::File.join(basepath, filepath)
42
39
  end
43
40
  end
44
41
  end
@@ -8,6 +8,9 @@ module SdrClient
8
8
  BOOK_TYPE = Cocina::Models::ObjectType.book
9
9
  # rubocop:disable Metrics/ParameterLists
10
10
  # rubocop:disable Metrics/MethodLength
11
+ # params [Array<String>] files a list of relative filepaths to upload
12
+ # params [String] basepath filepath to which filepaths are relative, defaults to current directory
13
+ # params [Hash<String,Hash>] file_metadata relative filepath, hash of metadata per-file metadata
11
14
  # @return [String] job id for the background job result
12
15
  def self.run(label: nil,
13
16
  type: BOOK_TYPE,
@@ -26,29 +29,34 @@ module SdrClient
26
29
  url:,
27
30
  files: [],
28
31
  files_metadata: {},
32
+ basepath: Dir.getwd,
29
33
  accession: false,
30
34
  priority: nil,
31
35
  grouping_strategy: SingleFileGroupingStrategy,
32
36
  file_set_type_strategy: FileTypeFileSetStrategy,
33
37
  logger: Logger.new($stdout))
34
- augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata)
35
- metadata = Request.new(label: label,
36
- type: type,
37
- view: view,
38
- download: download,
39
- apo: apo,
40
- use_and_reproduction: use_and_reproduction,
41
- copyright: copyright,
42
- collection: collection,
43
- source_id: source_id,
44
- catkey: catkey,
45
- embargo_release_date: embargo_release_date,
46
- embargo_access: embargo_access,
47
- embargo_download: embargo_download,
48
- viewing_direction: viewing_direction,
49
- files_metadata: augmented_metadata)
38
+ # augmented_metadata is a map of relative filepaths to file metadata
39
+ augmented_metadata = FileMetadataBuilder.build(files: files, files_metadata: files_metadata, basepath: basepath)
40
+ request = Request.new(label: label,
41
+ type: type,
42
+ view: view,
43
+ download: download,
44
+ apo: apo,
45
+ use_and_reproduction: use_and_reproduction,
46
+ copyright: copyright,
47
+ collection: collection,
48
+ source_id: source_id,
49
+ catkey: catkey,
50
+ embargo_release_date: embargo_release_date,
51
+ embargo_access: embargo_access,
52
+ embargo_download: embargo_download,
53
+ viewing_direction: viewing_direction,
54
+ files_metadata: augmented_metadata)
50
55
  connection = Connection.new(url: url)
51
- Process.new(metadata: metadata, connection: connection, files: files,
56
+ Process.new(metadata: request,
57
+ connection: connection,
58
+ files: files,
59
+ basepath: basepath,
52
60
  grouping_strategy: grouping_strategy,
53
61
  file_set_type_strategy: file_set_type_strategy,
54
62
  accession: accession,
@@ -57,9 +65,11 @@ module SdrClient
57
65
  end
58
66
  # rubocop:enable Metrics/MethodLength
59
67
 
60
- # @param [Array<String>] files absolute paths to files
68
+ # @param [Array<String>] files relative paths to files
69
+ # @params [String] basepath path to which files are relative
61
70
  def self.model_run(request_dro:,
62
71
  files: [],
72
+ basepath: Dir.getwd,
63
73
  url:,
64
74
  accession:,
65
75
  priority: nil,
@@ -68,6 +78,7 @@ module SdrClient
68
78
  ModelProcess.new(request_dro: request_dro,
69
79
  connection: connection,
70
80
  files: files,
81
+ basepath: basepath,
71
82
  logger: logger,
72
83
  accession: accession,
73
84
  priority: priority).run
@@ -90,7 +101,6 @@ require 'sdr_client/deposit/request'
90
101
  require 'sdr_client/deposit/metadata_builder'
91
102
  require 'sdr_client/deposit/model_process'
92
103
  require 'sdr_client/deposit/process'
93
- require 'sdr_client/deposit/unexpected_response'
94
104
  require 'sdr_client/deposit/update_resource'
95
105
  require 'sdr_client/deposit/update_dro_with_file_identifiers'
96
106
  require 'sdr_client/deposit/upload_files'
@@ -7,9 +7,6 @@ module SdrClient
7
7
  module Find
8
8
  DRO_PATH = '/v1/resources/%<id>s'
9
9
 
10
- # Raised when find returns an unsuccessful response
11
- class Failed < StandardError; end
12
-
13
10
  # @raise [Failed] if the find operation fails
14
11
  # @return [String] JSON for the given Cocina object or an error
15
12
  def self.run(druid, url:, logger: Logger.new($stdout))
@@ -17,12 +14,10 @@ module SdrClient
17
14
  path = format(DRO_PATH, id: druid)
18
15
  logger.info("Retrieving metadata from: #{path}")
19
16
  response = connection.get(path)
20
- unless response.success?
21
- error_message = "There was an HTTP #{response.status} error making the request: #{response.body}"
22
- logger.error(error_message)
23
- raise Failed, error_message
24
- end
25
- response.body
17
+ return response.body if response.success?
18
+
19
+ logger.error("There was an HTTP #{response.status} error making the request: #{response.body}")
20
+ UnexpectedResponse.call(response)
26
21
  end
27
22
  end
28
23
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SdrClient
4
+ # Handles unexpected responses
5
+ class UnexpectedResponse
6
+ # Raised when there is a request error (e.g.: a cocina-models version mismatch)
7
+ class BadRequest < StandardError; end
8
+ # Raised when there is a problem with the credentials
9
+ class Unauthorized < StandardError; end
10
+ # Raised when there is an expired token
11
+ class TokenExpired < StandardError; end
12
+
13
+ # @param [Faraday::Response] response
14
+ def self.call(response)
15
+ case response.status
16
+ when 400
17
+ raise BadRequest, "There was an error with your request: #{response.body}"
18
+ when 401
19
+ raise TokenExpired, 'Your token has expired' if response.body.match?('Signature has expired')
20
+
21
+ raise Unauthorized, 'There was an error with your credentials.'
22
+ else
23
+ raise "unexpected response: #{response.status} #{response.body}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SdrClient
4
- VERSION = '0.97.0'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/sdr_client.rb CHANGED
@@ -9,13 +9,13 @@ require 'active_support/core_ext/file/atomic'
9
9
  require 'cocina/models'
10
10
 
11
11
  require 'sdr_client/version'
12
+ require 'sdr_client/unexpected_response'
12
13
  require 'sdr_client/deposit'
13
14
  require 'sdr_client/update'
14
15
  require 'sdr_client/credentials'
15
16
  require 'sdr_client/find'
16
17
  require 'sdr_client/login'
17
18
  require 'sdr_client/login_prompt'
18
- require 'sdr_client/cli'
19
19
  require 'sdr_client/connection'
20
20
  require 'sdr_client/background_job_results'
21
21
 
data/sdr-client.gemspec CHANGED
@@ -33,6 +33,8 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency 'faraday', '>= 0.16'
34
34
 
35
35
  spec.add_development_dependency 'bundler', '~> 2.0'
36
+ spec.add_development_dependency 'config'
37
+ spec.add_development_dependency 'launchy'
36
38
  spec.add_development_dependency 'rake', '~> 13.0'
37
39
  spec.add_development_dependency 'rspec', '~> 3.0'
38
40
  spec.add_development_dependency 'rubocop', '~> 1.24'
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: 0.97.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-11-03 00:00:00.000000000 Z
11
+ date: 2022-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: config
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: launchy
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: rake
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -215,12 +243,14 @@ files:
215
243
  - Rakefile
216
244
  - bin/console
217
245
  - bin/setup
246
+ - config/settings.yml
218
247
  - exe/remove_w3cdtf_encoding_from_event_dates
219
248
  - exe/sdr
220
249
  - lib/sdr-client.rb
221
250
  - lib/sdr_client.rb
222
251
  - lib/sdr_client/background_job_results.rb
223
252
  - lib/sdr_client/cli.rb
253
+ - lib/sdr_client/cli/config.rb
224
254
  - lib/sdr_client/connection.rb
225
255
  - lib/sdr_client/credentials.rb
226
256
  - lib/sdr_client/deposit.rb
@@ -241,7 +271,6 @@ files:
241
271
  - lib/sdr_client/deposit/process.rb
242
272
  - lib/sdr_client/deposit/request.rb
243
273
  - lib/sdr_client/deposit/single_file_grouping_strategy.rb
244
- - lib/sdr_client/deposit/unexpected_response.rb
245
274
  - lib/sdr_client/deposit/update_dro_with_file_identifiers.rb
246
275
  - lib/sdr_client/deposit/update_resource.rb
247
276
  - lib/sdr_client/deposit/upload_files.rb
@@ -249,6 +278,7 @@ files:
249
278
  - lib/sdr_client/find.rb
250
279
  - lib/sdr_client/login.rb
251
280
  - lib/sdr_client/login_prompt.rb
281
+ - lib/sdr_client/unexpected_response.rb
252
282
  - lib/sdr_client/update.rb
253
283
  - lib/sdr_client/version.rb
254
284
  - sdr-client.gemspec
@@ -259,7 +289,7 @@ metadata:
259
289
  source_code_uri: https://github.com/sul-dlss/sdr-client
260
290
  changelog_uri: https://github.com/sul-dlss/sdr-client/releases
261
291
  rubygems_mfa_required: 'true'
262
- post_install_message:
292
+ post_install_message:
263
293
  rdoc_options: []
264
294
  require_paths:
265
295
  - lib
@@ -274,8 +304,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
304
  - !ruby/object:Gem::Version
275
305
  version: '0'
276
306
  requirements: []
277
- rubygems_version: 3.3.7
278
- signing_key:
307
+ rubygems_version: 3.2.32
308
+ signing_key:
279
309
  specification_version: 4
280
310
  summary: The CLI for https://github.com/sul-dlss/sdr-api
281
311
  test_files: []
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SdrClient
4
- module Deposit
5
- # Handles unexpected responses when manipulating resources
6
- class UnexpectedResponse
7
- # Raised when there is a request error (e.g.: a cocina-models version mismatch)
8
- class BadRequest < StandardError; end
9
- # Raised when there is a problem with the credentials
10
- class Unauthorized < StandardError; end
11
-
12
- # @param [Faraday::Response] response
13
- def self.call(response)
14
- case response.status
15
- when 400
16
- raise BadRequest, "There was an error with your request: #{response.body}"
17
- when 401
18
- raise Unauthorized, 'There was an error with your credentials. Perhaps they have expired?'
19
- else
20
- raise "unexpected response: #{response.status} #{response.body}"
21
- end
22
- end
23
- end
24
- end
25
- end