trocla 0.3.0 → 0.5.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 +5 -5
- data/.github/workflows/ruby.yml +24 -0
- data/.rspec +1 -1
- data/CHANGELOG.md +23 -0
- data/Gemfile +10 -36
- data/README.md +64 -5
- data/bin/trocla +57 -40
- data/lib/VERSION +1 -1
- data/lib/trocla/default_config.yaml +3 -4
- data/lib/trocla/encryptions/none.rb +0 -1
- data/lib/trocla/encryptions/ssl.rb +11 -11
- data/lib/trocla/encryptions.rb +12 -5
- data/lib/trocla/formats/bcrypt.rb +2 -2
- data/lib/trocla/formats/md5crypt.rb +2 -2
- data/lib/trocla/formats/mysql.rb +2 -2
- data/lib/trocla/formats/pgsql.rb +50 -3
- data/lib/trocla/formats/plain.rb +1 -3
- data/lib/trocla/formats/sha1.rb +1 -1
- data/lib/trocla/formats/sha256crypt.rb +2 -2
- data/lib/trocla/formats/sha512crypt.rb +2 -2
- data/lib/trocla/formats/ssha.rb +3 -3
- data/lib/trocla/formats/sshkey.rb +42 -0
- data/lib/trocla/formats/wireguard.rb +45 -0
- data/lib/trocla/formats/x509.rb +56 -46
- data/lib/trocla/formats.rb +15 -5
- data/lib/trocla/store.rb +24 -13
- data/lib/trocla/stores/memory.rb +26 -10
- data/lib/trocla/stores/moneta.rb +53 -17
- data/lib/trocla/stores/vault.rb +78 -0
- data/lib/trocla/stores.rb +3 -2
- data/lib/trocla/util.rb +28 -17
- data/lib/trocla/version.rb +7 -4
- data/lib/trocla.rb +51 -41
- data/spec/spec_helper.rb +13 -1
- data/spec/trocla/formats/pgsql_spec.rb +25 -0
- data/spec/trocla/formats/sshkey_spec.rb +52 -0
- data/spec/trocla/formats/x509_spec.rb +8 -1
- data/spec/trocla_spec.rb +43 -0
- data/trocla.gemspec +29 -26
- metadata +58 -12
- data/.travis.yml +0 -11
data/lib/trocla/formats/ssha.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
require 'base64'
|
3
3
|
require 'digest'
|
4
4
|
class Trocla::Formats::Ssha < Trocla::Formats::Base
|
5
|
-
def format(plain_password,options={})
|
6
|
-
|
7
|
-
|
5
|
+
def format(plain_password, options = {})
|
6
|
+
salt = options['salt'] || Trocla::Util.salt(16)
|
7
|
+
'{SSHA}' + Base64.encode64("#{Digest::SHA1.digest("#{plain_password}#{salt}")}#{salt}").chomp
|
8
8
|
end
|
9
9
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'sshkey'
|
2
|
+
|
3
|
+
class Trocla::Formats::Sshkey < Trocla::Formats::Base
|
4
|
+
expensive true
|
5
|
+
|
6
|
+
def format(plain_password,options={})
|
7
|
+
if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY/m)
|
8
|
+
# Import, validate ssh key
|
9
|
+
begin
|
10
|
+
sshkey = ::SSHKey.new(plain_password)
|
11
|
+
rescue Exception => e
|
12
|
+
raise "SSH key import failed: #{e.message}"
|
13
|
+
end
|
14
|
+
return sshkey.private_key + sshkey.ssh_public_key
|
15
|
+
end
|
16
|
+
|
17
|
+
type = options['type'] || 'rsa'
|
18
|
+
bits = options['bits'] || 2048
|
19
|
+
|
20
|
+
begin
|
21
|
+
sshkey = ::SSHKey.generate(
|
22
|
+
type: type, bits: bits,
|
23
|
+
comment: options['comment'],
|
24
|
+
passphrase: options['passphrase']
|
25
|
+
)
|
26
|
+
rescue Exception => e
|
27
|
+
raise "SSH key creation failed: #{e.message}"
|
28
|
+
end
|
29
|
+
|
30
|
+
sshkey.private_key + sshkey.ssh_public_key
|
31
|
+
end
|
32
|
+
|
33
|
+
def render(output, render_options = {})
|
34
|
+
if render_options['privonly']
|
35
|
+
::SSHKey.new(output).private_key
|
36
|
+
elsif render_options['pubonly']
|
37
|
+
::SSHKey.new(output).ssh_public_key
|
38
|
+
else
|
39
|
+
super(output, render_options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Trocla::Formats::Wireguard < Trocla::Formats::Base
|
5
|
+
expensive true
|
6
|
+
|
7
|
+
def format(plain_password, options={})
|
8
|
+
return YAML.safe_load(plain_password) if plain_password.match(/---/)
|
9
|
+
|
10
|
+
wg_priv = nil
|
11
|
+
wg_pub = nil
|
12
|
+
begin
|
13
|
+
Open3.popen3('wg genkey') do |_stdin, stdout, _stderr, _waiter|
|
14
|
+
wg_priv = stdout.read.chomp
|
15
|
+
end
|
16
|
+
rescue SystemCallError => e
|
17
|
+
raise 'trocla wireguard: wg binary not found' if e.message =~ /No such file or directory/
|
18
|
+
|
19
|
+
raise "trocla wireguard: #{e.message}"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
Open3.popen3('wg pubkey') do |stdin, stdout, _stderr, _waiter|
|
24
|
+
stdin.write(wg_priv)
|
25
|
+
stdin.close
|
26
|
+
|
27
|
+
wg_pub = stdout.read.chomp
|
28
|
+
end
|
29
|
+
rescue SystemCallError => e
|
30
|
+
raise "trocla wireguard: #{e.message}"
|
31
|
+
end
|
32
|
+
YAML.dump({ 'wg_priv' => wg_priv, 'wg_pub' => wg_pub })
|
33
|
+
end
|
34
|
+
|
35
|
+
def render(output, render_options = {})
|
36
|
+
data = YAML.safe_load(output)
|
37
|
+
if render_options['privonly']
|
38
|
+
data['wg_priv']
|
39
|
+
elsif render_options['pubonly']
|
40
|
+
data['wg_pub']
|
41
|
+
else
|
42
|
+
'pub: ' + data['wg_pub'] + "\npriv: " + data['wg_priv']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/trocla/formats/x509.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'openssl'
|
2
|
+
|
3
|
+
# Trocla::Formats::X509
|
2
4
|
class Trocla::Formats::X509 < Trocla::Formats::Base
|
3
5
|
expensive true
|
4
6
|
def format(plain_password,options={})
|
5
|
-
|
6
7
|
if plain_password.match(/-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----.*-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----/m)
|
7
8
|
# just an import, don't generate any new keys
|
8
9
|
return plain_password
|
@@ -11,17 +12,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
11
12
|
cn = nil
|
12
13
|
if options['subject']
|
13
14
|
subject = options['subject']
|
14
|
-
if cna = OpenSSL::X509::Name.parse(subject).to_a.find{|e| e[0] == 'CN' }
|
15
|
+
if cna = OpenSSL::X509::Name.parse(subject).to_a.find { |e| e[0] == 'CN' }
|
15
16
|
cn = cna[1]
|
16
17
|
end
|
17
18
|
elsif options['CN']
|
18
19
|
subject = ''
|
19
20
|
cn = options['CN']
|
20
|
-
['C','ST','L','O','OU','CN','emailAddress'].each do |field|
|
21
|
+
['C', 'ST', 'L', 'O', 'OU', 'CN', 'emailAddress'].each do |field|
|
21
22
|
subject << "/#{field}=#{options[field]}" if options[field]
|
22
23
|
end
|
23
24
|
else
|
24
|
-
raise
|
25
|
+
raise 'You need to pass "subject" or "CN" as an option to use this format'
|
25
26
|
end
|
26
27
|
hash = options['hash'] || 'sha2'
|
27
28
|
sign_with = options['ca']
|
@@ -33,17 +34,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
33
34
|
key_usages = Array(key_usages) if key_usages
|
34
35
|
|
35
36
|
altnames = if become_ca || (an = options['altnames']) && Array(an).empty?
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
37
|
+
[]
|
38
|
+
else
|
39
|
+
# ensure that we have the CN with us, but only if it
|
40
|
+
# it's like a hostname.
|
41
|
+
# This might have to be improved.
|
42
|
+
if cn.include?(' ')
|
43
|
+
Array(an).collect { |v| "DNS:#{v}" }.join(', ')
|
44
|
+
else
|
45
|
+
(["DNS:#{cn}"] + Array(an).collect { |v| "DNS:#{v}" }).uniq.join(', ')
|
46
|
+
end
|
47
|
+
end
|
47
48
|
|
48
49
|
begin
|
49
50
|
key = mkkey(keysize)
|
@@ -55,7 +56,7 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
55
56
|
cert = nil
|
56
57
|
if sign_with # certificate signed with CA
|
57
58
|
begin
|
58
|
-
ca_str = trocla.get_password(sign_with,'x509')
|
59
|
+
ca_str = trocla.get_password(sign_with, 'x509')
|
59
60
|
ca = OpenSSL::X509::Certificate.new(ca_str)
|
60
61
|
cakey = OpenSSL::PKey::RSA.new(ca_str)
|
61
62
|
caserial = getserial(sign_with)
|
@@ -72,8 +73,10 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
72
73
|
end
|
73
74
|
|
74
75
|
begin
|
75
|
-
cert = mkcert(
|
76
|
-
|
76
|
+
cert = mkcert(
|
77
|
+
caserial, request.subject, ca, request.public_key, days,
|
78
|
+
altnames, key_usages, name_constraints, become_ca
|
79
|
+
)
|
77
80
|
cert.sign(cakey, signature(hash))
|
78
81
|
addserial(sign_with, caserial)
|
79
82
|
rescue Exception => e
|
@@ -82,8 +85,10 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
82
85
|
else # self-signed certificate
|
83
86
|
begin
|
84
87
|
subj = OpenSSL::X509::Name.parse(subject)
|
85
|
-
cert = mkcert(
|
86
|
-
|
88
|
+
cert = mkcert(
|
89
|
+
getserial(subj), subj, nil, key.public_key, days,
|
90
|
+
altnames, key_usages, name_constraints, become_ca
|
91
|
+
)
|
87
92
|
cert.sign(key, signature(hash))
|
88
93
|
rescue Exception => e
|
89
94
|
raise "Self-signed certificate #{subject} creation failed: #{e.message}"
|
@@ -92,32 +97,34 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
92
97
|
key.to_pem + cert.to_pem
|
93
98
|
end
|
94
99
|
|
95
|
-
def render(output,render_options={})
|
100
|
+
def render(output, render_options = {})
|
96
101
|
if render_options['keyonly']
|
97
102
|
OpenSSL::PKey::RSA.new(output).to_pem
|
98
103
|
elsif render_options['certonly']
|
99
104
|
OpenSSL::X509::Certificate.new(output).to_pem
|
105
|
+
elsif render_options['publickeyonly']
|
106
|
+
OpenSSL::PKey::RSA.new(output).public_key.to_pem
|
100
107
|
else
|
101
|
-
super(output,render_options)
|
108
|
+
super(output, render_options)
|
102
109
|
end
|
103
110
|
end
|
104
111
|
|
105
112
|
private
|
106
|
-
# nice help: https://gist.github.com/mitfik/1922961
|
107
113
|
|
114
|
+
# nice help: https://gist.github.com/mitfik/1922961
|
108
115
|
def signature(hash = 'sha2')
|
109
116
|
if hash == 'sha1'
|
110
|
-
|
117
|
+
OpenSSL::Digest::SHA1.new
|
111
118
|
elsif hash == 'sha224'
|
112
|
-
|
119
|
+
OpenSSL::Digest::SHA224.new
|
113
120
|
elsif hash == 'sha2' || hash == 'sha256'
|
114
|
-
|
121
|
+
OpenSSL::Digest::SHA256.new
|
115
122
|
elsif hash == 'sha384'
|
116
|
-
|
123
|
+
OpenSSL::Digest::SHA384.new
|
117
124
|
elsif hash == 'sha512'
|
118
|
-
|
125
|
+
OpenSSL::Digest::SHA512.new
|
119
126
|
else
|
120
|
-
|
127
|
+
raise "Unrecognized hash: #{hash}"
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
@@ -125,7 +132,7 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
125
132
|
OpenSSL::PKey::RSA.generate(len)
|
126
133
|
end
|
127
134
|
|
128
|
-
def mkreq(subject,public_key)
|
135
|
+
def mkreq(subject, public_key)
|
129
136
|
request = OpenSSL::X509::Request.new
|
130
137
|
request.subject = subject
|
131
138
|
request.public_key = public_key
|
@@ -133,9 +140,9 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
133
140
|
request
|
134
141
|
end
|
135
142
|
|
136
|
-
def mkcert(serial,subject,issuer,public_key,days,altnames, key_usages = nil, name_constraints = [], become_ca = false)
|
143
|
+
def mkcert(serial, subject, issuer, public_key, days, altnames, key_usages = nil, name_constraints = [], become_ca = false)
|
137
144
|
cert = OpenSSL::X509::Certificate.new
|
138
|
-
issuer = cert if issuer
|
145
|
+
issuer = cert if issuer.nil?
|
139
146
|
cert.subject = subject
|
140
147
|
cert.issuer = issuer.subject
|
141
148
|
cert.not_before = Time.now
|
@@ -147,36 +154,36 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
147
154
|
ef = OpenSSL::X509::ExtensionFactory.new
|
148
155
|
ef.subject_certificate = cert
|
149
156
|
ef.issuer_certificate = issuer
|
150
|
-
cert.extensions = [
|
157
|
+
cert.extensions = [ef.create_extension('subjectKeyIdentifier', 'hash')]
|
151
158
|
|
152
159
|
if become_ca
|
153
|
-
cert.add_extension ef.create_extension(
|
160
|
+
cert.add_extension ef.create_extension('basicConstraints', 'CA:TRUE', true)
|
154
161
|
unless (ku = key_usages || ca_key_usages).empty?
|
155
|
-
cert.add_extension ef.create_extension(
|
162
|
+
cert.add_extension ef.create_extension('keyUsage', ku.join(', '), true)
|
156
163
|
end
|
157
164
|
if name_constraints && !name_constraints.empty?
|
158
|
-
cert.add_extension ef.create_extension(
|
165
|
+
cert.add_extension ef.create_extension('nameConstraints', "permitted;DNS:#{name_constraints.join(',permitted;DNS:')}", true)
|
159
166
|
end
|
160
167
|
else
|
161
|
-
cert.add_extension ef.create_extension(
|
162
|
-
cert.add_extension ef.create_extension(
|
168
|
+
cert.add_extension ef.create_extension('subjectAltName', altnames, true) unless altnames.empty?
|
169
|
+
cert.add_extension ef.create_extension('basicConstraints', 'CA:FALSE', true)
|
163
170
|
unless (ku = key_usages || cert_key_usages).empty?
|
164
|
-
cert.add_extension ef.create_extension(
|
171
|
+
cert.add_extension ef.create_extension('keyUsage', ku.join(', '), true)
|
165
172
|
end
|
166
173
|
end
|
167
|
-
cert.add_extension ef.create_extension(
|
174
|
+
cert.add_extension ef.create_extension('authorityKeyIdentifier', 'keyid:always,issuer:always')
|
168
175
|
|
169
176
|
cert
|
170
177
|
end
|
171
178
|
|
172
179
|
def getserial(ca)
|
173
|
-
newser = Trocla::Util.random_str(20,'hexadecimal').to_i(16)
|
180
|
+
newser = Trocla::Util.random_str(20, 'hexadecimal').to_i(16)
|
174
181
|
all_serials(ca).include?(newser) ? getserial(ca) : newser
|
175
182
|
end
|
176
183
|
|
177
184
|
def all_serials(ca)
|
178
|
-
if allser = trocla.get_password("#{ca}_all_serials",'plain')
|
179
|
-
YAML.
|
185
|
+
if allser = trocla.get_password("#{ca}_all_serials", 'plain')
|
186
|
+
YAML.safe_load(allser)
|
180
187
|
else
|
181
188
|
[]
|
182
189
|
end
|
@@ -184,14 +191,17 @@ class Trocla::Formats::X509 < Trocla::Formats::Base
|
|
184
191
|
|
185
192
|
def addserial(ca,serial)
|
186
193
|
serials = all_serials(ca) << serial
|
187
|
-
trocla.set_password("#{ca}_all_serials",'plain',YAML.dump(serials))
|
194
|
+
trocla.set_password("#{ca}_all_serials", 'plain', YAML.dump(serials))
|
188
195
|
end
|
189
196
|
|
190
197
|
def cert_key_usages
|
191
198
|
['nonRepudiation', 'digitalSignature', 'keyEncipherment']
|
192
199
|
end
|
200
|
+
|
193
201
|
def ca_key_usages
|
194
|
-
[
|
195
|
-
'
|
202
|
+
[
|
203
|
+
'keyCertSign', 'cRLSign', 'nonRepudiation',
|
204
|
+
'digitalSignature', 'keyEncipherment'
|
205
|
+
]
|
196
206
|
end
|
197
207
|
end
|
data/lib/trocla/formats.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Trocla::Formats
|
4
|
+
class Trocla::Formats
|
5
|
+
# Base
|
3
6
|
class Base
|
4
7
|
attr_reader :trocla
|
8
|
+
|
5
9
|
def initialize(trocla)
|
6
10
|
@trocla = trocla
|
7
11
|
end
|
8
|
-
|
12
|
+
|
13
|
+
def render(output, render_options = {})
|
9
14
|
output
|
10
15
|
end
|
16
|
+
|
11
17
|
def expensive?
|
12
18
|
self.class.expensive?
|
13
19
|
end
|
@@ -15,6 +21,7 @@ class Trocla::Formats
|
|
15
21
|
def expensive(is_expensive)
|
16
22
|
@expensive = is_expensive
|
17
23
|
end
|
24
|
+
|
18
25
|
def expensive?
|
19
26
|
@expensive == true
|
20
27
|
end
|
@@ -27,7 +34,9 @@ class Trocla::Formats
|
|
27
34
|
end
|
28
35
|
|
29
36
|
def all
|
30
|
-
Dir[File.expand_path(
|
37
|
+
Dir[File.expand_path(
|
38
|
+
File.join(File.dirname(__FILE__), 'formats', '*.rb')
|
39
|
+
)].collect { |f| File.basename(f, '.rb').downcase }
|
31
40
|
end
|
32
41
|
|
33
42
|
def available?(format)
|
@@ -35,10 +44,11 @@ class Trocla::Formats
|
|
35
44
|
end
|
36
45
|
|
37
46
|
private
|
47
|
+
|
38
48
|
def formats
|
39
49
|
@@formats ||= Hash.new do |hash, format|
|
40
50
|
format = format.downcase
|
41
|
-
if File.
|
51
|
+
if File.exist?(path(format))
|
42
52
|
require "trocla/formats/#{format}"
|
43
53
|
hash[format] = (eval "Trocla::Formats::#{format.capitalize}")
|
44
54
|
else
|
@@ -48,7 +58,7 @@ class Trocla::Formats
|
|
48
58
|
end
|
49
59
|
|
50
60
|
def path(format)
|
51
|
-
File.expand_path(File.join(File.dirname(__FILE__),'formats',"#{format}.rb"))
|
61
|
+
File.expand_path(File.join(File.dirname(__FILE__), 'formats', "#{format}.rb"))
|
52
62
|
end
|
53
63
|
end
|
54
64
|
end
|
data/lib/trocla/store.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# implements the default store behavior
|
2
2
|
class Trocla::Store
|
3
3
|
attr_reader :store_config, :trocla
|
4
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
5
6
|
@store_config = config
|
6
7
|
@trocla = trocla
|
7
8
|
end
|
@@ -9,14 +10,13 @@ class Trocla::Store
|
|
9
10
|
# closes the store
|
10
11
|
# when called do whatever "closes" your
|
11
12
|
# store, e.g. close database connections.
|
12
|
-
def close
|
13
|
-
end
|
13
|
+
def close; end
|
14
14
|
|
15
15
|
# should return value for key & format
|
16
16
|
# returns nil if nothing or a nil value
|
17
17
|
# was found.
|
18
18
|
# If a key is expired it must return nil.
|
19
|
-
def get(key,format)
|
19
|
+
def get(key, format)
|
20
20
|
raise 'not implemented'
|
21
21
|
end
|
22
22
|
|
@@ -33,11 +33,11 @@ class Trocla::Store
|
|
33
33
|
# amount of seconds a key can live with.
|
34
34
|
# This mechanism is expected to be
|
35
35
|
# be implemented by the backend.
|
36
|
-
def set(key,format,value,options={})
|
36
|
+
def set(key, format, value, options = {})
|
37
37
|
if format == 'plain'
|
38
|
-
set_plain(key,value,options)
|
38
|
+
set_plain(key, value, options)
|
39
39
|
else
|
40
|
-
set_format(key,format,value,options)
|
40
|
+
set_format(key, format, value, options)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -46,20 +46,31 @@ class Trocla::Store
|
|
46
46
|
# returns value of format or hash of
|
47
47
|
# format => value # if everything is
|
48
48
|
# deleted.
|
49
|
-
def delete(key,format=nil)
|
50
|
-
format.nil? ? (delete_all(key)||{}) : delete_format(key,format)
|
49
|
+
def delete(key, format = nil)
|
50
|
+
format.nil? ? (delete_all(key) || {}) : delete_format(key, format)
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns all formats for a key
|
54
|
+
def formats(_)
|
55
|
+
raise 'not implemented'
|
56
|
+
end
|
57
|
+
|
58
|
+
# def searches for a key
|
59
|
+
def search(_)
|
60
|
+
raise 'not implemented'
|
51
61
|
end
|
52
62
|
|
53
63
|
private
|
64
|
+
|
54
65
|
# sets a new plain value
|
55
66
|
# *must* invalidate all
|
56
67
|
# other formats
|
57
|
-
def set_plain(key,value,options)
|
68
|
+
def set_plain(key, value, options)
|
58
69
|
raise 'not implemented'
|
59
70
|
end
|
60
71
|
|
61
72
|
# sets a value of a format
|
62
|
-
def set_format(key,format,value,options)
|
73
|
+
def set_format(key, format, value, options)
|
63
74
|
raise 'not implemented'
|
64
75
|
end
|
65
76
|
|
@@ -67,14 +78,14 @@ class Trocla::Store
|
|
67
78
|
# and returns a hash with all
|
68
79
|
# formats and values
|
69
80
|
# or nil if nothing is found
|
70
|
-
def delete_all(
|
81
|
+
def delete_all(_)
|
71
82
|
raise 'not implemented'
|
72
83
|
end
|
73
84
|
|
74
85
|
# deletes the value of the passed
|
75
86
|
# key & format and returns the
|
76
87
|
# value.
|
77
|
-
def delete_format(key,format)
|
88
|
+
def delete_format(key, format)
|
78
89
|
raise 'not implemented'
|
79
90
|
end
|
80
91
|
end
|
data/lib/trocla/stores/memory.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# a simple in memory store just as an example
|
2
2
|
class Trocla::Stores::Memory < Trocla::Store
|
3
3
|
attr_reader :memory
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
6
7
|
@memory = Hash.new({})
|
7
8
|
end
|
8
9
|
|
9
|
-
def get(key,format)
|
10
|
+
def get(key, format)
|
10
11
|
unless expired?(key)
|
11
12
|
memory[key][format]
|
12
13
|
else
|
@@ -14,31 +15,45 @@ class Trocla::Stores::Memory < Trocla::Store
|
|
14
15
|
nil
|
15
16
|
end
|
16
17
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
def set(key, format, value, options = {})
|
20
|
+
super(key, format, value, options)
|
21
|
+
set_expires(key, options['expires'])
|
22
|
+
end
|
23
|
+
|
24
|
+
def formats(key)
|
25
|
+
memory[key].empty? ? nil : memory[key].keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def search(key)
|
29
|
+
r = memory.keys.grep(/#{key}/)
|
30
|
+
r.empty? ? nil : r
|
20
31
|
end
|
21
32
|
|
22
33
|
private
|
23
|
-
|
34
|
+
|
35
|
+
def set_plain(key, value, _)
|
24
36
|
memory[key] = { 'plain' => value }
|
25
37
|
end
|
26
38
|
|
27
|
-
def set_format(key,format,value,
|
39
|
+
def set_format(key, format, value, _)
|
28
40
|
memory[key].merge!({ format => value })
|
29
41
|
end
|
30
42
|
|
31
43
|
def delete_all(key)
|
32
44
|
memory.delete(key)
|
33
45
|
end
|
34
|
-
|
46
|
+
|
47
|
+
def delete_format(key, format)
|
35
48
|
old_val = (h = memory[key]).delete(format)
|
36
49
|
h.empty? ? memory.delete(key) : memory[key] = h
|
37
50
|
set_expires(key,nil)
|
38
51
|
old_val
|
39
52
|
end
|
53
|
+
|
40
54
|
private
|
41
|
-
|
55
|
+
|
56
|
+
def set_expires(key, expires)
|
42
57
|
expires = memory[key]['_expires'] if expires.nil?
|
43
58
|
if expires && expires > 0
|
44
59
|
memory[key]['_expires'] = expires
|
@@ -48,6 +63,7 @@ class Trocla::Stores::Memory < Trocla::Store
|
|
48
63
|
memory[key].delete('_expires_at')
|
49
64
|
end
|
50
65
|
end
|
66
|
+
|
51
67
|
def expired?(key)
|
52
68
|
memory.key?(key) &&
|
53
69
|
(a = memory[key]['_expires_at']).is_a?(Time) && \
|
data/lib/trocla/stores/moneta.rb
CHANGED
@@ -1,27 +1,42 @@
|
|
1
1
|
# the default moneta based store
|
2
2
|
class Trocla::Stores::Moneta < Trocla::Store
|
3
3
|
attr_reader :moneta
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
def initialize(config, trocla)
|
6
|
+
super(config, trocla)
|
6
7
|
require 'moneta'
|
7
8
|
# load expire support by default
|
8
|
-
adapter_options = {
|
9
|
-
|
10
|
-
|
9
|
+
adapter_options = {
|
10
|
+
:expires => true
|
11
|
+
}.merge(store_config['adapter_options'] || {})
|
12
|
+
@moneta = Moneta.new(store_config['adapter'], adapter_options)
|
11
13
|
end
|
12
14
|
|
13
15
|
def close
|
14
16
|
moneta.close
|
15
17
|
end
|
16
18
|
|
17
|
-
def get(key,format)
|
19
|
+
def get(key, format)
|
18
20
|
moneta.fetch(key, {})[format]
|
19
21
|
end
|
20
22
|
|
23
|
+
def formats(key)
|
24
|
+
r = moneta.fetch(key)
|
25
|
+
r.nil? ? nil : r.keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def search(key)
|
29
|
+
raise 'The search option is not available for any adapter other than Sequel or YAML' unless store_config['adapter'] == :Sequel || store_config['adapter'] == :YAML
|
30
|
+
|
31
|
+
r = search_keys(key)
|
32
|
+
r.empty? ? nil : r
|
33
|
+
end
|
34
|
+
|
21
35
|
private
|
22
|
-
|
36
|
+
|
37
|
+
def set_plain(key, value, options)
|
23
38
|
h = { 'plain' => value }
|
24
|
-
mo = moneta_options(key,options)
|
39
|
+
mo = moneta_options(key, options)
|
25
40
|
if options['expires'] && options['expires'] > 0
|
26
41
|
h['_expires'] = options['expires']
|
27
42
|
else
|
@@ -29,24 +44,28 @@ class Trocla::Stores::Moneta < Trocla::Store
|
|
29
44
|
# expires if nothing is set.
|
30
45
|
mo[:expires] = false
|
31
46
|
end
|
32
|
-
moneta.store(key,h,mo)
|
47
|
+
moneta.store(key, h, mo)
|
33
48
|
end
|
34
49
|
|
35
|
-
def set_format(key,format,value,options)
|
36
|
-
moneta.store(
|
37
|
-
|
38
|
-
|
50
|
+
def set_format(key, format, value, options)
|
51
|
+
moneta.store(
|
52
|
+
key,
|
53
|
+
moneta.fetch(key, {}).merge({ format => value }),
|
54
|
+
moneta_options(key, options)
|
55
|
+
)
|
39
56
|
end
|
40
57
|
|
41
58
|
def delete_all(key)
|
42
59
|
moneta.delete(key)
|
43
60
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
61
|
+
|
62
|
+
def delete_format(key, format)
|
63
|
+
old_val = (h = moneta.fetch(key, {})).delete(format)
|
64
|
+
h.empty? ? moneta.delete(key) : moneta.store(key, h, moneta_options(key, {}))
|
47
65
|
old_val
|
48
66
|
end
|
49
|
-
|
67
|
+
|
68
|
+
def moneta_options(key, options)
|
50
69
|
res = {}
|
51
70
|
if options.key?('expires')
|
52
71
|
res[:expires] = options['expires']
|
@@ -55,4 +74,21 @@ class Trocla::Stores::Moneta < Trocla::Store
|
|
55
74
|
end
|
56
75
|
res
|
57
76
|
end
|
77
|
+
|
78
|
+
def search_keys(key)
|
79
|
+
_moneta = Moneta.new(store_config['adapter'], (store_config['adapter_options'] || {}).merge({ :expires => false }))
|
80
|
+
a = []
|
81
|
+
|
82
|
+
if store_config['adapter'] == :Sequel
|
83
|
+
keys = _moneta.adapter.backend[:trocla].select_order_map { from_base64(:k) }
|
84
|
+
elsif store_config['adapter'] == :YAML
|
85
|
+
keys = _moneta.adapter.backend.transaction(true) { _moneta.adapter.backend.roots }
|
86
|
+
end
|
87
|
+
_moneta.close
|
88
|
+
regexp = Regexp.new("#{key}")
|
89
|
+
keys.each do |k|
|
90
|
+
a << k if regexp.match(k)
|
91
|
+
end
|
92
|
+
a
|
93
|
+
end
|
58
94
|
end
|