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 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
@@ -0,0 +1,3 @@
1
+ class Directory
2
+ # PLATFORM_API_BASE_URL
3
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,10 @@
1
+ class NYPLError < StandardError
2
+ attr_reader :object
3
+
4
+ def initialize(object='')
5
+ @object = object
6
+ end
7
+ end
8
+
9
+ class AvroError < NYPLError
10
+ end
@@ -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: []