hover-ruby-client 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ require 'api-auth'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module Hover
7
+ module Client
8
+ class HTTP
9
+ attr_accessor :prefix, :site
10
+
11
+ def initialize(site = 'http://localhost:3000', prefix = 'api/v1')
12
+ @prefix = prefix
13
+ @site = site
14
+ end
15
+
16
+ def authenticate(request)
17
+ end
18
+
19
+ def make_uri(path, parameters = {})
20
+ parts = [site, prefix, path]
21
+ uri = URI.parse(parts.map(&:presence).compact.join('/'))
22
+ uri = URI.parse("#{uri.to_s}?#{URI.encode_www_form(parameters)}") unless parameters.empty?
23
+
24
+ uri
25
+ end
26
+
27
+ def make_request(request, uri)
28
+ authenticate(request)
29
+
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ http.use_ssl = true if 'https' == uri.scheme
32
+
33
+ response = http.request(request)
34
+
35
+ raise_errors_for_cloudfront_response(response)
36
+
37
+ response
38
+ end
39
+
40
+ def get(path, parameters = {})
41
+ uri = make_uri(path, parameters)
42
+ request = Net::HTTP::Get.new(uri)
43
+ response = make_request(request, uri)
44
+
45
+ response
46
+ end
47
+
48
+ def get_redirect_location(path, parameters = {}, &block)
49
+ uri = make_uri(path, parameters)
50
+ request = Net::HTTP::Get.new(uri)
51
+ redirect_response = make_request(request, uri)
52
+
53
+ if redirect_response.is_a?(Net::HTTPRedirection) && redirect_response['location']
54
+ redirect_response['location']
55
+ else
56
+ nil
57
+ end
58
+ end
59
+
60
+ def post(path, parameters = {})
61
+ uri = make_uri(path)
62
+ request = Net::HTTP::Post.new(uri)
63
+ make_form_request(request, uri, parameters)
64
+ end
65
+
66
+ def make_form_request(request, uri, parameters)
67
+ request.set_form_data(parameters)
68
+ response = make_request(request, uri)
69
+ response
70
+ end
71
+
72
+ def put(path, parameters = {})
73
+ uri = make_uri(path)
74
+ request = Net::HTTP::Put.new(uri)
75
+ make_form_request(request, uri, parameters)
76
+ end
77
+
78
+ def patch(path, parameters = {})
79
+ uri = make_uri(path)
80
+ request = Net::HTTP::Patch.new(uri)
81
+ make_form_request(request, uri, parameters)
82
+ end
83
+
84
+ def delete(path, parameters = {})
85
+ uri = make_uri(path)
86
+ request = Net::HTTP::Delete.new(uri)
87
+ make_form_request(request, uri, parameters)
88
+ end
89
+
90
+ def parse_response(response)
91
+ raise_errors(response)
92
+
93
+ case response.code.to_i
94
+ when 204 # no content
95
+ {}
96
+ else
97
+ ::JSON.parse(response.body)
98
+ end
99
+ end
100
+
101
+ def raise_errors_for_cloudfront_response(response)
102
+ #
103
+ # If the server can't be reached cloudfront will respond with an
104
+ # error message of it's own. We need to catch those so that we can retry
105
+ # the requests.
106
+ #
107
+
108
+ return unless (response.header['Server'] =~ /Cloudfront/i)
109
+
110
+ raise ::Hover::Client::Unavailable
111
+ end
112
+
113
+ def raise_errors(response)
114
+ message = "(#{response.code}): - #{response.body}"
115
+
116
+ case response.code.to_i
117
+ when 400
118
+ raise BadRequest, message
119
+ when 401
120
+ raise Unauthorized, message
121
+ when 403
122
+ raise General, message
123
+ when 404
124
+ raise NotFound, message
125
+ when 500
126
+ raise InternalError, "Internal error: #{message}"
127
+ when 502..503
128
+ raise Unavailable, message
129
+ end
130
+ end
131
+ end
132
+
133
+ class BadRequest < StandardError; end
134
+ class Unauthorized < StandardError; end
135
+ class General < StandardError; end
136
+ class Unavailable < StandardError; end
137
+ class InternalError < StandardError; end
138
+ class NotFound < StandardError; end
139
+ class AloomaReportingError < StandardError; end
140
+ end
141
+ end
@@ -0,0 +1,17 @@
1
+ require 'hover/client/hover'
2
+
3
+ module Hover
4
+ module Client
5
+ class Manowar < ::Hover::Client::Hover
6
+
7
+ def submit_order(parameters = {})
8
+ json_post('orders.json', parameters)
9
+ end
10
+
11
+ def order_results(order_id)
12
+ json_get("orders/#{order_id}/results.json")
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ require 'open-uri'
2
+ require 'tempfile'
3
+ require 'zip'
4
+
5
+ require 'hover/client/hover'
6
+
7
+ module Hover
8
+ module Client
9
+ class Midas < ::Hover::Client::Hover
10
+
11
+ #
12
+ # Users
13
+ #
14
+
15
+ def me
16
+ json_get('me.json')
17
+ end
18
+
19
+ #
20
+ # Orders
21
+ #
22
+
23
+ def orders(parameters = {})
24
+ json_get('orders.json', parameters)
25
+ end
26
+
27
+ def order(id, parameters = {})
28
+ json_get("orders/#{id}.json", parameters)
29
+ end
30
+
31
+ def order_complete_work(order_id)
32
+ put("orders/#{order_id}/work_complete.json")
33
+ end
34
+
35
+ #def update_order(id, order_attributes)
36
+ # put("orders/#{id}.json", order_parameters(order_attributes))
37
+ #end
38
+
39
+ def create_order(params = {})
40
+ json_post('orders.json', params)
41
+ end
42
+
43
+ def complete_order_upload(order_id)
44
+ patch("orders/#{order_id}/archive_uploading_complete.json")
45
+ end
46
+
47
+ #
48
+ # Images
49
+ #
50
+
51
+ def images(parameters = {})
52
+ json_get("images.json", parameters)
53
+ end
54
+
55
+ def image(id)
56
+ json_get("images/#{id}.json")
57
+ end
58
+
59
+ def download_image(image, parameters = {}, &block)
60
+ return unless url = get_redirect_location("images/#{image["id"]}.jpg", parameters)
61
+
62
+ Tempfile.open(["image-download", ".jpg"]) do |output|
63
+ output.write(Kernel.open(url).read)
64
+ output.rewind
65
+
66
+ yield output
67
+ end
68
+ end
69
+
70
+ def upload_image(image, io)
71
+ raise "Already Uploaded" unless upload_url = image["image"].try(:[], "upload_url")
72
+
73
+ file = (io.is_a?(String) ? File.open(io) : io)
74
+
75
+ uri = URI.parse(upload_url)
76
+
77
+ request = Net::HTTP::Put.new(uri.request_uri)
78
+ request.body_stream = file
79
+ request.content_length = file.size
80
+ request['Content-Type'] = ''
81
+
82
+ http = Net::HTTP.new(uri.host, uri.port)
83
+ http.use_ssl = true
84
+ response = http.request(request)
85
+ end
86
+
87
+ def image_upload_urls(order_id)
88
+ json_get("images/upload_urls.json", order_id: order_id)
89
+ end
90
+
91
+ def upload_images_for_order(order_id, input_path)
92
+ image_array = image_upload_urls(order_id).map { |upload_url| {"image" => {"upload_url" => upload_url}} }
93
+ file_paths = Dir.glob(File.join(input_path, "*.jp*"))
94
+
95
+ if file_paths.size > image_array.size
96
+ raise("Trying to upload #{file_paths.size} files. #{image_array.size} is the maximum.")
97
+ end
98
+
99
+ file_paths.each do |file_path|
100
+ image = image_array.pop
101
+ upload_image(image, file_path)
102
+ end
103
+ end
104
+
105
+ def upload_images_from_prometheus_export(order_id, export_zip_path)
106
+ Zip::File.open(export_zip_path) do |zip|
107
+ Dir.mktmpdir do |directory_path|
108
+ zip.entries.select { |entry| entry.name =~ /original_image\.jp(g|eg)$/ }.each do |entry|
109
+ basename = File.basename(entry.name)
110
+ next if basename =~ /^\./
111
+ entry.extract(File.join(directory_path, basename)) { true }
112
+ end
113
+
114
+ upload_images_for_order(order_id, directory_path)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,89 @@
1
+ require 'hover/client/hover'
2
+
3
+ module Hover
4
+ module Client
5
+ class Static < ::Hover::Client::Hover
6
+
7
+ attr_accessor :application, :environment
8
+
9
+ def initialize(application, environment, client_id, client_secret, site = 'http://localhost:3000', prefix = 'api/v1')
10
+ super(client_id, client_secret, site, prefix)
11
+
12
+ self.application = application
13
+ self.environment = environment
14
+ end
15
+
16
+ def create_metric(name:, value:, happened_at: Time.now.utc, tags: {}, remote_record_id: nil)
17
+ return unless alooma_url
18
+
19
+ parameters = {
20
+ "metric[name]" => name,
21
+ 'metric[value]' => value,
22
+ 'metric[happened_at]' => happened_at.to_s,
23
+ 'metric[environment]' => environment,
24
+ 'metric[application]' => application
25
+ }.merge(self.class.tags_to_params(tags))
26
+ parameters['metric[remote_record_id]'] = remote_record_id if remote_record_id
27
+ add_unique_identifier(parameters)
28
+
29
+ post_to_alooma(parameters, alooma_url)
30
+ end
31
+
32
+ def alooma_url
33
+ @alooma_url ||= if Rails.env.staging?
34
+ "https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfc3RhZ2luZyIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.Iba3VM9GaOjzJEmUFPuHDA6oaQT7TBe9A0iMxfYW0x0"
35
+ elsif Rails.env.production?
36
+ "https://inputs.alooma.com/rest/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGllbnROYW1lIjoiaG92ZXItc2YtMSIsImlucHV0TGFiZWwiOiJzdGF0aWNfcHJvZHVjdGlvbiIsImlucHV0VHlwZSI6IlJFU1RBUEkifQ.jiuWVLDPDvZSv4bmiy54GVEpOre19bDqr9ZC7Y-HKwo"
37
+ end
38
+ end
39
+
40
+ def post_to_alooma(parameters, url)
41
+ uri = URI.parse(url)
42
+ request = Net::HTTP::Post.new(uri)
43
+ request.set_form_data(parameters)
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true if 'https' == uri.scheme
46
+ response = http.request(request)
47
+
48
+ unless (200 .. 299).include?(response.code.to_i)
49
+ raise AloomaReportingError.new("Error reporting to alooma. #{response.inspect}")
50
+ end
51
+
52
+ response
53
+ end
54
+
55
+ def add_unique_identifier(parameters)
56
+ sorted_array = parameters.to_a.sort_by(&:first)
57
+ metric_identifier = Digest::SHA256.hexdigest(sorted_array.to_json)
58
+
59
+ parameters['metric[identifier]'] = metric_identifier
60
+ end
61
+
62
+ #
63
+ # Class Methods
64
+ #
65
+
66
+ def self.tags_to_params(tags, parent_keys = ['metric', 'tags'])
67
+ params = {}
68
+
69
+ tags.each do |key, value|
70
+ if value.is_a?(Hash)
71
+ params.merge!(tags_to_params(value, (parent_keys + [key])))
72
+ elsif value.is_a?(Array)
73
+ params["#{param_name(*parent_keys, key)}[]"] = value
74
+ else
75
+ params[param_name(*parent_keys, key)] = value
76
+ end
77
+ end
78
+
79
+ params
80
+ end
81
+
82
+ def self.param_name(*args)
83
+ first = args.shift
84
+ "#{first}" + args.map{ |arg| "[#{arg}]" }.join
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,25 @@
1
+ require 'yajl'
2
+
3
+ module Hover
4
+ module Decoder
5
+ class JSONStream
6
+
7
+ def initialize(s3_object, block = ->(object) {})
8
+ @parser = Yajl::Parser.new
9
+ @s3_object = s3_object
10
+
11
+ @parser.on_parse_complete = block
12
+ end
13
+
14
+ def start
15
+ bucket_name = @s3_object.bucket.name
16
+ key = @s3_object.key
17
+
18
+ @s3_object.client.get_object(bucket: bucket_name, key: key) do |chunk|
19
+ @parser << chunk
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module Hover
2
+ module Encoder
3
+ class JSONStream
4
+ attr_accessor :file
5
+
6
+ def initialize
7
+ end
8
+
9
+ def open(path)
10
+ self.file = File.open(path, 'w')
11
+ end
12
+
13
+ def close
14
+ self.file.close
15
+ self.file = nil
16
+ end
17
+
18
+ def append(attributes, klass)
19
+ self.file.puts(JSON.dump(attributes.merge('class' => klass.name)))
20
+ end
21
+
22
+ def append_each(attributes_sets, klass)
23
+ attributes_sets.each do |attributes|
24
+ append(attributes, klass)
25
+ end
26
+ end
27
+
28
+ def append_records(scope)
29
+ scope.find_each do |record|
30
+ append(record.attributes.as_json, record.class)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,134 @@
1
+ module Hover
2
+ module Importer
3
+ class ActiveRecord
4
+ attr_accessor :remote_local_map, :update_finder, :column_changes
5
+
6
+ def initialize(remote_local_map, update_finder = nil, column_changes = {})
7
+ self.remote_local_map = Hash.new { |hash, key| hash[key] = {} }
8
+ self.remote_local_map.merge!(remote_local_map)
9
+ self.update_finder = update_finder
10
+ self.column_changes = column_changes
11
+ end
12
+
13
+ def change_columns(attributes)
14
+ attributes.inject({}) do |hash, key_value|
15
+ hash[(column_changes[key_value[0]] || key_value[0])] = key_value[1]
16
+ hash
17
+ end
18
+ end
19
+
20
+ def update(table_name, records_attributes)
21
+ klass = table_name.singularize.camelize.constantize
22
+ records_attributes.collect { |attributes| update_record(klass, attributes) }
23
+ end
24
+
25
+ def update_record(klass, attributes)
26
+ return unless record = find_record_to_update(klass, attributes)
27
+
28
+ attributes = change_columns(attributes.dup)
29
+ s3_file_attributes = s3_file_keys(klass, attributes)
30
+
31
+ attributes.delete(klass.primary_key)
32
+ attributes.reject! { |key, value| !klass.column_names.include?(key) }
33
+ attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
34
+
35
+ record.assign_attributes(attributes)
36
+ record.save!(validate: false)
37
+
38
+ [klass, record.id]
39
+ end
40
+
41
+ def s3_file_keys(klass, attributes)
42
+ klass.try(:s3_file_versions).try(:keys).try(:inject, {}) do |output, attribute|
43
+ if value = attributes.delete(attribute.to_s)
44
+ output["#{attribute}_s3_keys"] = value
45
+ end
46
+
47
+ output
48
+ end
49
+ end
50
+
51
+ def find_record_to_update(klass, attributes)
52
+ self.update_finder.call(self, klass, attributes)
53
+ end
54
+
55
+ def import_results(results)
56
+ class_id_sets = []
57
+
58
+ results.each do |table_name, records_attributes|
59
+ if table_class_id_sets = import_table(table_name, records_attributes)
60
+ class_id_sets.concat(table_class_id_sets)
61
+ end
62
+ end
63
+
64
+ class_id_sets
65
+ end
66
+
67
+ def import_table(table_name, records_attributes)
68
+ klass = table_name.singularize.camelize.constantize
69
+
70
+ records_attributes.collect { |attributes|
71
+ import_record(klass, attributes) unless attributes.nil?
72
+ }
73
+ end
74
+
75
+ def import_record(klass, attributes)
76
+ attributes = change_columns(attributes.dup)
77
+ external_primary_key = attributes.delete(klass.primary_key)
78
+ s3_file_attributes = s3_file_keys(klass, attributes)
79
+
80
+ attributes.reject! { |key, value| !klass.column_names.include?(key) }
81
+ attributes.merge!(s3_file_attributes) if s3_file_attributes.is_a?(Hash)
82
+
83
+ record = klass.new(attributes)
84
+ return unless last_insert_id = insert_record(record)
85
+
86
+ remote_local_map[klass.table_name][external_primary_key] = last_insert_id
87
+
88
+ [klass, last_insert_id]
89
+ end
90
+
91
+ # see https://github.com/rails/rails/blob/v5.2.0/activerecord/lib/active_record/persistence.rb#L729-L741
92
+ def insert_record(record)
93
+ attribute_names = record.class.column_names
94
+ attributes_values = record.send(:attributes_with_values_for_create, attribute_names)
95
+
96
+ primary_key = record.class._insert_record(attributes_values)
97
+
98
+ record[record.class.primary_key] = primary_key
99
+ end
100
+
101
+ def new_foreign_keys(record)
102
+ record.class.reflect_on_all_associations.inject({}) do |hash, association|
103
+ method = "new_foreign_keys_for_#{association.macro}"
104
+ method += "_polymorphic" if association.options[:polymorphic]
105
+
106
+ if respond_to?(method) && before_after = send(method, record, association)
107
+ hash.store(*before_after)
108
+ end
109
+
110
+ hash
111
+ end
112
+ end
113
+
114
+ def new_foreign_keys_for_belongs_to(record, association)
115
+ return unless id = self.remote_local_map[association.klass.table_name].try(:[], record[association.foreign_key])
116
+ [association.foreign_key, id]
117
+ end
118
+
119
+ def new_foreign_keys_for_belongs_to_polymorphic(record, association)
120
+ return unless id = self.remote_local_map[record[association.foreign_type].tableize].try(:[], record[association.foreign_key])
121
+ [association.foreign_key, id]
122
+ end
123
+
124
+ def update_foreign_keys(record)
125
+ foreign_key_attributes = new_foreign_keys(record)
126
+ return if foreign_key_attributes.empty?
127
+
128
+ record.class.unscoped.where(id: record.id).update_all(foreign_key_attributes)
129
+
130
+ [record.reload.class, record.id]
131
+ end
132
+ end
133
+ end
134
+ end