keyp 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -6
- data/bin/keyp +91 -20
- data/lib/keyp.rb +44 -19
- data/lib/keyp/bag.rb +168 -71
- data/lib/keyp/version.rb +1 -1
- data/spec/bag_spec.rb +144 -18
- data/spec/keyp_spec.rb +90 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2951607e2465cc3204c7f01d993802e75be59acb
|
4
|
+
data.tar.gz: efaa651f76f6d65eefde6316111bd62640790c43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d69defc81b8351eeeb6171738687dd2577c910e145ec67808ff56c72a6052c399d8be9bb399a38b857912d41d7de4d9574b50ea876206157ee383e8c122a8174
|
7
|
+
data.tar.gz: c4d11396115a4ebe2b0a1ed49f24cc2e1e72dbbada753aa0796d1fdaa301d5bfccd822f5d43168578b8dfca0f52249bf4d94e8d44b7e33442d7153f33c35e3f4
|
data/README.md
CHANGED
@@ -12,12 +12,14 @@ Web applications generally need sensitive information, like salts and keys. Mana
|
|
12
12
|
|
13
13
|
## Important
|
14
14
|
|
15
|
-
Keyp is still in early development
|
15
|
+
Keyp is still in early development. As such it is very much a work in progress. Not all features
|
16
16
|
expressed in the documentation may be working. The documentation may be plain wrong. I don't recommend you use Keyp
|
17
17
|
for any kind of production code at this point in this gem's lifecycle.
|
18
18
|
|
19
19
|
## Quick Tour
|
20
20
|
|
21
|
+
To install, see "Installation" below
|
22
|
+
|
21
23
|
Keyp manages key value pairs in collections. As of version 0.0.4, Keyp refers to collections as *bags*. There is a
|
22
24
|
default bag called *default*. Unless you specify a bag name, *default* will be used.
|
23
25
|
|
@@ -51,7 +53,8 @@ Keyp is not limited to just access keys. Any kind of string based name/value pai
|
|
51
53
|
|
52
54
|
TODO: Improve description
|
53
55
|
|
54
|
-
_CAVEAT:_ This gem is at
|
56
|
+
_CAVEAT:_ This gem is at an early stage in development. Functionality is
|
57
|
+
subject to change depending on need and clarity.
|
55
58
|
|
56
59
|
## Installation
|
57
60
|
|
@@ -93,15 +96,14 @@ TODO: Write more detailed documentation, For now, see the quick start above and
|
|
93
96
|
$ keyp --help
|
94
97
|
|
95
98
|
## Release Notes
|
99
|
+
* v 0.0.7 - Added loading a bag from the ENV. Added renaming bags Fixed error with keys of less than three characters.
|
100
|
+
* v 0.0.6 - Added copying key:values to the Ruby ENV in the library (still have to implement in the CLI)
|
96
101
|
* v 0.0.5 - Fixed iso8601 error
|
97
102
|
* v 0.0.4 - Fixed missing file error
|
98
103
|
|
99
104
|
## Development plan/Features to implement
|
100
105
|
|
101
|
-
*
|
102
|
-
* Implement .keyp directory setup
|
103
|
-
* Add ENV vars to bag
|
104
|
-
* Add bag vars to ENV
|
106
|
+
* Fill out rspec tests
|
105
107
|
* Incorporate with shell configuration
|
106
108
|
* Write very clear documentation
|
107
109
|
* Implement import/Export to YAML and/or JSON
|
data/bin/keyp
CHANGED
@@ -6,16 +6,18 @@
|
|
6
6
|
# implementations. A good number and a few well known use Thor, so seems like a good idea to
|
7
7
|
# figure out how to use Thor as well.
|
8
8
|
#
|
9
|
-
|
10
|
-
# Then I'll pick a single approach after exploring them
|
9
|
+
|
11
10
|
|
12
11
|
# TO run in the project dir:
|
13
12
|
# ruby -Ilib ./bin/keyp
|
13
|
+
# or
|
14
|
+
# bundle exec bin/keyp
|
14
15
|
|
15
16
|
# resolve bin path, ignoring symlinks
|
16
17
|
require 'pathname'
|
17
18
|
bin_file = Pathname.new(__FILE__).realpath
|
18
19
|
|
20
|
+
# The Keyp CLI uses GLI
|
19
21
|
# http://davetron5000.github.io/gli/
|
20
22
|
require 'gli'
|
21
23
|
|
@@ -24,13 +26,6 @@ require 'pp'
|
|
24
26
|
require 'keyp'
|
25
27
|
include GLI::App
|
26
28
|
|
27
|
-
# keyp set key=value
|
28
|
-
# keyp get key
|
29
|
-
# keyp
|
30
|
-
|
31
|
-
|
32
|
-
# keyp create bag
|
33
|
-
|
34
29
|
program_desc 'A command line interface for the Keyp key:value manager'
|
35
30
|
|
36
31
|
desc 'The Keyp bag to use'
|
@@ -42,6 +37,11 @@ switch [:d, :debug]
|
|
42
37
|
desc 'Suppress hints: If invalid parameters are set, no suggestive feedback will be provided'
|
43
38
|
switch [:s, :suppress]
|
44
39
|
|
40
|
+
|
41
|
+
|
42
|
+
version Keyp::VERSION
|
43
|
+
|
44
|
+
|
45
45
|
# TODO: fix this because this implies that we either already have a bag or it will create a new one
|
46
46
|
pre do |global_options, command, options, args|
|
47
47
|
# initialize our store
|
@@ -57,8 +57,7 @@ pre do |global_options, command, options, args|
|
|
57
57
|
puts "---- end prehook ---"
|
58
58
|
end
|
59
59
|
|
60
|
-
unless command.name
|
61
|
-
|
60
|
+
unless [:setup, :show, :delete].include? command.name
|
62
61
|
# check if we are configured
|
63
62
|
unless Keyp.configured?
|
64
63
|
puts "Please run setup to use Keyp. Run with --help to view help menu"
|
@@ -125,7 +124,7 @@ command :set do |c|
|
|
125
124
|
nvp = Keyp.parse_arg_string buff
|
126
125
|
|
127
126
|
if nvp
|
128
|
-
$bag[nvp[:key]] = nvp[:value]
|
127
|
+
$bag[nvp[:key]] = nvp[:value].strip
|
129
128
|
elsif global_options[:suppress] == false
|
130
129
|
# puts "Usage keyp set KEY1=VALUE1 [KEY2=VALUE2 ...]"
|
131
130
|
puts "Usage: keyp set KEY=VALUE"
|
@@ -190,8 +189,11 @@ command :setup do |c|
|
|
190
189
|
end
|
191
190
|
end
|
192
191
|
|
193
|
-
|
192
|
+
create_desc = 'Creates a new bag'
|
194
193
|
|
194
|
+
desc create_desc
|
195
|
+
command :create do |c|
|
196
|
+
c.desc create_desc
|
195
197
|
c.action do |global_options, options, args|
|
196
198
|
puts "keyp create"
|
197
199
|
# TODO: add checking in case store already exists
|
@@ -206,14 +208,56 @@ command :create do |c|
|
|
206
208
|
end
|
207
209
|
end
|
208
210
|
|
211
|
+
delete_desc = 'Deletes an existing bag or set of bags matching the delete parameter'
|
212
|
+
desc delete_desc
|
209
213
|
command :delete do |c|
|
214
|
+
c.desc 'forces delete (no prompting)'
|
215
|
+
c.switch [:f, :force]
|
216
|
+
c.desc "outputs the name of each file as it's deleted"
|
217
|
+
c.switch [:v, :verbose]
|
218
|
+
c.desc "Confirm deletion of each file. Currently not enabled"
|
219
|
+
c.switch [:c, :confirm]
|
220
|
+
c.desc delete_desc
|
210
221
|
c.action do |global_options, options, args|
|
211
|
-
|
212
|
-
# TODO:
|
222
|
+
# TODO: add * for all bags
|
223
|
+
# TODO: enable prompting per file. Confirm skips the global confirm
|
224
|
+
|
225
|
+
help_now!('filename pattern is required') if args.empty?
|
226
|
+
|
227
|
+
if options[:force]
|
228
|
+
do_delete = true
|
229
|
+
else
|
230
|
+
puts "This will delete all the bags matching \"#{args[0]}\"."
|
231
|
+
print "Are you sure? (yes to confirm. Anything else to cancel) "
|
232
|
+
do_delete = STDIN.gets.chomp.upcase == 'YES'
|
233
|
+
end
|
234
|
+
|
235
|
+
if do_delete
|
236
|
+
# experimenting with different techniques
|
237
|
+
delete_type = 1
|
238
|
+
case delete_type
|
239
|
+
when 1
|
240
|
+
files = Dir["#{File.join(Keyp.home,args[0]+Keyp.ext)}"]
|
241
|
+
puts "going to delete files #{files}" if $debug
|
242
|
+
files.each do |f|
|
243
|
+
File.delete(f)
|
244
|
+
puts "deleted #{File.basename(f, Keyp.ext)}" if options[:verbose]
|
245
|
+
end
|
246
|
+
when 2
|
247
|
+
result = File.delete(args[0])
|
248
|
+
puts "delete results: #{result}"
|
249
|
+
#TODO: provide feedback
|
250
|
+
end
|
251
|
+
else
|
252
|
+
puts "delete bag(s) canceled."
|
253
|
+
end
|
213
254
|
end
|
214
255
|
end
|
215
256
|
|
257
|
+
show_desc = 'Lists all the bags in Keyp'
|
258
|
+
desc show_desc
|
216
259
|
command :show do |c|
|
260
|
+
c.desc show_desc
|
217
261
|
c.action do |global_options, options, args|
|
218
262
|
# TODO: implement filtering, stats (like number of items in a bag, last updated, etc)
|
219
263
|
# and just filename
|
@@ -224,13 +268,40 @@ command :show do |c|
|
|
224
268
|
end
|
225
269
|
end
|
226
270
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
271
|
+
rename_desc = 'Rename the given bag'
|
272
|
+
desc rename_desc
|
273
|
+
command :rename do |c|
|
274
|
+
#c.desc 'from bag name)'
|
275
|
+
#c.flag [:f, :from]
|
276
|
+
#c.desc 'to bag name'
|
277
|
+
#c.flag [:t, :to]
|
278
|
+
c.desc rename_desc
|
279
|
+
c.action do |global_options, options, args|
|
280
|
+
# get from_name and to_name
|
281
|
+
if args.count < 2
|
282
|
+
puts "TODO: rename help"
|
283
|
+
else
|
284
|
+
begin
|
285
|
+
res = Keyp::rename_bag(from: args[0], to: args[1])
|
286
|
+
puts "Renamed bag #{args[0]} to #{args[1]}"
|
287
|
+
rescue
|
288
|
+
puts "Unable to rename bag #{args[0]} to #{args[1]}"
|
289
|
+
# TODO: add reason
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
231
293
|
end
|
294
|
+
end
|
295
|
+
|
232
296
|
|
233
|
-
|
297
|
+
post do |global_options, command, options, args|
|
298
|
+
|
299
|
+
unless [:setup, :show, :delete].include? command.name
|
300
|
+
if $debug
|
301
|
+
puts "post-hook, state of bag.dirty = #{$bag.dirty}"
|
302
|
+
end
|
303
|
+
# if bag.data is dirty, then save
|
304
|
+
# TODO: check bag.save. if it checks dirty, then we don't need to check here
|
234
305
|
$bag.save
|
235
306
|
else
|
236
307
|
true
|
data/lib/keyp.rb
CHANGED
@@ -43,6 +43,8 @@ module Keyp
|
|
43
43
|
# Default home directory that keyp uses to store bags
|
44
44
|
DEFAULT_KEYP_DIR = '.keyp'
|
45
45
|
|
46
|
+
# number of digits for the fractional seconds in decimal
|
47
|
+
TIMESTAMP_FS_DIGITS = 6
|
46
48
|
#TODO: set this
|
47
49
|
#ENV_VAR_NAME_REGEX =
|
48
50
|
|
@@ -64,7 +66,16 @@ module Keyp
|
|
64
66
|
# it is recommended you restrict permissions
|
65
67
|
#
|
66
68
|
def self.home
|
67
|
-
|
69
|
+
#puts "Keyp::home, KEYP_HOME=#{ENV['KEYP_HOME']}"
|
70
|
+
ENV['KEYP_HOME'] || File.join(ENV['HOME'], DEFAULT_KEYP_DIR)
|
71
|
+
=begin
|
72
|
+
puts "home_dir=#{home_dir}"
|
73
|
+
if ENV['KEYP_HOME']
|
74
|
+
ENV['KEYP_HOME']
|
75
|
+
else
|
76
|
+
File.join(ENV['HOME'], DEFAULT_KEYP_DIR)
|
77
|
+
end
|
78
|
+
=end
|
68
79
|
end
|
69
80
|
|
70
81
|
##
|
@@ -94,15 +105,13 @@ module Keyp
|
|
94
105
|
# check if keyp directory exists
|
95
106
|
|
96
107
|
unless Dir.exist?(home)
|
97
|
-
puts "making directory #{home}"
|
98
108
|
Dir.mkdir(home, 0700)
|
99
109
|
if Dir.exist?(home)
|
100
110
|
home
|
101
111
|
else
|
102
|
-
|
112
|
+
raise "unable to create Keyp directory #{home}"
|
103
113
|
end
|
104
114
|
else
|
105
|
-
Puts "#{home} already exists"
|
106
115
|
nil
|
107
116
|
end
|
108
117
|
end
|
@@ -159,25 +168,14 @@ module Keyp
|
|
159
168
|
##
|
160
169
|
# Creates a new bag if one does not already exist with the given name
|
161
170
|
#
|
171
|
+
# raises exception if bag already exists
|
172
|
+
# NOTE: We might want to change this
|
162
173
|
def self.create_bag(name, options = {} )
|
163
|
-
time_now = Time.now.utc.iso8601
|
164
|
-
file_data = {}
|
165
|
-
file_data['meta'] = {
|
166
|
-
'name' => name,
|
167
|
-
'description' => '',
|
168
|
-
'created_at' => time_now,
|
169
|
-
'updated_at' => time_now
|
170
|
-
}
|
171
|
-
file_data['data'] = nil
|
172
174
|
unless exist? name
|
173
|
-
|
174
|
-
f.write file_data.to_yaml
|
175
|
-
f.chmod(0600)
|
176
|
-
end
|
175
|
+
Bag.new(name)
|
177
176
|
else
|
178
|
-
raise "Unable to create a new
|
177
|
+
raise "Unable to create a new bag \"#{name}\". It already exists."
|
179
178
|
end
|
180
|
-
bag name
|
181
179
|
end
|
182
180
|
|
183
181
|
##
|
@@ -193,6 +191,33 @@ module Keyp
|
|
193
191
|
end
|
194
192
|
end
|
195
193
|
|
194
|
+
##
|
195
|
+
#
|
196
|
+
# == options
|
197
|
+
# +:from+ current name of the bag (rename from)
|
198
|
+
# +:to+ new name of the bag (rename to)
|
199
|
+
#
|
200
|
+
def self.rename_bag(options = {})
|
201
|
+
|
202
|
+
#TODO: Implement this
|
203
|
+
# validate_options(:from,:to)
|
204
|
+
|
205
|
+
from_name = options[:from]
|
206
|
+
to_name = options[:to]
|
207
|
+
|
208
|
+
unless from_name || to_name
|
209
|
+
raise "required parameters are missing. Requires both :from and :to"
|
210
|
+
end
|
211
|
+
|
212
|
+
# if current bag does not exist, we raise and exception
|
213
|
+
unless exist?(from_name)
|
214
|
+
raise ("cannot rename #{from_name} because it does not exist")
|
215
|
+
end
|
216
|
+
bag = bag(from_name)
|
217
|
+
bag.rename(to_name)
|
218
|
+
#bag(from_name).rename(to_name)
|
219
|
+
end
|
220
|
+
|
196
221
|
def self.parse_arg_string(arg_string, options={})
|
197
222
|
|
198
223
|
mode = options[:token_mode] || :default
|
data/lib/keyp/bag.rb
CHANGED
@@ -4,21 +4,27 @@ module Keyp
|
|
4
4
|
# Some inspiration:
|
5
5
|
# http://stackoverflow.com/questions/2680523/dry-ruby-initialization-with-hash-argument
|
6
6
|
#
|
7
|
-
# TODO: add handling so that keys (hierarchical keys too) are accessed as members instead of
|
8
|
-
# hash access
|
7
|
+
# TODO: add handling so that keys (hierarchical keys too) are accessed as members instead of hash access
|
9
8
|
#
|
10
|
-
# TODO: move to own file, rename to "Bag"
|
11
9
|
# TODO: i18n error messages
|
10
|
+
# TODO: handle symbol to string and back for keys. Need instance setting to handle validating keys as symbols
|
12
11
|
#
|
13
12
|
class Bag
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
# TODO: Make name attr_reader
|
15
|
+
# TODO: See if we need data as an attr_accessor If not then it makes
|
16
|
+
# it easier to encapsulate handling of string/symbol keys
|
17
|
+
attr_reader :keypdir, :dirty, :meta
|
18
|
+
attr_accessor :data, :file_hash
|
19
|
+
|
20
|
+
def name
|
21
|
+
@meta['name']
|
22
|
+
end
|
17
23
|
|
18
24
|
##
|
19
25
|
# Returns the full path of this Bag's file
|
20
26
|
def keypfile
|
21
|
-
File.join(@keypdir,
|
27
|
+
File.join(@keypdir, name+@ext)
|
22
28
|
end
|
23
29
|
|
24
30
|
##
|
@@ -28,39 +34,44 @@ module Keyp
|
|
28
34
|
# +keypdir+
|
29
35
|
# +read_only+
|
30
36
|
# +ext+
|
31
|
-
def initialize(
|
37
|
+
def initialize(bag_name, options = {})
|
32
38
|
# I'm not happy with how creating instance variables works. There must be a cleaner way
|
33
|
-
@
|
39
|
+
@meta = { 'name' => bag_name}
|
34
40
|
options.each do |k,v|
|
35
|
-
puts "processing options #{k} = #{v}"
|
41
|
+
#JB set debug log: puts "processing options #{k} = #{v}"
|
36
42
|
instance_variable_set("@#{k}", v) unless v.nil?
|
37
43
|
end
|
38
|
-
# set attributes not set by params
|
39
44
|
|
45
|
+
# set attributes not set by params
|
40
46
|
@keypdir ||= Keyp::home
|
41
47
|
@read_only ||= false
|
42
48
|
@ext ||= Keyp::DEFAULT_EXT
|
43
|
-
#@keypfile = config_path
|
44
|
-
# load our resource
|
45
|
-
|
46
|
-
# load config file into hashes
|
47
|
-
# not the most efficient thing, but simpler and safe enough for now
|
48
49
|
|
49
50
|
unless File.exist? keypfile
|
50
|
-
puts "Bag.initialize, create_bag #{keypfile}"
|
51
|
-
|
51
|
+
#JB set debug log: puts "Bag.initialize, create_bag #{keypfile}"
|
52
|
+
#TODO: move this to its own method
|
53
|
+
time_now = Time.now.utc.iso8601(TIMESTAMP_FS_DIGITS)
|
54
|
+
file_data = {}
|
55
|
+
file_data['meta'] = {
|
56
|
+
'name' => bag_name,
|
57
|
+
'description' => '',
|
58
|
+
'created_at' => time_now,
|
59
|
+
'updated_at' => time_now
|
60
|
+
}
|
61
|
+
file_data['data'] = nil
|
62
|
+
File.open(Keyp::bag_path(bag_name), 'w') do |f|
|
63
|
+
f.write file_data.to_yaml
|
64
|
+
f.chmod(0600)
|
65
|
+
end
|
52
66
|
end
|
53
|
-
|
54
|
-
|
55
|
-
@meta = file_data[:meta]
|
56
|
-
@data = file_data[:data]|| {}
|
57
|
-
@file_hash = file_data[:file_hash]
|
67
|
+
load_file
|
58
68
|
@dirty = false
|
59
69
|
end
|
60
70
|
|
61
71
|
##
|
62
72
|
#
|
63
73
|
def [](key)
|
74
|
+
# TODO: convert from any symbols to string
|
64
75
|
@data[key]
|
65
76
|
end
|
66
77
|
|
@@ -74,6 +85,7 @@ module Keyp
|
|
74
85
|
# Sets a key:value pair
|
75
86
|
# NOTE: This may be made protected
|
76
87
|
def set_prop(key, value)
|
88
|
+
# TODO
|
77
89
|
unless @read_only
|
78
90
|
# TODO: check if data has been modified
|
79
91
|
# maybe there is a way hash tells us its been modified. If not then
|
@@ -83,22 +95,19 @@ module Keyp
|
|
83
95
|
@dirty = true
|
84
96
|
end
|
85
97
|
else
|
86
|
-
raise "Bag #{
|
98
|
+
raise "Bag #{name} is read only"
|
87
99
|
end
|
88
100
|
end
|
89
101
|
|
90
102
|
##
|
91
103
|
# Deletes the key:value pair from this bag for the given key
|
92
104
|
def delete(key)
|
93
|
-
|
94
|
-
|
95
|
-
@dirty = true
|
96
|
-
end
|
97
|
-
val = @data.delete(key)
|
98
|
-
else
|
99
|
-
raise "Bag #{@name} is read only"
|
105
|
+
if @read_only
|
106
|
+
raise "Cannot delete key \"#{key}\". Bag \"#{name}\" is read only."
|
100
107
|
end
|
101
|
-
|
108
|
+
|
109
|
+
@dirty = true if @data.key? key
|
110
|
+
@data.delete(key)
|
102
111
|
end
|
103
112
|
|
104
113
|
##
|
@@ -108,6 +117,14 @@ module Keyp
|
|
108
117
|
end
|
109
118
|
|
110
119
|
|
120
|
+
def read_only?
|
121
|
+
@read_only
|
122
|
+
end
|
123
|
+
|
124
|
+
def key?(key)
|
125
|
+
@data.key?(key)
|
126
|
+
end
|
127
|
+
|
111
128
|
##
|
112
129
|
# Adds key/value pairs from this bag to the Ruby ENV
|
113
130
|
# NOTE: Currently in development.
|
@@ -136,7 +153,7 @@ module Keyp
|
|
136
153
|
overwrite = options[:overwrite] || false
|
137
154
|
pattern = options[:sysvar] if options.key?(:sysvar)
|
138
155
|
|
139
|
-
pattern ||= /(
|
156
|
+
pattern ||= /(..*)/
|
140
157
|
@data.each do |key,value|
|
141
158
|
if pattern.match(key)
|
142
159
|
# TODO: add overwrite checking
|
@@ -147,9 +164,71 @@ module Keyp
|
|
147
164
|
assigned
|
148
165
|
end
|
149
166
|
|
167
|
+
##
|
168
|
+
# load environment variables into this bag
|
169
|
+
# === Options
|
170
|
+
# +:pattern+ Applies keys matching this pattern
|
171
|
+
# +:overwrite+ true to overwrite existing keys, false (default) to not overwrite existing keys
|
172
|
+
#
|
173
|
+
#
|
174
|
+
def load_from_env(options = {})
|
175
|
+
assigned = {}
|
176
|
+
overwrite = options[:overwrite] || false
|
177
|
+
pattern = options[:pattern] || /(..*)/
|
178
|
+
ENV.each do | key, value|
|
179
|
+
if pattern.match(key)
|
180
|
+
unless overwrite && @data.key?(key)
|
181
|
+
@data[key] = value
|
182
|
+
assigned[key] = value
|
183
|
+
end
|
184
|
+
else
|
185
|
+
puts "load_from_env. No match for #{key}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
assigned
|
189
|
+
end
|
150
190
|
|
151
191
|
# TODO add from hash
|
152
192
|
|
193
|
+
##
|
194
|
+
# renames the bag to +new_name+
|
195
|
+
# returns new bag name if succesful, raises exception if not
|
196
|
+
#
|
197
|
+
def rename(new_name, options = {})
|
198
|
+
|
199
|
+
# Since we are not changing any key pairs, the only meta to be changed is +name+
|
200
|
+
#
|
201
|
+
# TODO: Check for valid key name
|
202
|
+
#TODO: change to try to create a new file and lock it. If we can, then we fill it
|
203
|
+
if Keyp::exist?(new_name)
|
204
|
+
raise "Bag rename error. Target \"#{new_name}\" already exists."
|
205
|
+
end
|
206
|
+
|
207
|
+
if @dirty
|
208
|
+
# We don't want to allow renaming a bag with modified values because it will complicate rollback
|
209
|
+
#TODO: improve error message
|
210
|
+
raise "Can't rename a modified bag. Save first"
|
211
|
+
end
|
212
|
+
|
213
|
+
# TODO: treat the following as a transaction so if the bag file can't be renamed then
|
214
|
+
# the old file is restored unmodified
|
215
|
+
#curr_name = @meta['name']
|
216
|
+
curr_name = name
|
217
|
+
old_file = Keyp::bag_path(curr_name)
|
218
|
+
to_file = Keyp::bag_path(new_name)
|
219
|
+
#TODO: Add file locking for concurrency protection
|
220
|
+
@meta['name'] = new_name
|
221
|
+
@meta['last_name'] =curr_name
|
222
|
+
@renaming = true
|
223
|
+
# saves with the new name
|
224
|
+
save
|
225
|
+
@renaming = nil
|
226
|
+
# now remove the old file
|
227
|
+
File.delete(old_file)
|
228
|
+
|
229
|
+
load_file
|
230
|
+
@meta['name']
|
231
|
+
end
|
153
232
|
|
154
233
|
def import(filename)
|
155
234
|
raise "import not yet supported"
|
@@ -165,39 +244,12 @@ module Keyp
|
|
165
244
|
# TODO: def to_s
|
166
245
|
|
167
246
|
##
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# Get extension
|
175
|
-
file_ext = File.extname(config_path)
|
176
|
-
|
177
|
-
# check
|
178
|
-
# only YAML supported for initial version. Will consider adapters to
|
179
|
-
# abstract the persistence layer
|
180
|
-
|
181
|
-
|
182
|
-
# TODO: make this hardcoded case a hash of helpers
|
183
|
-
# TODO: Add two sections: Meta and data, then return as hash
|
184
|
-
|
185
|
-
if file_ext.downcase == Keyp::DEFAULT_EXT
|
186
|
-
|
187
|
-
# Either we are arbitrarily creating directories when
|
188
|
-
# given a path for a file that doesn't exist
|
189
|
-
# or we have special behavior for the default dir
|
190
|
-
# or we just fault and let the caller deal with it
|
191
|
-
unless File.exist? config_path
|
192
|
-
raise "Keyp config file not found: #{config_path}"
|
193
|
-
end
|
194
|
-
|
195
|
-
file_data = YAML.load_file(config_path)
|
196
|
-
|
197
|
-
else
|
198
|
-
raise "Keyp version x only supports YAML for config files. You tried a #{file_ext}"
|
199
|
-
end
|
200
|
-
{ meta: file_data['meta'], data: file_data['data']||{}, file_hash: file_data.hash }
|
247
|
+
#
|
248
|
+
def load_file
|
249
|
+
file_data = Bag::load_file(keypfile)
|
250
|
+
@meta = file_data[:meta]
|
251
|
+
@data = file_data[:data]|| {}
|
252
|
+
@file_hash = file_data[:file_hash]
|
201
253
|
end
|
202
254
|
|
203
255
|
##
|
@@ -209,22 +261,25 @@ module Keyp
|
|
209
261
|
if @read_only
|
210
262
|
raise "This bag instance is read only"
|
211
263
|
end
|
212
|
-
if @dirty
|
264
|
+
if @dirty || @renaming
|
213
265
|
# lock file
|
214
266
|
# read checksum
|
215
267
|
# if checksum matches our saved checksum then update file and release lock
|
216
268
|
# otherwise, raise
|
217
269
|
# TODO: implement merge from updated file and raise only if conflict
|
218
270
|
begin
|
219
|
-
file_data = { 'meta' => @meta, 'data' => @data }
|
220
|
-
|
221
271
|
if File.exist? keypfile
|
222
|
-
read_file_data =
|
272
|
+
read_file_data = Bag::load_file(keypfile)
|
223
273
|
unless @file_hash == read_file_data[:file_hash]
|
224
274
|
raise "Will not write to #{keypfile}\nHashes differ. Expected hash =#{@file_hash}\n" +
|
225
275
|
"found hash #{read_file_data[:file_hash]}"
|
226
276
|
end
|
227
277
|
end
|
278
|
+
if @dirty && !@renaming
|
279
|
+
@meta['updated_at'] = Time.now.utc.iso8601(Keyp::TIMESTAMP_FS_DIGITS)
|
280
|
+
end
|
281
|
+
|
282
|
+
file_data = { 'meta' => @meta, 'data' => @data }
|
228
283
|
File.open(keypfile, 'w') do |f|
|
229
284
|
f.write file_data.to_yaml
|
230
285
|
end
|
@@ -233,7 +288,49 @@ module Keyp
|
|
233
288
|
# TODO: log error
|
234
289
|
|
235
290
|
end
|
291
|
+
else
|
292
|
+
puts "save called but not saving (not dirty or renaming flag set"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
# Class methods
|
298
|
+
##
|
299
|
+
# Give full path, attempt to load
|
300
|
+
# sticking with YAML format for now
|
301
|
+
# May add new file format later in which case we'll
|
302
|
+
# TODO: Make this a class method and an instance method
|
303
|
+
# that calls the class method and does the instance assignment
|
304
|
+
def self.load_file (config_path)
|
305
|
+
# Get extension
|
306
|
+
file_ext = File.extname(config_path)
|
307
|
+
|
308
|
+
# check
|
309
|
+
# only YAML supported for initial version. Will consider adapters to
|
310
|
+
# abstract the persistence layer
|
311
|
+
|
312
|
+
|
313
|
+
# TODO: make this hardcoded case a hash of helpers
|
314
|
+
# TODO: Add two sections: Meta and data, then return as hash
|
315
|
+
|
316
|
+
if file_ext.downcase == Keyp::DEFAULT_EXT
|
317
|
+
|
318
|
+
# Either we are arbitrarily creating directories when
|
319
|
+
# given a path for a file that doesn't exist
|
320
|
+
# or we have special behavior for the default dir
|
321
|
+
# or we just fault and let the caller deal with it
|
322
|
+
unless File.exist? config_path
|
323
|
+
raise "Keyp config file not found: #{config_path}"
|
324
|
+
end
|
325
|
+
|
326
|
+
file_data = YAML.load_file(config_path)
|
327
|
+
|
328
|
+
else
|
329
|
+
raise "Keyp version x only supports YAML for config files. You tried a #{file_ext}"
|
236
330
|
end
|
331
|
+
{ meta: file_data['meta'], data: file_data['data']||{}, file_hash: file_data.hash }
|
237
332
|
end
|
238
|
-
|
239
|
-
|
333
|
+
|
334
|
+
|
335
|
+
end # class Bag
|
336
|
+
end # module Keyp
|
data/lib/keyp/version.rb
CHANGED
data/spec/bag_spec.rb
CHANGED
@@ -2,44 +2,53 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Keyp::Bag do
|
4
4
|
|
5
|
+
def bag_name_for_testing
|
6
|
+
# TODO: pseudo random bag name generation to a helper
|
7
|
+
"grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
8
|
+
end
|
9
|
+
|
5
10
|
context "is empty" do
|
6
11
|
before (:each) do
|
7
|
-
|
8
|
-
@bag = Keyp::Bag.new
|
12
|
+
@bag_name = bag_name_for_testing
|
13
|
+
@bag = Keyp::Bag.new @bag_name
|
9
14
|
end
|
10
15
|
|
11
16
|
it "should return an empty hash" do
|
12
|
-
|
13
17
|
@bag.data.size.should == 0
|
14
18
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
after (:each) do
|
20
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
context "is not empty" do
|
21
25
|
before (:each) do
|
22
|
-
|
23
|
-
@bag = Keyp::Bag.new
|
26
|
+
@bag_name = bag_name_for_testing
|
27
|
+
@bag = Keyp::Bag.new @bag_name
|
24
28
|
@bag['LIGHTS'] = 'out'
|
25
29
|
end
|
26
30
|
|
27
31
|
it "should return a non-empty hash" do
|
28
32
|
@bag.data.size.should > 0
|
29
33
|
end
|
34
|
+
after (:each) do
|
35
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
36
|
+
end
|
30
37
|
end
|
31
38
|
|
32
39
|
context "environment variables" do
|
33
40
|
before (:each) do
|
34
|
-
|
35
|
-
|
36
|
-
@bag = Keyp::Bag.new bag_name
|
41
|
+
@bag_name = bag_name_for_testing
|
42
|
+
@bag = Keyp::Bag.new @bag_name
|
37
43
|
end
|
38
44
|
it "should copy all vars" do
|
39
45
|
testvars = {
|
40
46
|
'ALPHA' => 'First in the phonetic alphabet',
|
41
47
|
'BRAVO' => 'Second in the phonetic alphabet',
|
42
|
-
'CHARLIE' => 'Third in the phonetic alphabet'
|
48
|
+
'CHARLIE' => 'Third in the phonetic alphabet',
|
49
|
+
'_' => 'a single underscore',
|
50
|
+
'a' => 'first letter in the alphabet',
|
51
|
+
'bb' => 'two of the second letter in the alphabet'
|
43
52
|
}
|
44
53
|
|
45
54
|
testvars.each { |key, value| @bag[key] = value }
|
@@ -50,14 +59,39 @@ describe Keyp::Bag do
|
|
50
59
|
ENV[key].should == value
|
51
60
|
end
|
52
61
|
end
|
62
|
+
|
63
|
+
it 'should receive all vars' do
|
64
|
+
test_keyvals = {
|
65
|
+
'foo' => 'bar',
|
66
|
+
'biz' => 'baz',
|
67
|
+
'GRUE' => 'Eats you in the dark',
|
68
|
+
'a' => 'first letter in the alphabet',
|
69
|
+
'bb' => 'two of the second letter in the alphabet'
|
70
|
+
}
|
71
|
+
|
72
|
+
test_keyvals.each do |key,value|
|
73
|
+
ENV[key] = value
|
74
|
+
end
|
75
|
+
@bag.load_from_env
|
76
|
+
ENV.each do |key,value|
|
77
|
+
#puts "testing key=#{key}"
|
78
|
+
@bag.key?(key).should == true
|
79
|
+
@bag[key].should == value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should not overwrite existing keys'
|
84
|
+
it 'should match filtering pattern'
|
85
|
+
after (:each) do
|
86
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
87
|
+
end
|
53
88
|
end
|
54
89
|
|
55
|
-
context
|
90
|
+
context 'Bag state' do
|
56
91
|
|
57
92
|
before (:each) do
|
58
|
-
|
59
|
-
|
60
|
-
@bag = Keyp::Bag.new bag_name
|
93
|
+
@bag_name = bag_name_for_testing
|
94
|
+
@bag = Keyp::Bag.new @bag_name
|
61
95
|
end
|
62
96
|
|
63
97
|
it 'should not set the dirty flag when no items are in the bag' do
|
@@ -92,10 +126,102 @@ describe Keyp::Bag do
|
|
92
126
|
@bag['KEY1'] = 'value1'
|
93
127
|
@bag.dirty.should == false
|
94
128
|
end
|
129
|
+
|
130
|
+
it 'should allow assigning a key if not read only'
|
131
|
+
it 'should not allow assigning a key if read only'
|
132
|
+
after (:each) do
|
133
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'Metadata' do
|
138
|
+
|
139
|
+
before (:each) do
|
140
|
+
@bag_name = bag_name_for_testing
|
141
|
+
@bag = Keyp::Bag.new @bag_name
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should have metadata" do
|
145
|
+
# @bag.meta['created_on'].should_not == nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should not update created_at after bag is initially created' do
|
149
|
+
created_at = @bag.meta['created_at']
|
150
|
+
@bag['foo'] = 'bar'
|
151
|
+
@bag.save
|
152
|
+
@bag.meta['created_at'].should == created_at
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should update updated_at when bag is saved' do
|
156
|
+
updated_at = @bag.meta['updated_at']
|
157
|
+
@bag['foo'] = 'bar'
|
158
|
+
@bag.save
|
159
|
+
@bag.meta['updated_at'].should > updated_at
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should not update updated_at if bag has not been saved' do
|
163
|
+
updated_at = @bag.meta['updated_at']
|
164
|
+
@bag['foo'] = 'bar'
|
165
|
+
@bag.meta['updated_at'].should == updated_at
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should use metadata for the bag name' do
|
169
|
+
def @bag.set_name(name)
|
170
|
+
@meta['name'] = name
|
171
|
+
end
|
172
|
+
test_name = 'norwegian_blue'
|
173
|
+
@bag.set_name(test_name)
|
174
|
+
@bag.name.should == test_name
|
175
|
+
end
|
176
|
+
after (:each) do
|
177
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
178
|
+
end
|
95
179
|
end
|
96
180
|
|
181
|
+
context 'Bag management' do
|
182
|
+
before (:each) do
|
183
|
+
@from_name = bag_name_for_testing
|
184
|
+
@from_bag = Keyp::Bag.new @from_name
|
185
|
+
sleep(1)
|
186
|
+
@to_name = bag_name_for_testing
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should rename if new name does not exist' do
|
190
|
+
bag = @from_bag
|
191
|
+
kp = {
|
192
|
+
'key1' => 'value1',
|
193
|
+
'key2' => 'value2'
|
194
|
+
}
|
195
|
+
|
196
|
+
# seed a couple of kv pairs just to keep it a bit real
|
197
|
+
# but we should also test with an empty bag
|
198
|
+
kp.each {|k,v| bag[k] = v }
|
199
|
+
bag.save
|
200
|
+
before_meta = {}
|
201
|
+
bag.meta.each { |k,v| before_meta[k] = v }
|
202
|
+
bag.name.should == @from_name
|
203
|
+
@to_name.should_not == @from_name
|
204
|
+
# we know the to_name does not exist
|
205
|
+
result = bag.rename(@to_name)
|
206
|
+
bag.name.should == @to_name
|
207
|
+
bag.meta['name'].should == @to_name
|
208
|
+
bag.meta['created_at'].should == before_meta['created_at']
|
209
|
+
# Since we are not changing any key pairs, updated_at doesn't change
|
210
|
+
bag.meta['updated_at'].should == before_meta['updated_at']
|
211
|
+
bag.meta['name'].should_not == before_meta['name']
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should not rename if new name exists' do
|
215
|
+
|
216
|
+
end
|
217
|
+
after (:each) do
|
218
|
+
Keyp.delete_bag @from_name if Keyp.exist? @from_name
|
219
|
+
Keyp.delete_bag @to_name if Keyp.exist? @to_name
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
97
224
|
it 'should return a key with data member'
|
98
225
|
it 'should return a key acting as a hash'
|
99
|
-
|
100
|
-
it 'should not allow assigning a key if read only'
|
226
|
+
|
101
227
|
end
|
data/spec/keyp_spec.rb
CHANGED
@@ -1,18 +1,25 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
2
4
|
|
3
5
|
describe Keyp do
|
4
6
|
|
5
|
-
|
7
|
+
def bag_name_for_testing
|
8
|
+
# TODO: pseudo random bag name generation to a helper
|
9
|
+
"bring_a_towel_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'CONSTANTS' do
|
6
13
|
it 'should return correct version string' do
|
7
14
|
#Keyp.version_string.should == "Keyp version #{Keyp::VERSION}"
|
8
|
-
Keyp::VERSION.should == '0.0.
|
15
|
+
Keyp::VERSION.should == '0.0.7'
|
9
16
|
end
|
10
17
|
|
11
18
|
it 'should specify default store' do
|
12
19
|
Keyp::DEFAULT_BAG.should == 'default'
|
13
20
|
end
|
14
21
|
|
15
|
-
it
|
22
|
+
it 'should specifify default store extension' do
|
16
23
|
Keyp::DEFAULT_EXT.should == '.yml'
|
17
24
|
end
|
18
25
|
|
@@ -21,16 +28,41 @@ describe Keyp do
|
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
|
-
context
|
31
|
+
context 'Default Keyp directory' do
|
25
32
|
it 'should return correct default keyp dir' do
|
26
33
|
# DEFAULT_KEYP_DIRNAME = ENV['KEYP_DIRNAME'] || '.keyp'
|
27
34
|
Keyp::home.should == File.join(ENV['HOME'], '.keyp')
|
28
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'Alternate Keyp directory' do
|
39
|
+
|
40
|
+
|
41
|
+
before (:each) do
|
42
|
+
@root_tempdir = Dir.mktmpdir
|
43
|
+
@temp_keyp_dir = File.join(@root_tempdir,Keyp::DEFAULT_KEYP_DIR)
|
44
|
+
ENV['KEYP_HOME'] = @temp_keyp_dir
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should be able to override default keyp dir' do
|
48
|
+
puts "ENV['KEYP_HOME']=#{ENV['KEYP_HOME']}"
|
49
|
+
keyp_dir = Keyp::setup
|
50
|
+
keyp_dir.should == Keyp::home
|
51
|
+
keyp_dir.should == @temp_keyp_dir
|
52
|
+
Keyp::home.should == ENV['KEYP_HOME']
|
53
|
+
|
54
|
+
end
|
29
55
|
|
30
|
-
|
56
|
+
#TODO: Keyp::setup should return nil when trying to setup to an existing dir
|
57
|
+
# Check if we can describe a method
|
58
|
+
after (:each) do
|
59
|
+
puts "AFTER called, going to try to delete #{@tempdir}"
|
60
|
+
FileUtils.rm_rf @root_tempdir
|
61
|
+
ENV['KEYP_HOME'] = nil
|
62
|
+
end
|
31
63
|
end
|
32
64
|
|
33
|
-
context
|
65
|
+
context 'Bag management' do
|
34
66
|
it 'should return default bag' do
|
35
67
|
keyper = Keyp::bag
|
36
68
|
keyper.name.should == 'default'
|
@@ -42,15 +74,65 @@ describe Keyp do
|
|
42
74
|
Keyp.exist?(keyper.name).should == true
|
43
75
|
end
|
44
76
|
|
45
|
-
it
|
77
|
+
it 'should say a bag does not exist' do
|
46
78
|
# TODO: add bag name generation to a helper
|
47
79
|
bag_name = "grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
48
80
|
Keyp.exist?(bag_name).should_not == true
|
49
81
|
end
|
50
|
-
|
51
82
|
end
|
52
83
|
|
84
|
+
context 'Renaming bags' do
|
85
|
+
# NOTE: Yeah, this code and the bag_spec code are redundant
|
86
|
+
before (:each) do
|
87
|
+
@from_name = bag_name_for_testing
|
88
|
+
if Keyp::exist?(@from_name)
|
89
|
+
puts "Rename bag, bag #{@from_name} already exists"
|
90
|
+
else
|
91
|
+
puts "Rename bag, bag #{@from_name} does NOT exist"
|
92
|
+
end
|
93
|
+
@from_bag = Keyp.create_bag(@from_name)
|
94
|
+
sleep(1)
|
95
|
+
@to_name = bag_name_for_testing
|
96
|
+
end
|
53
97
|
|
98
|
+
it 'should rename bag if it exists and new name does not' do
|
99
|
+
bag = @from_bag
|
100
|
+
kp = {
|
101
|
+
'key1' => 'value1',
|
102
|
+
'key2' => 'value2'
|
103
|
+
}
|
104
|
+
|
105
|
+
# seed a couple of kv pairs just to keep it a bit real
|
106
|
+
# but we should also test with an empty bag
|
107
|
+
kp.each {|k,v| bag[k] = v }
|
108
|
+
bag.save
|
109
|
+
before_meta = {}
|
110
|
+
bag.meta.each { |k,v| before_meta[k] = v }
|
111
|
+
bag.name.should == @from_name
|
112
|
+
@to_name.should_not == @from_name
|
113
|
+
result = Keyp.rename_bag(from: @from_name, to: @to_name)
|
114
|
+
Keyp.exist?(@from_name).should == false
|
115
|
+
Keyp.exist?(@to_name).should == true
|
116
|
+
bag = Keyp.bag @to_name
|
117
|
+
bag.name.should == @to_name
|
118
|
+
bag.meta['name'].should == @to_name
|
119
|
+
bag.meta['created_at'].should == before_meta['created_at']
|
120
|
+
# Since we are not changing any key pairs, updated_at doesn't change
|
121
|
+
bag.meta['updated_at'].should == before_meta['updated_at']
|
122
|
+
kp.each do |k,v|
|
123
|
+
bag.key?(k).should == true
|
124
|
+
bag[k].should == v
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should not rename bag if current name does not exist'
|
129
|
+
it 'should not rename bag if new name exists'
|
130
|
+
|
131
|
+
after (:each) do
|
132
|
+
Keyp.delete_bag @from_name if Keyp.exist? @from_name
|
133
|
+
Keyp.delete_bag @to_name if Keyp.exist? @to_name
|
134
|
+
end
|
135
|
+
end
|
54
136
|
|
55
137
|
|
56
138
|
# Braindump of tests
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keyp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Baldwin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|