ravendb-manager 0.1.0
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/ravendb-manager.rb +348 -0
- metadata +44 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23e22ef63e24ebe6f613b1fb8f7ee1602d84a161
|
4
|
+
data.tar.gz: 51c247984693b2bc4c319e7c0bda3b99bdac3d02
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a39fcd7615ac584cac75301a627de77a88c15316b4a0d97f1fd9bb5efb9cc2c82685f118f36228fa19d3230029e4ee533f7585855f14b313851aa1d5419fa911
|
7
|
+
data.tar.gz: a69fd21bcb89ce9294f24d31630543a02e27c7e30aba318cc93d0e23dec5c51d2fda0ce6c7be103704e1684553afda438501867ed4f203f0597f443f7545e64e
|
@@ -0,0 +1,348 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'digest/sha1'
|
3
|
+
require 'base64'
|
4
|
+
require 'securerandom'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
class RavenDBManager
|
8
|
+
@hostname
|
9
|
+
@port
|
10
|
+
@api_key_name
|
11
|
+
@api_key_secret
|
12
|
+
@bearer_token
|
13
|
+
|
14
|
+
@uri
|
15
|
+
@oauth_endpoint
|
16
|
+
|
17
|
+
def self.create(server_url, api_key)
|
18
|
+
uri = URI.parse(server_url)
|
19
|
+
api_key_parts = api_key.split('/')
|
20
|
+
|
21
|
+
return RavenDBManager.new(uri.host, uri.port, api_key_parts[0], api_key_parts[1])
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(hostname, port, api_key_name, api_key_secret)
|
25
|
+
raise 'hostname is empty' unless !hostname.to_s.empty?
|
26
|
+
raise 'port is invalid' unless port.to_i != 0
|
27
|
+
raise 'api_key_name empty' unless !api_key_name.to_s.empty?
|
28
|
+
raise 'api_key_secret empty' unless !api_key_secret.to_s.empty?
|
29
|
+
|
30
|
+
@hostname = hostname.to_s
|
31
|
+
@port = port.to_i
|
32
|
+
@api_key_name = api_key_name.to_s
|
33
|
+
@api_key_secret = api_key_secret.to_s
|
34
|
+
|
35
|
+
@uri = "http://#{@hostname}:#{port}"
|
36
|
+
@oauth_endpoint = URI.parse("#{@uri}/OAuth/API-Key")
|
37
|
+
|
38
|
+
@bearer_token = get_bearer_token
|
39
|
+
end
|
40
|
+
|
41
|
+
def bearer_token
|
42
|
+
@bearer_token
|
43
|
+
end
|
44
|
+
|
45
|
+
def list_databases
|
46
|
+
start = 0
|
47
|
+
databases = []
|
48
|
+
|
49
|
+
results = []
|
50
|
+
loop do
|
51
|
+
response = http_get("/databases/?pageSize=1024&start=#{start}")
|
52
|
+
raise get_error_string(response) unless is_success?(response)
|
53
|
+
results = JSON.parse(response.body)
|
54
|
+
databases = databases + results
|
55
|
+
start += 1024
|
56
|
+
break if results.length < 1024
|
57
|
+
end
|
58
|
+
|
59
|
+
return databases
|
60
|
+
end
|
61
|
+
|
62
|
+
def list_filesystems
|
63
|
+
start = 0
|
64
|
+
filesystems = []
|
65
|
+
|
66
|
+
results = []
|
67
|
+
loop do
|
68
|
+
response = http_get("/fs/?pageSize=1024&start=#{start}")
|
69
|
+
raise get_error_string(response) unless is_success?(response)
|
70
|
+
results = JSON.parse(response.body)
|
71
|
+
filesystems = filesystems + results
|
72
|
+
start += 1024
|
73
|
+
break if results.length < 1024
|
74
|
+
end
|
75
|
+
|
76
|
+
return filesystems
|
77
|
+
end
|
78
|
+
|
79
|
+
def version
|
80
|
+
response = http_get('/databases/<system>/build/version')
|
81
|
+
raise get_error_string(response) unless is_success?(response)
|
82
|
+
return JSON.parse(response.body)
|
83
|
+
end
|
84
|
+
|
85
|
+
def alerts(database = '<system>')
|
86
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
87
|
+
|
88
|
+
doc = get_document(database, 'Raven/Alerts')
|
89
|
+
return doc.nil? ? [] : doc['Alerts']
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_database(name, useVoron = false)
|
93
|
+
raise "Invalid database name #{name}" unless is_valid_string?(name)
|
94
|
+
raise "Invalid parameter useVoron" unless !!useVoron == useVoron
|
95
|
+
|
96
|
+
db_document = {
|
97
|
+
'Id' => name,
|
98
|
+
'Settings' => {
|
99
|
+
'Raven/ActiveBundles' => "",
|
100
|
+
'Raven/DataDir' => "~/#{name}",
|
101
|
+
'Raven/StorageTypeName' => useVoron ? 'voron' : 'esent'
|
102
|
+
},
|
103
|
+
'SecuredSettings'=> {},
|
104
|
+
'Disabled' => false}
|
105
|
+
.to_json
|
106
|
+
|
107
|
+
response = http_put("/admin/databases/#{name}", db_document)
|
108
|
+
raise get_error_string(response) unless is_success?(response)
|
109
|
+
return nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_filesystem(name)
|
113
|
+
raise "Invalid filesystem name #{name}" unless is_valid_string?(name)
|
114
|
+
|
115
|
+
fs_document = {
|
116
|
+
'Id' => name,
|
117
|
+
'Settings' => {
|
118
|
+
'Raven/ActiveBundles' => "",
|
119
|
+
'Raven/FileSystem/DataDir' => "~/#{name}"
|
120
|
+
},
|
121
|
+
'SecuredSettings'=> {},
|
122
|
+
'Disabled' => false}
|
123
|
+
.to_json
|
124
|
+
|
125
|
+
response = http_put("/admin/fs/#{name}", fs_document)
|
126
|
+
raise get_error_string(response) unless is_success?(response)
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_document(database, key)
|
131
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
132
|
+
raise "Invalid key #{key}" unless key.to_s.length > 0
|
133
|
+
|
134
|
+
response = http_get("/databases/#{database}/docs?id=#{key}")
|
135
|
+
if is_success?(response)
|
136
|
+
return JSON.parse(response.body)
|
137
|
+
elsif response.code == '404'
|
138
|
+
return nil
|
139
|
+
else
|
140
|
+
raise get_error_string(response)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def put_document(database, key, document)
|
145
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
146
|
+
raise "Invalid key #{key}" unless key.to_s.length > 0
|
147
|
+
raise "Invalid document" if document.nil?
|
148
|
+
|
149
|
+
response = http_put("/databases/#{database}/docs/#{key}", document.to_json)
|
150
|
+
raise get_error_string(response) unless is_success?(response)
|
151
|
+
return JSON.parse(response.body)['Key']
|
152
|
+
end
|
153
|
+
|
154
|
+
def delete_document(database, key)
|
155
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
156
|
+
raise "Invalid key #{key}" unless key.to_s.length > 0
|
157
|
+
|
158
|
+
response = http_delete("/databases/#{database}/docs/#{key}")
|
159
|
+
raise get_error_string(response) unless is_success?(response)
|
160
|
+
return nil
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_indexing_status(database)
|
164
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
165
|
+
|
166
|
+
response = http_get("/databases/#{database}/admin/IndexingStatus")
|
167
|
+
raise get_error_string(response) unless is_success?(response)
|
168
|
+
return JSON.parse(response.body)['IndexingStatus']
|
169
|
+
end
|
170
|
+
|
171
|
+
def compact_database(database)
|
172
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
173
|
+
|
174
|
+
response = http_post("/admin/compact?database=#{database}")
|
175
|
+
raise get_error_string(response) unless is_success?(response)
|
176
|
+
return JSON.parse(response.body)['OperationId']
|
177
|
+
end
|
178
|
+
|
179
|
+
def get_operation_status(operation_id)
|
180
|
+
raise "Invalid OperationId #{operation_id}" if operation_id.nil?
|
181
|
+
|
182
|
+
response = http_get("/operation/status?id=#{operation_id}")
|
183
|
+
return is_success?(response) ? JSON.parse(response.body) : nil
|
184
|
+
end
|
185
|
+
|
186
|
+
def database_statistics(database)
|
187
|
+
raise "Invalid database name #{database}" unless is_valid_string?(database)
|
188
|
+
|
189
|
+
response = http_get("/databases/#{database}/stats")
|
190
|
+
raise get_error_string(response) unless is_success?(response)
|
191
|
+
return JSON.parse(response.body)
|
192
|
+
end
|
193
|
+
|
194
|
+
def server_statistics
|
195
|
+
response = http_get("/admin/stats")
|
196
|
+
raise get_error_string(response) unless is_success?(response)
|
197
|
+
return JSON.parse(response.body)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Backup ...
|
201
|
+
|
202
|
+
def create_api_key(name)
|
203
|
+
raise "Invalid ApiKey name #{name}" unless is_valid_string?(name)
|
204
|
+
|
205
|
+
document = {
|
206
|
+
'Enabled' => true,
|
207
|
+
'Name' => name,
|
208
|
+
'Secret' => generate_secret,
|
209
|
+
'Databases': []
|
210
|
+
}
|
211
|
+
key = "Raven/ApiKeys/#{name}"
|
212
|
+
put_document('<system>', key, document)
|
213
|
+
return "#{name}/#{document['Secret']}"
|
214
|
+
end
|
215
|
+
|
216
|
+
def add_db_to_api_key(api_key_name, db_name, admin = false, readonly = false)
|
217
|
+
raise "Invalid ApiKey name #{api_key_name}" unless is_valid_string?(api_key_name)
|
218
|
+
raise "Invalid database name #{db_name}" unless is_valid_string?(db_name)
|
219
|
+
raise "Invalid parameter admin" unless !!admin == admin
|
220
|
+
raise "Invalid parameter readonly" unless !!readonly == readonly
|
221
|
+
|
222
|
+
key = "Raven/ApiKeys/#{api_key_name}"
|
223
|
+
document = get_document('<system>', key)
|
224
|
+
raise "API-Key #{api_key_name} doesn't exist" if document.nil?
|
225
|
+
document['Databases'] = document['Databases'].select {|db| db['TenantId'] != db_name}
|
226
|
+
document['Databases'].push({'TenantId' => db_name, 'admin' => admin, 'readonly' => readonly})
|
227
|
+
put_document('<system>', key, document)
|
228
|
+
return nil
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
233
|
+
PASSWORD_CHARS = [*('a'..'z'),*('A'..'Z'),*(0..9)]
|
234
|
+
NAME_PATTERN = /^[a-zA-Z0-9.-<>]+$/
|
235
|
+
|
236
|
+
def generate_secret
|
237
|
+
return (16 + SecureRandom.random_number(4)).times.map { PASSWORD_CHARS[SecureRandom.random_number(PASSWORD_CHARS.length)] }.join
|
238
|
+
end
|
239
|
+
|
240
|
+
def is_valid_string?(str)
|
241
|
+
return !NAME_PATTERN.match(str).nil?
|
242
|
+
end
|
243
|
+
|
244
|
+
def is_success?(http_response)
|
245
|
+
code = http_response.code.to_i
|
246
|
+
return code >= 200 && code <= 299
|
247
|
+
end
|
248
|
+
|
249
|
+
def get_error_string(http_error_response)
|
250
|
+
if http_error_response.methods.include?(:body) and !http_error_response.body.to_s.empty?
|
251
|
+
return http_error_response.body
|
252
|
+
else
|
253
|
+
return "#{http_error_response.code} #{http_error_response.message}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def http_get(url)
|
258
|
+
http = Net::HTTP.new(@hostname, @port)
|
259
|
+
request = Net::HTTP::Get.new(URI.escape(url))
|
260
|
+
request['Authorization'] = "Bearer #{@bearer_token}"
|
261
|
+
return http.request(request)
|
262
|
+
end
|
263
|
+
|
264
|
+
def http_delete(url)
|
265
|
+
http = Net::HTTP.new(@hostname, @port)
|
266
|
+
request = Net::HTTP::Delete.new(URI.escape(url))
|
267
|
+
request['Authorization'] = "Bearer #{@bearer_token}"
|
268
|
+
return http.request(request)
|
269
|
+
end
|
270
|
+
|
271
|
+
def http_put(url, document)
|
272
|
+
http = Net::HTTP.new(@hostname, @port)
|
273
|
+
request = Net::HTTP::Put.new(URI.escape(url))
|
274
|
+
request['Authorization'] = "Bearer #{@bearer_token}"
|
275
|
+
request.body = document
|
276
|
+
return http.request(request)
|
277
|
+
end
|
278
|
+
|
279
|
+
def http_post(url, document = nil)
|
280
|
+
http = Net::HTTP.new(@hostname, @port)
|
281
|
+
request = Net::HTTP::Post.new(URI.escape(url))
|
282
|
+
request['Authorization'] = "Bearer #{@bearer_token}"
|
283
|
+
request.body = document unless document.nil?
|
284
|
+
return http.request(request)
|
285
|
+
end
|
286
|
+
|
287
|
+
def get_bearer_token
|
288
|
+
request = get_auth_request_hash
|
289
|
+
response = prepare_response(request['challenge'])
|
290
|
+
public_key = create_rsa_key(request['modulus'], request['exponent'])
|
291
|
+
|
292
|
+
data = "api key name=#{@api_key_name},challenge=#{request['challenge']},response=#{response}"
|
293
|
+
response_data = "exponent=#{request['exponent']},modulus=#{request['modulus']},data=#{encrypt(public_key, data)}"
|
294
|
+
get_auth_token(response_data)
|
295
|
+
end
|
296
|
+
|
297
|
+
def get_auth_request_hash
|
298
|
+
http = Net::HTTP.new(@server_url, 8088)
|
299
|
+
res = Net::HTTP.post_form(@oauth_endpoint, {})
|
300
|
+
|
301
|
+
wwwauth_header = res.header['www-authenticate']
|
302
|
+
wwwauth_header[7, wwwauth_header.length - 7] # Remove 'Raven '
|
303
|
+
.split(',')
|
304
|
+
.map{|str|
|
305
|
+
idx = str.index '='
|
306
|
+
[str[0, idx].strip.downcase, str[idx+1, str.length]]
|
307
|
+
}
|
308
|
+
.to_h
|
309
|
+
end
|
310
|
+
|
311
|
+
def get_auth_token (response_data)
|
312
|
+
req = Net::HTTP::Post.new(@oauth_endpoint.request_uri)
|
313
|
+
req.add_field('grant-type', 'client_credentials')
|
314
|
+
req.body = response_data
|
315
|
+
|
316
|
+
res = Net::HTTP.new(@oauth_endpoint.host, @oauth_endpoint.port).request(req)
|
317
|
+
res.body.to_s
|
318
|
+
end
|
319
|
+
|
320
|
+
def prepare_response (challenge)
|
321
|
+
input = challenge + ";" + @api_key_secret
|
322
|
+
Digest::SHA1.base64digest input
|
323
|
+
end
|
324
|
+
|
325
|
+
def create_rsa_key (modulus_base64, exponent_base64)
|
326
|
+
key = OpenSSL::PKey::RSA.new
|
327
|
+
exponent = OpenSSL::BN.new(Base64.decode64(exponent_base64).unpack("H*").first, 16)
|
328
|
+
modulus = OpenSSL::BN.new(Base64.decode64(modulus_base64).unpack("H*").first, 16)
|
329
|
+
key.e = exponent
|
330
|
+
key.n = modulus
|
331
|
+
key
|
332
|
+
end
|
333
|
+
|
334
|
+
def encrypt(public_key, data)
|
335
|
+
key = SecureRandom.random_bytes(32)
|
336
|
+
iv = SecureRandom.random_bytes(16)
|
337
|
+
|
338
|
+
key_and_iv_encrypted = public_key.public_encrypt(key + iv, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
|
339
|
+
|
340
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
341
|
+
cipher.encrypt
|
342
|
+
cipher.key = key
|
343
|
+
cipher.iv = iv
|
344
|
+
encrypted = cipher.update(data) + cipher.final
|
345
|
+
|
346
|
+
Base64.encode64 (key_and_iv_encrypted + encrypted)
|
347
|
+
end
|
348
|
+
end
|
metadata
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ravendb-manager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christoph Enzmann
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-31 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Allows to manage and monitor your RavenDB Document Database
|
14
|
+
email: christoph.enzmann@oakten.ch
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/ravendb-manager.rb
|
20
|
+
homepage: https://github.com/oakten-software-engineering/ravendb-manager
|
21
|
+
licenses:
|
22
|
+
- GPL-3.0
|
23
|
+
metadata: {}
|
24
|
+
post_install_message:
|
25
|
+
rdoc_options: []
|
26
|
+
require_paths:
|
27
|
+
- lib
|
28
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
requirements: []
|
39
|
+
rubyforge_project:
|
40
|
+
rubygems_version: 2.6.7
|
41
|
+
signing_key:
|
42
|
+
specification_version: 4
|
43
|
+
summary: Manage your RavenDB Document Database
|
44
|
+
test_files: []
|