sdr-client 0.1.0 → 0.2.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/.rubocop_todo.yml +11 -12
- data/.travis.yml +3 -0
- data/README.md +10 -1
- data/exe/sdr +40 -2
- data/lib/sdr_client.rb +3 -0
- data/lib/sdr_client/cli.rb +4 -0
- data/lib/sdr_client/credentials.rb +31 -0
- data/lib/sdr_client/deposit.rb +14 -2
- data/lib/sdr_client/deposit/process.rb +33 -17
- data/lib/sdr_client/deposit/request.rb +49 -13
- data/lib/sdr_client/login.rb +23 -0
- data/lib/sdr_client/login_prompt.rb +19 -0
- data/lib/sdr_client/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a995f725226eef2f793d8b0b45e487c37a6e34899b223a1f2c6864f32204325
|
4
|
+
data.tar.gz: dd947253f76fe8dcfd20565537e89fa86c4b6b13a4e6c68b2ecf285d30406c08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3f0c8d2c9994c4a0408438199105dfdcccb0901e4905220cbf99fd69b3d5e9a735752c3de638264c1397986985ecb2a9f992ab1f1fad2e3245025e78eed1dd0
|
7
|
+
data.tar.gz: 300591272660ad09e74bc992742c3c26c14251bd107252b441a2e2856ed981749dfd5ff298704b13c360a0b3dab0cc89fceb2282a9f301fe24909eaac0b8de0d
|
data/.rubocop_todo.yml
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2020-01-
|
3
|
+
# on 2020-01-10 17:16:01 -0600 using RuboCop version 0.79.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
9
|
# Offense count: 1
|
10
|
-
|
11
|
-
|
12
|
-
Layout/ExtraSpacing:
|
13
|
-
Exclude:
|
14
|
-
- 'sdr-client.gemspec'
|
10
|
+
Metrics/AbcSize:
|
11
|
+
Max: 16
|
15
12
|
|
16
13
|
# Offense count: 1
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
15
|
+
Metrics/MethodLength:
|
16
|
+
Max: 13
|
17
|
+
|
18
|
+
# Offense count: 2
|
19
|
+
# Configuration parameters: CountKeywordArgs.
|
20
|
+
Metrics/ParameterLists:
|
21
|
+
Max: 8
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -10,7 +10,16 @@ The code for the SDR API server is at https://github.com/sul-dlss/sdr-api
|
|
10
10
|
|
11
11
|
## Usage
|
12
12
|
|
13
|
+
Log in:
|
14
|
+
```
|
15
|
+
sdr --service-url http://sdr-api-server:3000 login
|
16
|
+
```
|
17
|
+
|
18
|
+
|
13
19
|
Deposit a new object:
|
14
20
|
```
|
15
|
-
sdr --service-url
|
21
|
+
sdr --service-url https://sdr-api-server:3000 deposit --label 'hey there' \
|
22
|
+
--admin-policy 'druid:bk123gh4567' \
|
23
|
+
--collection 'druid:gh456kw9876' \
|
24
|
+
--source-id 'googlebooks:stanford_12345' file1.png file2.png
|
16
25
|
```
|
data/exe/sdr
CHANGED
@@ -10,7 +10,28 @@ global = OptionParser.new do |opts|
|
|
10
10
|
options[:url] = url
|
11
11
|
end
|
12
12
|
opts.on('-h', '--help', 'Display this screen') do
|
13
|
-
puts
|
13
|
+
puts <<~HELP
|
14
|
+
DESCRIPTION:
|
15
|
+
The SDR Command Line Interface is a tool to interact with the Stanford Digital Repository.
|
16
|
+
|
17
|
+
SYNOPSIS:
|
18
|
+
sdr [options] <command>
|
19
|
+
|
20
|
+
OPTIONS:
|
21
|
+
--service-url (string)
|
22
|
+
Override the command's default URL with the given URL.
|
23
|
+
|
24
|
+
-h, --help
|
25
|
+
Displays this screen
|
26
|
+
|
27
|
+
COMMANDS
|
28
|
+
deposit
|
29
|
+
deposit files to the SDR
|
30
|
+
|
31
|
+
login
|
32
|
+
Will prompt for email & password and exchange it for an login token, which it saves in ~/.sdr/token
|
33
|
+
|
34
|
+
HELP
|
14
35
|
exit
|
15
36
|
end
|
16
37
|
end
|
@@ -23,11 +44,28 @@ subcommands = {
|
|
23
44
|
opts.on('--label LABEL', 'The object label') do |label|
|
24
45
|
options[:label] = label
|
25
46
|
end
|
47
|
+
|
48
|
+
opts.on('--admin-policy ADMIN_POLICY', 'The druid identifier of the admin policy object') do |apo|
|
49
|
+
options[:apo] = apo
|
50
|
+
end
|
51
|
+
opts.on('--collection COLLECTION', 'The druid identifier of the collection object') do |collection|
|
52
|
+
options[:collection] = collection
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on('--catkey CATKEY', 'The catkey for this item') do |catkey|
|
56
|
+
options[:catkey] = catkey
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on('--source-id SOURCE_ID', 'The source id for this object') do |source_id|
|
60
|
+
options[:source_id] = source_id
|
61
|
+
end
|
62
|
+
|
26
63
|
opts.on('-h', '--help', 'Display this screen') do
|
27
64
|
puts opts
|
28
65
|
exit
|
29
66
|
end
|
30
|
-
end
|
67
|
+
end,
|
68
|
+
'login' => OptionParser.new
|
31
69
|
}
|
32
70
|
|
33
71
|
unless subcommands.key?(command)
|
data/lib/sdr_client.rb
CHANGED
data/lib/sdr_client/cli.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
# The stored credentials
|
5
|
+
class Credentials
|
6
|
+
# @param [String] a json string that contains a field 'token'
|
7
|
+
def self.write(body)
|
8
|
+
json = JSON.parse(body)
|
9
|
+
Dir.mkdir(credentials_path, 0o700) unless Dir.exist?(credentials_path)
|
10
|
+
File.open(credentials_file, 'w', 0o600) do |file|
|
11
|
+
file.write(json.fetch('token'))
|
12
|
+
end
|
13
|
+
puts 'Signed in.'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.read
|
17
|
+
return IO.readlines(credentials_file, chomp: true).first if File.exist?(credentials_file)
|
18
|
+
|
19
|
+
puts 'Log in first'
|
20
|
+
exit(1)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.credentials_path
|
24
|
+
@credentials_path ||= File.join(Dir.home, '.sdr')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.credentials_file
|
28
|
+
File.join(credentials_path, 'credentials')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/sdr_client/deposit.rb
CHANGED
@@ -3,10 +3,22 @@
|
|
3
3
|
module SdrClient
|
4
4
|
# The namespace for the "deposit" command
|
5
5
|
module Deposit
|
6
|
-
def self.run(label
|
6
|
+
def self.run(label: nil,
|
7
7
|
type: 'http://cocina.sul.stanford.edu/models/book.jsonld',
|
8
|
+
apo:,
|
9
|
+
collection:,
|
10
|
+
catkey: nil,
|
11
|
+
source_id:,
|
8
12
|
url:, files: [])
|
9
|
-
|
13
|
+
token = Credentials.read
|
14
|
+
|
15
|
+
metadata = Request.new(label: label,
|
16
|
+
type: type,
|
17
|
+
apo: apo,
|
18
|
+
collection: collection,
|
19
|
+
source_id: source_id,
|
20
|
+
catkey: catkey)
|
21
|
+
Process.new(metadata: metadata, url: url, token: token, files: files).run
|
10
22
|
end
|
11
23
|
end
|
12
24
|
end
|
@@ -6,13 +6,18 @@ module SdrClient
|
|
6
6
|
module Deposit
|
7
7
|
# The process for doing a deposit
|
8
8
|
class Process
|
9
|
-
BLOB_PATH = '/
|
9
|
+
BLOB_PATH = '/v1/direct_uploads'
|
10
10
|
DRO_PATH = '/v1/resources'
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
# @param [Request] metadata information about the object
|
12
|
+
# @param [String] url the server to send to
|
13
|
+
# @param [String] token the bearer auth token for the server
|
14
|
+
# @param [Array<String>] files a list of file names to upload
|
15
|
+
# @param [Logger] logger the logger to use
|
16
|
+
def initialize(metadata:, url:, token:, files: [], logger: Logger.new(STDOUT))
|
14
17
|
@files = files
|
15
18
|
@url = url
|
19
|
+
@token = token
|
20
|
+
@metadata = metadata
|
16
21
|
@logger = logger
|
17
22
|
end
|
18
23
|
|
@@ -21,15 +26,13 @@ module SdrClient
|
|
21
26
|
file_metadata = collect_file_metadata
|
22
27
|
upload_responses = upload_file_metadata(file_metadata)
|
23
28
|
upload_files(upload_responses)
|
24
|
-
|
25
|
-
|
26
|
-
uploads: upload_responses.values)
|
27
|
-
upload_metadata(metadata.as_json)
|
29
|
+
request = metadata.with_uploads(upload_responses.values)
|
30
|
+
upload_metadata(request.as_json)
|
28
31
|
end
|
29
32
|
|
30
33
|
private
|
31
34
|
|
32
|
-
attr_reader :
|
35
|
+
attr_reader :metadata, :files, :url, :token, :logger
|
33
36
|
|
34
37
|
def check_files_exist
|
35
38
|
logger.info('checking to see if files exist')
|
@@ -51,7 +54,7 @@ module SdrClient
|
|
51
54
|
|
52
55
|
def direct_upload(metadata_json)
|
53
56
|
logger.info("Starting an upload request: #{metadata_json}")
|
54
|
-
response =
|
57
|
+
response = connection.post(BLOB_PATH, metadata_json, 'Content-Type' => 'application/json')
|
55
58
|
raise "unexpected response: #{response.inspect}" unless response.status == 200
|
56
59
|
|
57
60
|
logger.info("Response from server: #{response.body}")
|
@@ -73,10 +76,8 @@ module SdrClient
|
|
73
76
|
|
74
77
|
def upload_file(filename:, url:, content_type:, content_length:)
|
75
78
|
logger.info("Uploading `#{filename}' to #{url}")
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
upload_response = conn.put(url) do |req|
|
79
|
+
|
80
|
+
upload_response = connection.put(url) do |req|
|
80
81
|
req.body = File.open(filename)
|
81
82
|
req.headers['Content-Type'] = content_type
|
82
83
|
req.headers['Content-Length'] = content_length.to_s
|
@@ -85,15 +86,30 @@ module SdrClient
|
|
85
86
|
raise "unexpected response: #{upload_response.inspect}" unless upload_response.status == 204
|
86
87
|
end
|
87
88
|
|
89
|
+
# @return [Hash<Symbol,String>] the result of the metadata call
|
88
90
|
def upload_metadata(metadata)
|
89
91
|
logger.info("Starting upload metadata: #{metadata}")
|
90
92
|
request_json = JSON.generate(metadata)
|
91
|
-
response =
|
92
|
-
|
93
|
+
response = connection.post(DRO_PATH, request_json, 'Content-Type' => 'application/json')
|
94
|
+
unexpected_response(response) unless response.status == 201
|
93
95
|
|
94
96
|
logger.info("Response from server: #{response.body}")
|
95
97
|
|
96
|
-
JSON.parse(response.body)
|
98
|
+
{ druid: JSON.parse(response.body)['druid'], background_job: response.headers['Location'] }
|
99
|
+
end
|
100
|
+
|
101
|
+
def unexpected_response(response)
|
102
|
+
raise "unexpected response: #{response.inspect}" unless response.status == 400
|
103
|
+
|
104
|
+
puts "\nThere was an error with your request: #{response.body}"
|
105
|
+
exit(1)
|
106
|
+
end
|
107
|
+
|
108
|
+
def connection
|
109
|
+
@connection ||= Faraday.new(url: url) do |conn|
|
110
|
+
conn.authorization :Bearer, token
|
111
|
+
conn.adapter :net_http
|
112
|
+
end
|
97
113
|
end
|
98
114
|
end
|
99
115
|
end
|
@@ -4,33 +4,70 @@ module SdrClient
|
|
4
4
|
module Deposit
|
5
5
|
# This represents the metadata that we send to the server for doing a deposit
|
6
6
|
class Request
|
7
|
-
CONTEXT = 'http://cocina.sul.stanford.edu/contexts/cocina-base.jsonld'
|
8
|
-
|
9
7
|
# @param [String] label the required object label
|
10
8
|
# @param [String] type (http://cocina.sul.stanford.edu/models/object.jsonld) the required object type.
|
11
9
|
# @param [Array<SdrClient::Deposit::Files::DirectUploadResponse>] uploads the uploaded files to attach.
|
12
|
-
def initialize(label
|
10
|
+
def initialize(label: nil,
|
11
|
+
apo:,
|
12
|
+
collection:,
|
13
|
+
source_id:,
|
14
|
+
catkey: nil,
|
13
15
|
type: 'http://cocina.sul.stanford.edu/models/object.jsonld',
|
14
16
|
uploads: [])
|
15
17
|
@label = label
|
16
18
|
@type = type
|
19
|
+
@source_id = source_id
|
20
|
+
@collection = collection
|
21
|
+
@catkey = catkey
|
22
|
+
@apo = apo
|
17
23
|
@uploads = uploads
|
18
24
|
end
|
19
25
|
|
20
26
|
def as_json
|
21
27
|
{
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
access: {},
|
29
|
+
type: type,
|
30
|
+
administrative: administrative,
|
31
|
+
identification: identification,
|
32
|
+
structural: structural
|
33
|
+
}.tap do |json|
|
34
|
+
json[:label] = label if label
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Request] a clone of this request with the uploads added
|
39
|
+
def with_uploads(uploads)
|
40
|
+
Request.new(label: label,
|
41
|
+
apo: apo,
|
42
|
+
collection: collection,
|
43
|
+
source_id: source_id,
|
44
|
+
catkey: catkey,
|
45
|
+
type: type,
|
46
|
+
uploads: uploads)
|
29
47
|
end
|
30
48
|
|
31
49
|
private
|
32
50
|
|
33
|
-
attr_reader :label, :uploads, :type
|
51
|
+
attr_reader :label, :uploads, :source_id, :catkey, :apo, :collection, :type
|
52
|
+
|
53
|
+
def administrative
|
54
|
+
{
|
55
|
+
hasAdminPolicy: apo
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def identification
|
60
|
+
{ sourceId: source_id }.tap do |json|
|
61
|
+
json[:catkey] = catkey if catkey
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def structural
|
66
|
+
{
|
67
|
+
isMemberOf: collection,
|
68
|
+
hasMember: file_sets_as_json
|
69
|
+
}
|
70
|
+
end
|
34
71
|
|
35
72
|
# In this case there is a 1-1 mapping between Files and FileSets,
|
36
73
|
# but this doesn't always have to be the case. We could change this in the
|
@@ -38,8 +75,7 @@ module SdrClient
|
|
38
75
|
def file_sets_as_json
|
39
76
|
uploads.map do |upload|
|
40
77
|
{
|
41
|
-
"
|
42
|
-
"@type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
|
78
|
+
"type": 'http://cocina.sul.stanford.edu/models/fileset.jsonld',
|
43
79
|
label: upload.filename,
|
44
80
|
structural: {
|
45
81
|
hasMember: [upload.signed_id]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SdrClient
|
4
|
+
# The namespace for the "login" command
|
5
|
+
module Login
|
6
|
+
LOGIN_PATH = '/v1/auth/login'
|
7
|
+
def self.run(url:, login_service: LoginPrompt)
|
8
|
+
request_json = JSON.generate(login_service.run)
|
9
|
+
response = Faraday.post(url + LOGIN_PATH, request_json, 'Content-Type' => 'application/json')
|
10
|
+
case response.status
|
11
|
+
when 200
|
12
|
+
Credentials.write(response.body)
|
13
|
+
when 400
|
14
|
+
puts 'Email address is not a valid email'
|
15
|
+
when 401
|
16
|
+
puts 'Invalid username or password'
|
17
|
+
else
|
18
|
+
puts "Status: #{response.status}"
|
19
|
+
puts response.body
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
module SdrClient
|
6
|
+
# The namespace for the "login" command
|
7
|
+
module LoginPrompt
|
8
|
+
def self.run
|
9
|
+
print 'Email: '
|
10
|
+
email = gets
|
11
|
+
email.strip!
|
12
|
+
print 'Password: '
|
13
|
+
password = $stdin.noecho(&:gets)
|
14
|
+
password.strip!
|
15
|
+
puts
|
16
|
+
{ email: email, password: password }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/sdr_client/version.rb
CHANGED
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Coyne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-01-
|
11
|
+
date: 2020-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -146,11 +146,14 @@ files:
|
|
146
146
|
- exe/sdr
|
147
147
|
- lib/sdr_client.rb
|
148
148
|
- lib/sdr_client/cli.rb
|
149
|
+
- lib/sdr_client/credentials.rb
|
149
150
|
- lib/sdr_client/deposit.rb
|
150
151
|
- lib/sdr_client/deposit/files/direct_upload_request.rb
|
151
152
|
- lib/sdr_client/deposit/files/direct_upload_response.rb
|
152
153
|
- lib/sdr_client/deposit/process.rb
|
153
154
|
- lib/sdr_client/deposit/request.rb
|
155
|
+
- lib/sdr_client/login.rb
|
156
|
+
- lib/sdr_client/login_prompt.rb
|
154
157
|
- lib/sdr_client/version.rb
|
155
158
|
- sdr-client.gemspec
|
156
159
|
homepage: https://github.com/sul-dlss/sdr-client
|
@@ -174,7 +177,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
177
|
- !ruby/object:Gem::Version
|
175
178
|
version: '0'
|
176
179
|
requirements: []
|
177
|
-
rubygems_version: 3.0.
|
180
|
+
rubygems_version: 3.0.3
|
178
181
|
signing_key:
|
179
182
|
specification_version: 4
|
180
183
|
summary: The CLI for https://github.com/sul-dlss/sdr-api
|