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: []
         |