lolitado 0.0.8 → 0.1.3
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 +4 -4
- data/.DS_Store +0 -0
- data/.gitignore +1 -0
- data/README.md +69 -0
- data/lib/.DS_Store +0 -0
- data/lib/lolitado.rb +85 -6
- data/lib/lolitado/api.rb +130 -0
- data/lib/lolitado/box.rb +44 -0
- data/lib/lolitado/db.rb +140 -0
- data/lib/lolitado/pool.rb +139 -0
- data/lib/{version.rb → lolitado/version.rb} +1 -1
- data/lolitado.gemspec +22 -0
- metadata +16 -26
- data/lib/accessors.rb +0 -22
- data/lib/box.rb +0 -29
- data/lib/db.rb +0 -27
- data/lib/pool.rb +0 -109
- data/lib/request.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e3340ad161dcc24cdbdf7f33fb3a5d3320e2f1d803b62450aaea1c675ea22e9
|
4
|
+
data.tar.gz: 841aa2524c08929995ad31d1b327a5845ff6f9dc9d85640ed5de8231f26c8153
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e03f1347e053cd101482de80c4cdc19c4f7ab7a707f21445a988d53490b1bdd570ebbfb4fc80fd1c9e1721fce697cefdd5859ff0748149147fd8c1545493a0bd
|
7
|
+
data.tar.gz: feb5d8b78188e22ae3afb1c591873bf2b0b85672b4565245a94d547099f5f5d4797c0f3f72d2c074b00904292b75961162cd3ee03d56a2c0bd58b38c1a943849
|
data/.DS_Store
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lolitado*.gem
|
data/README.md
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# lolitado
|
2
|
+
Library for using database and API
|
3
|
+
|
4
|
+
----
|
5
|
+
## How to call database?
|
6
|
+
|
7
|
+
|
8
|
+
You need to do these things:
|
9
|
+
|
10
|
+
* put all sql in sql.yml or in code
|
11
|
+
* put database connect info in xx_db.yml
|
12
|
+
----
|
13
|
+
run case like
|
14
|
+
|
15
|
+
````ruby
|
16
|
+
pool = Lolitado:Pool.new('xx_db.yml')
|
17
|
+
db = pool.use(:db =>'db_name')
|
18
|
+
sql = "select * from identity.user where phone = '+86 139 0000 0000"
|
19
|
+
result = db.query(sql)
|
20
|
+
````
|
21
|
+
----
|
22
|
+
## How to call API?
|
23
|
+
|
24
|
+
----
|
25
|
+
run case like
|
26
|
+
|
27
|
+
````ruby
|
28
|
+
* Rest API
|
29
|
+
class TestRest
|
30
|
+
include Lolitado
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
base_uri 'https://xxx.com'
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_details_of_a_city city_slug, locale
|
37
|
+
add_headers({'Accept-Language' => locale})
|
38
|
+
request('get', "/cities/#{city_slug}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
* Graph API
|
43
|
+
class TestGraph
|
44
|
+
include Lolitado
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
base_uri 'https://xxx.com'
|
48
|
+
end
|
49
|
+
|
50
|
+
def user_login payload
|
51
|
+
add_headers({'Content-Type' => "application/json"})
|
52
|
+
query = "mutation login($input: UserLoginInput!) {userLogin(input:$input) {authToken}}"
|
53
|
+
graph_request(query, payload)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
````
|
57
|
+
----
|
58
|
+
## How to encrypt/decrypt credential file?
|
59
|
+
|
60
|
+
* Initialize Box with secret key of environment variable
|
61
|
+
* file_encrypt to encrypt the file
|
62
|
+
* file_decrypt to decrypt the file
|
63
|
+
|
64
|
+
----
|
65
|
+
````ruby
|
66
|
+
box = Lolitado:Box.new('ENV_KEY')
|
67
|
+
box.file_encrypt('file_name')
|
68
|
+
box.file_decrypt('encrypt_file_name')
|
69
|
+
````
|
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/lolitado.rb
CHANGED
@@ -1,10 +1,89 @@
|
|
1
|
-
|
2
|
-
require 'db'
|
3
|
-
require 'request'
|
4
|
-
require 'box'
|
1
|
+
$:.unshift File.expand_path('..', __FILE__)
|
5
2
|
|
6
|
-
|
7
|
-
|
3
|
+
require 'lolitado/pool'
|
4
|
+
require 'lolitado/db'
|
5
|
+
require 'lolitado/api'
|
6
|
+
require 'lolitado/box'
|
8
7
|
|
8
|
+
#
|
9
|
+
# Module that when included adds functionality to an API(Rest/Graph) Class.
|
10
|
+
#
|
11
|
+
# How to use it ?
|
12
|
+
#
|
13
|
+
# @example Rest API
|
14
|
+
# class TestRest
|
15
|
+
# include Lolitado
|
16
|
+
|
17
|
+
# def initialize
|
18
|
+
# base_uri 'https://xxx.com'
|
19
|
+
# end
|
20
|
+
|
21
|
+
# def get_details_of_a_city city_slug, locale
|
22
|
+
# request('get', "/cities/#{city_slug}", locale)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @example Graph API
|
27
|
+
# class TestGraph
|
28
|
+
# include Lolitado
|
29
|
+
|
30
|
+
# def initialize
|
31
|
+
# base_uri 'https://xxx.com'
|
32
|
+
# end
|
33
|
+
|
34
|
+
# def user_login payload
|
35
|
+
# query = "mutation login($input: UserLoginInput!) {userLogin(input:$input) {authToken}}"
|
36
|
+
# graph_request(query, payload)
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
module Lolitado
|
40
|
+
|
41
|
+
#
|
42
|
+
# forward request method from Rest class to API.request method
|
43
|
+
#
|
44
|
+
# @param method [String] http request method
|
45
|
+
# @param endpoint [String] http request endpoint
|
46
|
+
# @param token [String] authorization token if query needed
|
47
|
+
# @param locale [String] locale if query needed
|
48
|
+
# @param body [String] http request body
|
49
|
+
#
|
50
|
+
def request method, endpoint, token = false, locale = false, body=false
|
51
|
+
add_headers({'Authorization' => "Bearer #{token}"}) if token
|
52
|
+
add_headers({'Accept-Language' => locale}) if locale
|
53
|
+
API.request(method, endpoint, body)
|
9
54
|
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# forward graph_request method from Graph class to Graph.request method
|
58
|
+
#
|
59
|
+
# @param query [String] graph query
|
60
|
+
# @param token [String] authorization token if query needed
|
61
|
+
# @param locale [String] locale if query needed
|
62
|
+
# @param variables [String] input variables for graph query using
|
63
|
+
#
|
64
|
+
def graph_request query, token = false, locale = false, variables = false
|
65
|
+
add_headers({'Content-Type' => "application/json"})
|
66
|
+
add_headers({'Authorization' => "Bearer #{token}"}) if token
|
67
|
+
add_headers({'Accept-Language' => locale}) if locale
|
68
|
+
Graph.request(query, variables)
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# forward add_headers from Rest/Graph class to API.add_headers method
|
73
|
+
#
|
74
|
+
# @param value [Hash] http header value
|
75
|
+
#
|
76
|
+
def add_headers value
|
77
|
+
API.add_headers value
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# forward uri from Rest/Graph class to API.uri method
|
82
|
+
#
|
83
|
+
# @param uri [String] http request uri
|
84
|
+
#
|
85
|
+
def base_uri uri
|
86
|
+
API.base_uri uri
|
87
|
+
end
|
88
|
+
|
10
89
|
end
|
data/lib/lolitado/api.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
module Lolitado
|
3
|
+
class API
|
4
|
+
include HTTParty
|
5
|
+
|
6
|
+
#
|
7
|
+
# format api response
|
8
|
+
#
|
9
|
+
# @param response [String] api response
|
10
|
+
# @param msecs [Float] benchmark for api response time
|
11
|
+
#
|
12
|
+
def self.format_response response, msecs
|
13
|
+
new_response = {:response => response, :message => response.parsed_response, :status => response.code, :duration => msecs}
|
14
|
+
response_with_hash_key = JSON.parse(JSON[new_response], symbolize_names: true)
|
15
|
+
return response_with_hash_key
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# define a customize http request method for rest api, forward this method to call HTTParty.get or HTTParty.post or HTTParty.put
|
20
|
+
# or HTTParty.delete method
|
21
|
+
#
|
22
|
+
# @param method [String] http request method
|
23
|
+
# @param endpoint [String] http request endpoint
|
24
|
+
# @param body [String] http request body
|
25
|
+
#
|
26
|
+
def self.request method, endpoint, body = false
|
27
|
+
start = Time.now
|
28
|
+
case method.to_s
|
29
|
+
when "get"
|
30
|
+
response = self.get(endpoint, :body => body, :headers => API.new_headers, :verify => false)
|
31
|
+
when "post"
|
32
|
+
response = self.post(endpoint, :body => body, :headers => API.new_headers, :verify => false)
|
33
|
+
when "put"
|
34
|
+
response = self.put(endpoint, :body => body, :headers => API.new_headers, :verify => false)
|
35
|
+
when "delete"
|
36
|
+
response = self.delete(endpoint, :headers => API.new_headers, :verify => false)
|
37
|
+
else
|
38
|
+
warn "#{method} is invalid http method."
|
39
|
+
end
|
40
|
+
finish = Time.now
|
41
|
+
msecs = (finish - start) * 1000.0
|
42
|
+
clear_headers
|
43
|
+
# puts "RESPONSE - #{msecs}"
|
44
|
+
return self.format_response(response, msecs)
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# define a method to set http headers
|
49
|
+
#
|
50
|
+
# @param value [Hash] http header value
|
51
|
+
#
|
52
|
+
def self.add_headers value
|
53
|
+
@new_headers ||= {}
|
54
|
+
@new_headers = @new_headers.merge(value.to_hash)
|
55
|
+
return @new_headers
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# define a method to get http headers
|
60
|
+
#
|
61
|
+
def self.new_headers
|
62
|
+
return @new_headers
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# clear headers
|
67
|
+
#
|
68
|
+
def self.clear_headers
|
69
|
+
API.new_headers.clear unless API.new_headers.nil?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Graph < API
|
74
|
+
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# define a customize http request method for graph api, forward this method to call HTTParty.get or HTTParty.post or HTTParty.put
|
78
|
+
# or HTTParty.delete method
|
79
|
+
#
|
80
|
+
# @param query [String] graph query
|
81
|
+
# @param variables [String] input variables for graph query using
|
82
|
+
#
|
83
|
+
def self.request query, variables = false
|
84
|
+
super(:post, '', generate_body(query, variables))
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# format api response, override the parent method in API class
|
89
|
+
#
|
90
|
+
# @param response [String] api response
|
91
|
+
# @param msecs [Float] benchmark for api response time
|
92
|
+
#
|
93
|
+
def self.format_response response, msecs
|
94
|
+
if response.parsed_response.is_a?(Hash)
|
95
|
+
if response.parsed_response.has_key?('errors')
|
96
|
+
message = response.parsed_response
|
97
|
+
else
|
98
|
+
message = response.parsed_response['data'].values[0]
|
99
|
+
end
|
100
|
+
else
|
101
|
+
message = {'errors'=>response.parsed_response}
|
102
|
+
end
|
103
|
+
new_response = {:response => response, :message => message, :status => response.code, :duration => msecs}
|
104
|
+
response_with_hash_key = JSON.parse(JSON[new_response], symbolize_names: true)
|
105
|
+
return response_with_hash_key
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# generate http request body for graph api response
|
110
|
+
#
|
111
|
+
# @param query [String] graph query
|
112
|
+
# @param variables [String] input variables for graph query using
|
113
|
+
#
|
114
|
+
def self.generate_body query, variables
|
115
|
+
body = {}
|
116
|
+
body['query'] = query
|
117
|
+
if variables
|
118
|
+
variable_indices = query.enum_for(:scan, /(?=\$.*:)/).map { Regexp.last_match.offset(0).first }
|
119
|
+
if query.include?('$input') && variable_indices.to_a.length == 1
|
120
|
+
body['variables'] = {'input'=>variables}
|
121
|
+
else
|
122
|
+
body['variables'] = variables
|
123
|
+
end
|
124
|
+
end
|
125
|
+
body['operationName'] = query.split(' ')[1].split('(')[0]
|
126
|
+
return body.to_json
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
data/lib/lolitado/box.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rbnacl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Lolitado
|
5
|
+
class Box
|
6
|
+
|
7
|
+
attr_accessor :box
|
8
|
+
|
9
|
+
#
|
10
|
+
# initialize box
|
11
|
+
#
|
12
|
+
# @param key [String] ENV key value used for initialize box
|
13
|
+
#
|
14
|
+
def initialize key
|
15
|
+
fail "There's no environment variable #{key}..." if ENV[key].nil?
|
16
|
+
key = Base64.decode64(ENV[key])
|
17
|
+
@box = RbNaCl::SimpleBox.from_secret_key(key)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# encrypt file
|
22
|
+
#
|
23
|
+
# @param file [String] the file need to be encrypted
|
24
|
+
#
|
25
|
+
def file_encrypt file
|
26
|
+
plaintext = File.read(file)
|
27
|
+
ciphertext = box.encrypt(plaintext)
|
28
|
+
enc_file = file + '.enc'
|
29
|
+
File.write(enc_file, Base64.encode64(ciphertext))
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# decrypt file
|
34
|
+
#
|
35
|
+
# @param file [String] the file need to be decrypted
|
36
|
+
#
|
37
|
+
def file_decrypt file
|
38
|
+
ciphertext = File.read(file)
|
39
|
+
plaintext = box.decrypt(Base64.decode64(ciphertext))
|
40
|
+
plain_file = file[0..-5]
|
41
|
+
File.write(plain_file, plaintext)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/lolitado/db.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Lolitado
|
2
|
+
class Database
|
3
|
+
|
4
|
+
attr_accessor :db
|
5
|
+
|
6
|
+
def initialize db
|
7
|
+
@db = db
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# Return the data by sql.
|
12
|
+
#
|
13
|
+
# @param [String] sql the sql you wanna query
|
14
|
+
#
|
15
|
+
def query sql
|
16
|
+
result = db[sql].all
|
17
|
+
return result
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Return the data by sql.
|
22
|
+
#
|
23
|
+
# @param [String] sql the sql you wanna query, the multiple sql should be split by ;
|
24
|
+
#
|
25
|
+
def multiple_query sql
|
26
|
+
splited_sql = sql.split(';')
|
27
|
+
splited_sql.each do |each_sql|
|
28
|
+
query(each_sql)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Return the data and execute duration by sql.
|
34
|
+
#
|
35
|
+
# @param [String] sql the sql you wanna query
|
36
|
+
# @param [Boolean] type the default value is true that the expected result is not empty, otherwise, the expected result is empty
|
37
|
+
# @param [Number] waiting_time the default value is 10 that maxium execute duration is 10 second
|
38
|
+
#
|
39
|
+
def query_duration sql, type = true, waiting_time = 10
|
40
|
+
start = Time.now
|
41
|
+
if type
|
42
|
+
result = query_wait(sql, waiting_time)
|
43
|
+
else
|
44
|
+
result = query_empty(sql, waiting_time)
|
45
|
+
end
|
46
|
+
finish = Time.now
|
47
|
+
msecs = (finish - start) * 1000.0
|
48
|
+
hash = {'result' => result, 'duration' => msecs}
|
49
|
+
return hash
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Expect to return data for the query. If the result is empty then it will be called again until waiting_time reach.
|
54
|
+
#
|
55
|
+
# @param [String] sql the sql you wanna query
|
56
|
+
# @param [Number] waiting_time the default value is 10 that maxium execute duration is 10 second
|
57
|
+
#
|
58
|
+
def query_wait sql, waiting_time = 10
|
59
|
+
result = db[sql].all
|
60
|
+
if result.empty?
|
61
|
+
if waiting_time != 0
|
62
|
+
sleep 1
|
63
|
+
result = query_wait(sql, waiting_time - 1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
return result
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Expect to return empty for the query. If the result is not empty then it will be called again until waiting_time reach.
|
71
|
+
#
|
72
|
+
# @param [String] sql the sql you wanna query
|
73
|
+
# @param [Number] waiting_time the default value is 10 that maxium execute duration is 10 second
|
74
|
+
#
|
75
|
+
def query_empty sql, waiting_time = 10
|
76
|
+
result = db[sql].all
|
77
|
+
if !result.empty?
|
78
|
+
if waiting_time != 0
|
79
|
+
sleep 1
|
80
|
+
result = query(sql, waiting_time - 1)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
return result
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Execute by sql, the sql is normally update or insert.
|
88
|
+
#
|
89
|
+
# @param [String] sql the sql you wanna execute
|
90
|
+
#
|
91
|
+
def execute sql
|
92
|
+
db[sql]
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Insert data by data.
|
97
|
+
#
|
98
|
+
# @param [String] data the data wanna insert which is array or hash
|
99
|
+
# @param [String] table the table wanna insert
|
100
|
+
#
|
101
|
+
def insert_by_data data, table
|
102
|
+
sql = "insert into #{table} "
|
103
|
+
case data
|
104
|
+
when Array
|
105
|
+
data.each do |d|
|
106
|
+
insert_by_data(d, table)
|
107
|
+
end
|
108
|
+
when Hash
|
109
|
+
columns = data.keys.to_s.gsub('[','(').gsub(']',')').gsub('"','')
|
110
|
+
values = data.values.to_s.gsub('[','(').gsub(']',')').gsub('nil','NULL')
|
111
|
+
sql = sql + columns + " values " + values
|
112
|
+
query(sql)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Update data by data.
|
118
|
+
#
|
119
|
+
# @param [Hash] data the data wanna update which is hash
|
120
|
+
# @param [String] table the table for the update
|
121
|
+
# @param [Hash] condition the condition for the update
|
122
|
+
#
|
123
|
+
def update data, table, condition = {}
|
124
|
+
sql = "update #{table} set"
|
125
|
+
data.each do |k,v|
|
126
|
+
v = v.to_json if v.is_a?(Hash)
|
127
|
+
if !!v == v
|
128
|
+
sql = "#{sql} #{k}=#{v},"
|
129
|
+
else
|
130
|
+
sql = "#{sql} #{k}='#{v}',"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
sql = sql[0..-2] + " where"
|
134
|
+
condition.each do |k,v|
|
135
|
+
sql = "#{sql} #{k} = '#{v}' and"
|
136
|
+
end
|
137
|
+
query(sql[0..-4])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'mysql2'
|
2
|
+
require 'sequel'
|
3
|
+
require 'net/ssh/gateway'
|
4
|
+
require 'psych'
|
5
|
+
require_relative 'db'
|
6
|
+
|
7
|
+
module Lolitado
|
8
|
+
class Pool
|
9
|
+
|
10
|
+
attr_accessor :db_pool, :ssh_pool, :file
|
11
|
+
|
12
|
+
#
|
13
|
+
# initialize Lolitado::Pool, generate db_pool, ssh_pool object to use
|
14
|
+
#
|
15
|
+
# @param file_path [String] the configure file to used for initialize
|
16
|
+
#
|
17
|
+
def initialize file_path
|
18
|
+
@db_pool = {}
|
19
|
+
@ssh_pool = {}
|
20
|
+
@file = Psych.load_file(file_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
#
|
24
|
+
# use existing db or ssh pool or switch to another db and ssh pool
|
25
|
+
#
|
26
|
+
# @param params [Hash] key, value used to use db/ssh pool
|
27
|
+
#
|
28
|
+
# @example
|
29
|
+
# pool.use(:db => 'db_name')
|
30
|
+
#
|
31
|
+
def use(params = {})
|
32
|
+
ssh_name = params.fetch(:ssh, false)
|
33
|
+
db_name = params.fetch(:db, false)
|
34
|
+
if db_name
|
35
|
+
return get_db db_name
|
36
|
+
elsif ssh_name
|
37
|
+
return get_ssh ssh_name
|
38
|
+
else
|
39
|
+
fail "Please provide :ssh or :db"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# return existing db object or create new db object
|
45
|
+
#
|
46
|
+
# @param name [String] db configure name in yaml file
|
47
|
+
#
|
48
|
+
def get_db name
|
49
|
+
if db_pool[name].nil?
|
50
|
+
db_conf = file[name]
|
51
|
+
if db_conf['proxy'].nil?
|
52
|
+
db = connect_database db_conf
|
53
|
+
else
|
54
|
+
db = connect_database_by_proxy db_conf
|
55
|
+
end
|
56
|
+
db_pool[name] = Database.new(db)
|
57
|
+
end
|
58
|
+
return db_pool[name]
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# return existing ssh object or create new ssh object
|
63
|
+
#
|
64
|
+
# @param name [String] ssh configure name in yaml file
|
65
|
+
#
|
66
|
+
def get_ssh name
|
67
|
+
if ssh_pool[name].nil?
|
68
|
+
ssh = connect_remote_server name
|
69
|
+
ssh_pool[name] = ssh
|
70
|
+
else
|
71
|
+
ssh_pool[name]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# connect database by ssh proxy
|
77
|
+
#
|
78
|
+
# @param conf [Hash] configuration for connect remote databse via ssh
|
79
|
+
#
|
80
|
+
def connect_database_by_proxy conf
|
81
|
+
port = forward_port conf['proxy']
|
82
|
+
connect_database conf, port
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# connect databse
|
87
|
+
#
|
88
|
+
# @param conf [Hash] configuration for connect databse directly
|
89
|
+
# @pram port [Integer] forward port
|
90
|
+
#
|
91
|
+
def connect_database conf, port = false
|
92
|
+
conf.delete('proxy') if port
|
93
|
+
conf = conf.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
94
|
+
conf = conf.merge(:port => port) if port
|
95
|
+
begin
|
96
|
+
Sequel.connect(conf)
|
97
|
+
rescue
|
98
|
+
fail "database configuration \n #{conf} \n is not correct, please double check"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# connect remote server
|
104
|
+
#
|
105
|
+
# @param name [String] the ssh server name wanna to connect
|
106
|
+
#
|
107
|
+
def connect_remote_server name
|
108
|
+
ssh_conf = file[name]
|
109
|
+
host = ssh_conf.delete('host')
|
110
|
+
user = ssh_conf.delete('user')
|
111
|
+
options = ssh_conf.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
112
|
+
begin
|
113
|
+
Net::SSH::Gateway.new(host, user, options)
|
114
|
+
rescue
|
115
|
+
fail "ssh configuration \n #{ssh_conf} \n is not correct, please double check"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# forward port
|
121
|
+
#
|
122
|
+
# @param conf [Hash] configuration for generate forward port
|
123
|
+
#
|
124
|
+
def forward_port conf
|
125
|
+
ssh = get_ssh conf['ssh']
|
126
|
+
host = conf['database']['host']
|
127
|
+
remote_port = conf['database']['remote_port']
|
128
|
+
local_port = conf['database']['local_port']
|
129
|
+
# puts "forward remote port #{remote_port} to local port #{local_port}"
|
130
|
+
begin
|
131
|
+
ssh.open(host, remote_port, local_port)
|
132
|
+
rescue Errno::EADDRINUSE
|
133
|
+
return local_port
|
134
|
+
rescue
|
135
|
+
fail "fail to forward remote port #{remote_port} to local_port #{local_port}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lolitado.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "lolitado/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "lolitado"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.version = Lolitado::VERSION
|
9
|
+
s.date = '2018-12-14'
|
10
|
+
s.authors = ["Dolores Zhang"]
|
11
|
+
s.email = ["379808610@qq.com"]
|
12
|
+
s.homepage = "http://github.com/doloreszhang/lolitado"
|
13
|
+
s.summary = %q{Lolitado DSL for database process}
|
14
|
+
s.description = %q{Lolitado DSL that works with Watir}
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.license = 'MIT'
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.add_dependency "sequel", "~> 4.37"
|
19
|
+
s.add_dependency "net-ssh-gateway", "~> 1.2"
|
20
|
+
s.add_dependency "httparty", "~> 0.14"
|
21
|
+
s.add_dependency "rbnacl-libsodium", "~> 1.0.10"
|
22
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lolitado
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dolores Zhang
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
date: 2018-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: mysql2
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '0.4'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '0.4'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: sequel
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -87,18 +73,22 @@ executables: []
|
|
87
73
|
extensions: []
|
88
74
|
extra_rdoc_files: []
|
89
75
|
files:
|
90
|
-
-
|
91
|
-
-
|
92
|
-
-
|
76
|
+
- ".DS_Store"
|
77
|
+
- ".gitignore"
|
78
|
+
- README.md
|
79
|
+
- lib/.DS_Store
|
93
80
|
- lib/lolitado.rb
|
94
|
-
- lib/
|
95
|
-
- lib/
|
96
|
-
- lib/
|
81
|
+
- lib/lolitado/api.rb
|
82
|
+
- lib/lolitado/box.rb
|
83
|
+
- lib/lolitado/db.rb
|
84
|
+
- lib/lolitado/pool.rb
|
85
|
+
- lib/lolitado/version.rb
|
86
|
+
- lolitado.gemspec
|
97
87
|
homepage: http://github.com/doloreszhang/lolitado
|
98
88
|
licenses:
|
99
89
|
- MIT
|
100
90
|
metadata: {}
|
101
|
-
post_install_message:
|
91
|
+
post_install_message:
|
102
92
|
rdoc_options: []
|
103
93
|
require_paths:
|
104
94
|
- lib
|
@@ -113,9 +103,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
103
|
- !ruby/object:Gem::Version
|
114
104
|
version: '0'
|
115
105
|
requirements: []
|
116
|
-
rubyforge_project:
|
117
|
-
rubygems_version: 2.7.
|
118
|
-
signing_key:
|
106
|
+
rubyforge_project:
|
107
|
+
rubygems_version: 2.7.6
|
108
|
+
signing_key:
|
119
109
|
specification_version: 4
|
120
110
|
summary: Lolitado DSL for database process
|
121
111
|
test_files: []
|
data/lib/accessors.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Contains the class level methods that are inserted into your api class
|
3
|
-
#
|
4
|
-
module Accessors
|
5
|
-
|
6
|
-
# just for example "how to create method in accesors"
|
7
|
-
def row name, identifier
|
8
|
-
define_method("delete_#{name}") do |value|
|
9
|
-
db[identifier[:table].to_sym].filter(value).delete
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
def column name, identifier
|
14
|
-
define_method("avg_#{name}") do |value|
|
15
|
-
db[identifier[:table].to_sym].filter(value).avg(identifier.values.last.to_sym)
|
16
|
-
end
|
17
|
-
define_method("query_#{name}") do |value|
|
18
|
-
db[identifier[:table].to_sym].filter(value).all
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
end
|
data/lib/box.rb
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'rbnacl'
|
2
|
-
require 'base64'
|
3
|
-
|
4
|
-
class Box
|
5
|
-
|
6
|
-
def initialize key
|
7
|
-
key_env = ENV[key]
|
8
|
-
if key_env.nil?
|
9
|
-
puts "There's no environment variable #{key}..."
|
10
|
-
return
|
11
|
-
end
|
12
|
-
key = Base64.decode64(key_env)
|
13
|
-
@box = RbNaCl::SimpleBox.from_secret_key(key)
|
14
|
-
end
|
15
|
-
|
16
|
-
def file_encrypt file
|
17
|
-
plaintext = File.read(file)
|
18
|
-
ciphertext = @box.encrypt(plaintext)
|
19
|
-
enc_file = file + '.enc'
|
20
|
-
File.write(enc_file, Base64.encode64(ciphertext))
|
21
|
-
end
|
22
|
-
|
23
|
-
def file_decrypt file
|
24
|
-
ciphertext = File.read(file)
|
25
|
-
plaintext = @box.decrypt(Base64.decode64(ciphertext))
|
26
|
-
plain_file = file[0..-5]
|
27
|
-
File.write(plain_file, plaintext)
|
28
|
-
end
|
29
|
-
end
|
data/lib/db.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'accessors'
|
2
|
-
|
3
|
-
class DBFactory
|
4
|
-
extend Accessors
|
5
|
-
|
6
|
-
attr_accessor :db
|
7
|
-
|
8
|
-
def initialize db
|
9
|
-
@db = db
|
10
|
-
end
|
11
|
-
|
12
|
-
def query sql
|
13
|
-
result = db[sql].all
|
14
|
-
return result
|
15
|
-
end
|
16
|
-
|
17
|
-
def multiple_query sql
|
18
|
-
splited_sql = sql.split(';')
|
19
|
-
splited_sql.each do |each_sql|
|
20
|
-
query(each_sql)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def insert sql
|
25
|
-
db[sql]
|
26
|
-
end
|
27
|
-
end
|
data/lib/pool.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
require 'mysql2'
|
2
|
-
require 'sequel'
|
3
|
-
require 'net/ssh/gateway'
|
4
|
-
|
5
|
-
class Pool
|
6
|
-
|
7
|
-
attr_accessor :db_pool, :ssh_pool, :file_path
|
8
|
-
|
9
|
-
def initialize file_path
|
10
|
-
@db_pool = {}
|
11
|
-
@ssh_pool = {}
|
12
|
-
@file_path = file_path
|
13
|
-
end
|
14
|
-
|
15
|
-
def use(params ={})
|
16
|
-
ssh_name = params.fetch(:ssh, false)
|
17
|
-
db_name = params.fetch(:db, false)
|
18
|
-
if db_pool[db_name].nil? && db_name
|
19
|
-
ssh_key = fetch_ssh_config db_name
|
20
|
-
unless ssh_key.nil?
|
21
|
-
ssh = create_or_use_ssh ssh_key
|
22
|
-
port = forward_port ssh, ssh_key
|
23
|
-
db = connect_database db_name, port
|
24
|
-
db_pool[db_name] = db
|
25
|
-
else
|
26
|
-
db = connect_database_directly db_name
|
27
|
-
db_pool[db_name] = db
|
28
|
-
end
|
29
|
-
elsif ssh_name
|
30
|
-
create_or_use_ssh ssh_name
|
31
|
-
else
|
32
|
-
db_pool[db_name]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def create_or_use_ssh name
|
37
|
-
if ssh_pool[name].nil?
|
38
|
-
ssh = connect_remote_server name
|
39
|
-
ssh_pool[name] = ssh
|
40
|
-
else
|
41
|
-
ssh_pool[name]
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def connect_database name, port=false
|
46
|
-
database_file = YAML.load_file(file_path)
|
47
|
-
db_conf = database_file[name]
|
48
|
-
db_conf = db_conf.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
49
|
-
db_conf = db_conf.merge(:port => port) if port
|
50
|
-
db = get_ready_for_database db_conf
|
51
|
-
end
|
52
|
-
|
53
|
-
def get_ready_for_database conf
|
54
|
-
begin
|
55
|
-
Sequel.connect(conf)
|
56
|
-
rescue
|
57
|
-
fail "database configuration \n #{conf} \n is not correct, please double check"
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def connect_remote_server name
|
62
|
-
database_file = YAML.load_file(file_path)
|
63
|
-
ssh_conf = database_file[name]
|
64
|
-
ssh_conf = ssh_conf.delete_if { |key,value| key == 'database'}
|
65
|
-
ssh = get_ready_for_ssh ssh_conf
|
66
|
-
return ssh
|
67
|
-
end
|
68
|
-
|
69
|
-
def get_ready_for_ssh conf
|
70
|
-
host = conf.delete('host')
|
71
|
-
user = conf.delete('user')
|
72
|
-
options = conf.delete_if {|key,value| key == 'host' && 'user'}
|
73
|
-
options = options.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
74
|
-
begin
|
75
|
-
Net::SSH::Gateway.new(host, user, options)
|
76
|
-
rescue
|
77
|
-
fail "ssh configuration \n #{conf} \n is not correct, please double check"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def forward_port ssh, name
|
82
|
-
database_file = YAML.load_file(file_path)
|
83
|
-
ssh_conf = database_file[name]
|
84
|
-
new_conf = ssh_conf.delete('database')
|
85
|
-
host = new_conf.delete('host')
|
86
|
-
remote_port = new_conf.delete('remote_port')
|
87
|
-
local_port = new_conf.delete('local_port')
|
88
|
-
begin
|
89
|
-
ssh.open(host, remote_port, local_port)
|
90
|
-
rescue Errno::EADDRINUSE
|
91
|
-
return local_port
|
92
|
-
rescue
|
93
|
-
fail "fail to forward remote port #{remote_port} to local_port #{local_port}"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def fetch_ssh_config db_name
|
98
|
-
database_file = YAML.load_file(file_path)
|
99
|
-
db_conf = database_file[db_name]
|
100
|
-
ssh_key = db_conf.delete('ssh')
|
101
|
-
end
|
102
|
-
|
103
|
-
def connect_database_directly db_name
|
104
|
-
database_file = YAML.load_file(file_path)
|
105
|
-
db_conf = database_file[db_name]
|
106
|
-
db_conf = db_conf.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
107
|
-
db = Sequel.connect(db_conf)
|
108
|
-
end
|
109
|
-
end
|
data/lib/request.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'httparty'
|
2
|
-
|
3
|
-
class Request
|
4
|
-
include HTTParty
|
5
|
-
|
6
|
-
attr_accessor :new_headers
|
7
|
-
# debug_output $stdout
|
8
|
-
|
9
|
-
def new_response response, msecs
|
10
|
-
new_response = {}
|
11
|
-
new_response.merge({:response => response, :message => response.parsed_response, :status => response.code, :duration => msecs})
|
12
|
-
end
|
13
|
-
|
14
|
-
def request base_uri, method, endpoint, body = false
|
15
|
-
url = base_uri + endpoint
|
16
|
-
case method
|
17
|
-
when "get"
|
18
|
-
start = Time.now
|
19
|
-
response = self.class.get(url, :body => body, :headers => new_headers, :verify => false)
|
20
|
-
finish = Time.now
|
21
|
-
when "post"
|
22
|
-
start = Time.now
|
23
|
-
response = self.class.post(url, :body => body, :headers => new_headers, :verify => false)
|
24
|
-
finish = Time.now
|
25
|
-
when "put"
|
26
|
-
start = Time.now
|
27
|
-
response = self.class.put(url, :body => body, :headers => new_headers, :verify => false)
|
28
|
-
finish = Time.now
|
29
|
-
when "delete"
|
30
|
-
start = Time.now
|
31
|
-
response = self.class.delete(url, :headers => new_headers, :verify => false)
|
32
|
-
finish = Time.now
|
33
|
-
else
|
34
|
-
puts "#{method} is invalid method."
|
35
|
-
exit
|
36
|
-
end
|
37
|
-
msecs = (finish - start) * 1000.0
|
38
|
-
return new_response(response, msecs)
|
39
|
-
end
|
40
|
-
|
41
|
-
def add_headers value
|
42
|
-
@new_headers ||= {}
|
43
|
-
@new_headers = @new_headers.merge(value.to_hash)
|
44
|
-
return @new_headers
|
45
|
-
end
|
46
|
-
end
|