nypl_ruby_util 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|