nypl_ruby_util 0.0.1
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 +7 -0
- data/lib/directory.rb +3 -0
- data/lib/errors.rb +10 -0
- data/lib/kinesis_client.rb +49 -0
- data/lib/kms_client.rb +27 -0
- data/lib/nypl_avro.rb +70 -0
- data/lib/nypl_ruby_util.rb +28 -0
- data/lib/platform_api_client.rb +105 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a102fc9301f667018c0ae2c9f2fc26c5525f8ac2fad91b35b980da52731b67b2
|
4
|
+
data.tar.gz: 4a3ac1f699931ca2432196a3c4997948ded44e6e470ec3bd958cb639b0731eca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9cb6c355ffc6d948bd800e7e6b69267115f67f49cb644aa14212d296556a0e95acdd2d8004e88f31f9671e38d4eca6a8ce349a7a18bc52fe694c990e6f6a561c
|
7
|
+
data.tar.gz: 59e711f7bebcacfe8dc24520ec8c2bcde349d2bffabc1ad40386622cf55096bdde87ee7aff4f088f70408d76466e67d1a0be634f7d96457ba5265b3304b15e70
|
data/lib/directory.rb
ADDED
data/lib/errors.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'aws-sdk-kinesis'
|
3
|
+
require_relative 'nypl_avro'
|
4
|
+
require_relative 'errors'
|
5
|
+
# Model representing the result message posted to Kinesis stream about everything that has gone on here -- good, bad, or otherwise.
|
6
|
+
|
7
|
+
class KinesisClient
|
8
|
+
attr_reader :config, :avro
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
@avro = nil
|
13
|
+
|
14
|
+
if config[:schema_string]
|
15
|
+
@avro = NYPLAvro.by_name(config[:schema_string])
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(json_message)
|
21
|
+
p '<< ', config[:schema_string], avro
|
22
|
+
if config[:schema_string]
|
23
|
+
message = avro.encode(json_message)
|
24
|
+
else
|
25
|
+
message = json_message
|
26
|
+
end
|
27
|
+
|
28
|
+
client = Aws::Kinesis::Client.new
|
29
|
+
partition_key = (config[:partition_key] ? json_message[config[:partition_key]] : SecureRandom.hex(20)).hash.to_s
|
30
|
+
|
31
|
+
resp = client.put_record({
|
32
|
+
stream_name: config[:stream_name],
|
33
|
+
data: message,
|
34
|
+
partition_key: partition_key
|
35
|
+
})
|
36
|
+
|
37
|
+
return_hash = {}
|
38
|
+
|
39
|
+
if resp.successful?
|
40
|
+
return_hash["code"] = "200"
|
41
|
+
return_hash["message"] = json_message, resp
|
42
|
+
$logger.info("Message sent to HoldRequestResult #{json_message}, #{resp}") if $logger
|
43
|
+
else
|
44
|
+
$logger.error("message" => "FAILED to send message to HoldRequestResult #{json_message}, #{resp}.") if $logger
|
45
|
+
raise NYPLError.new json_message, resp
|
46
|
+
end
|
47
|
+
return_hash
|
48
|
+
end
|
49
|
+
end
|
data/lib/kms_client.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'aws-sdk-kms'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
class KmsClient
|
5
|
+
@@kms = nil
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
options ||= {}
|
9
|
+
@kms = self.class.aws_kms_client(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def decrypt(cipher)
|
13
|
+
# Assume value is base64 encoded:
|
14
|
+
decoded = Base64.decode64 cipher
|
15
|
+
decrypted = @kms.decrypt ciphertext_blob: decoded
|
16
|
+
decrypted[:plaintext]
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.aws_kms_client(options)
|
20
|
+
params = {
|
21
|
+
region: 'us-east-1',
|
22
|
+
stub_responses: ENV['APP_ENV'] == 'test'
|
23
|
+
}.merge(options)
|
24
|
+
@@kms = Aws::KMS::Client.new(params) if @@kms.nil?
|
25
|
+
@@kms
|
26
|
+
end
|
27
|
+
end
|
data/lib/nypl_avro.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'avro'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
require_relative 'errors'
|
6
|
+
require_relative 'directory'
|
7
|
+
|
8
|
+
class NYPLAvro
|
9
|
+
def initialize (schemaString)
|
10
|
+
begin
|
11
|
+
@schema = Avro::Schema.parse(schemaString)
|
12
|
+
rescue Exception => e
|
13
|
+
raise AvroError.new(e), "Failed to parse schema string: \"#{schemaString}\""
|
14
|
+
end
|
15
|
+
|
16
|
+
@reader = Avro::IO::DatumReader.new(@schema)
|
17
|
+
end
|
18
|
+
|
19
|
+
def decode(encoded_data_string, base64 = true)
|
20
|
+
avro_string = base64 ? Base64.decode64(encoded_data_string) : encoded_data_string
|
21
|
+
stringreader = StringIO.new(avro_string)
|
22
|
+
bin_decoder = Avro::IO::BinaryDecoder.new(stringreader)
|
23
|
+
begin
|
24
|
+
read_value = @reader.read(bin_decoder)
|
25
|
+
rescue Exception => e
|
26
|
+
raise AvroError.new(e), "Error decoding data using #{@schema.name} schema"
|
27
|
+
end
|
28
|
+
|
29
|
+
read_value
|
30
|
+
end
|
31
|
+
|
32
|
+
def encode(decoded_data, base64 = true)
|
33
|
+
bin_encoder = Avro::IO::DatumWriter.new(@schema)
|
34
|
+
buffer = StringIO.new
|
35
|
+
encoder = Avro::IO::BinaryEncoder.new(buffer)
|
36
|
+
|
37
|
+
begin
|
38
|
+
bin_encoder.write(decoded_data, encoder)
|
39
|
+
rescue Avro::IO::AvroTypeError => e
|
40
|
+
raise AvroError.new(e), "Error encoding data using #{@schema.name} schema due to #{e.message}"
|
41
|
+
end
|
42
|
+
|
43
|
+
buffer.rewind
|
44
|
+
result = buffer.read
|
45
|
+
base64 ? Base64.encode64(result) : result
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.by_name (name)
|
49
|
+
require 'net/http'
|
50
|
+
require 'uri'
|
51
|
+
|
52
|
+
uri = URI.parse("#{ENV["PLATFORM_API_BASE_URL"]}current-schemas/#{name}")
|
53
|
+
begin
|
54
|
+
response = Net::HTTP.get_response(uri)
|
55
|
+
rescue Exception => e
|
56
|
+
raise AvroError.new(e), "Failed to retrieve #{name} schema: #{e.message}"
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
response_hash = JSON.parse(response.body)
|
61
|
+
rescue JSON::ParserError => e
|
62
|
+
raise AvroError.new(e), "Retrieved #{name} schema is malformed: #{response.body}"
|
63
|
+
end
|
64
|
+
|
65
|
+
raise AvroError.new, "Failed to retrieve #{name} schema: statusCode=#{response_hash["statusCode"]}" if response_hash["statusCode"] >= 400
|
66
|
+
raise AvroError.new, "Retrieved #{name} schema is malformed" if response_hash["data"].nil? || response_hash["data"]["schema"].nil?
|
67
|
+
|
68
|
+
self.new response_hash["data"]["schema"]
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'nypl_log_formatter'
|
2
|
+
require 'nypl_sierra_api_client'
|
3
|
+
require_relative 'kms_client'
|
4
|
+
require_relative 'platform_api_client'
|
5
|
+
require_relative 'directory'
|
6
|
+
require_relative 'nypl_avro'
|
7
|
+
require_relative 'errors'
|
8
|
+
require_relative 'kinesis_client'
|
9
|
+
|
10
|
+
class NYPLRubyUtil
|
11
|
+
class SierraApiClient < SierraApiClient
|
12
|
+
end
|
13
|
+
|
14
|
+
class NyplLogFormatter < NyplLogFormatter
|
15
|
+
end
|
16
|
+
|
17
|
+
class KmsClient < KmsClient
|
18
|
+
end
|
19
|
+
|
20
|
+
class NYPLAvro < NYPLAvro
|
21
|
+
end
|
22
|
+
|
23
|
+
class PlatformApiClient < PlatformApiClient
|
24
|
+
end
|
25
|
+
|
26
|
+
class KinesisClient < KinesisClient
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
|
5
|
+
require_relative 'kms_client'
|
6
|
+
|
7
|
+
class PlatformApiClient
|
8
|
+
attr_reader :authenticated, :client_id, :client_secret, :error_options, :oauth_site
|
9
|
+
attr_accessor :access_token
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
raise 'Missing config: NYPL_OAUTH_ID is unset' if ENV['NYPL_OAUTH_ID'].nil? || ENV['NYPL_OAUTH_ID'].empty?
|
13
|
+
raise 'Missing config: NYPL_OAUTH_SECRET is unset' if ENV['NYPL_OAUTH_SECRET'].nil? || ENV['NYPL_OAUTH_SECRET'].empty?
|
14
|
+
|
15
|
+
kms_client = KmsClient.new(options[:kms_options])
|
16
|
+
@client_id = kms_client.decrypt(ENV['NYPL_OAUTH_ID'])
|
17
|
+
@client_secret = kms_client.decrypt(ENV['NYPL_OAUTH_SECRET'])
|
18
|
+
|
19
|
+
@oauth_site = ENV['NYPL_OAUTH_URL']
|
20
|
+
@authenticated = options[:authenticated] || true
|
21
|
+
@error_options = default_errors.merge(options[:errors] || {})
|
22
|
+
end
|
23
|
+
|
24
|
+
def get (path)
|
25
|
+
|
26
|
+
authenticate! if authenticated
|
27
|
+
|
28
|
+
uri = URI.parse("#{ENV['PLATFORM_API_BASE_URL']}#{path}")
|
29
|
+
|
30
|
+
$logger.debug "Getting from platform api", { uri: uri }
|
31
|
+
|
32
|
+
begin
|
33
|
+
request = Net::HTTP::Get.new(uri)
|
34
|
+
request["Authorization"] = "Bearer #{access_token}" if authenticated
|
35
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme === 'https') do |http|
|
36
|
+
http.request(request)
|
37
|
+
end
|
38
|
+
|
39
|
+
$logger.debug "Got platform api response", { code: response.code, body: response.body }
|
40
|
+
|
41
|
+
parse_json_response response, path
|
42
|
+
|
43
|
+
rescue Exception => e
|
44
|
+
raise StandardError.new(e), "Failed to retrieve #{path} #{e.message}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def parse_json_response (response, path)
|
51
|
+
code = response.code.to_i
|
52
|
+
if code < 400
|
53
|
+
JSON.parse(response.body)
|
54
|
+
elsif error_options[code]
|
55
|
+
instance_exec(response, path, &error_options[code])
|
56
|
+
else
|
57
|
+
raise "Error interpretting response for path #{path}: (#{response.code}): #{response.body}"
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Authorizes the request.
|
63
|
+
def authenticate!
|
64
|
+
# NOOP if we've already authenticated
|
65
|
+
return nil if ! access_token.nil?
|
66
|
+
|
67
|
+
uri = URI.parse("#{oauth_site}oauth/token")
|
68
|
+
request = Net::HTTP::Post.new(uri)
|
69
|
+
request.basic_auth(client_id, client_secret)
|
70
|
+
request.set_form_data(
|
71
|
+
"grant_type" => "client_credentials"
|
72
|
+
)
|
73
|
+
|
74
|
+
req_options = {
|
75
|
+
use_ssl: uri.scheme == "https",
|
76
|
+
request_timeout: 500
|
77
|
+
}
|
78
|
+
|
79
|
+
response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
|
80
|
+
http.request(request)
|
81
|
+
end
|
82
|
+
|
83
|
+
if response.code == '200'
|
84
|
+
access_token = JSON.parse(response.body)["access_token"]
|
85
|
+
else
|
86
|
+
nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_errors
|
91
|
+
{
|
92
|
+
401 => lambda do |response, path|
|
93
|
+
if @try_count < 1
|
94
|
+
# Likely an expired access-token; Wipe it for next run
|
95
|
+
@try_count += 1
|
96
|
+
access_token = nil
|
97
|
+
get(path)
|
98
|
+
else
|
99
|
+
raise "Error interpretting response for path #{path}: (#{response.code}): #{response.body}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nypl_ruby_util
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Appel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A repository of common utilities for NYPL Ruby application
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/directory.rb
|
20
|
+
- lib/errors.rb
|
21
|
+
- lib/kinesis_client.rb
|
22
|
+
- lib/kms_client.rb
|
23
|
+
- lib/nypl_avro.rb
|
24
|
+
- lib/nypl_ruby_util.rb
|
25
|
+
- lib/platform_api_client.rb
|
26
|
+
homepage:
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.0.3
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: A repository of common utilities for NYPL Ruby application
|
49
|
+
test_files: []
|