trocla 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|