keyp 0.0.2
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/README.md +112 -0
- data/Rakefile +3 -0
- data/bin/keyp +195 -0
- data/keyp.gemspec +27 -0
- data/lib/keyp/cli.rb +11 -0
- data/lib/keyp/version.rb +3 -0
- data/lib/keyp.rb +298 -0
- data/spec/keyp_spec.rb +39 -0
- data/spec/spec_helper.rb +7 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b14d1fd77f310af5875fcb9ad4ccd985a04c5abc
|
4
|
+
data.tar.gz: 78815bce0d5db5bb66103fb60132960256d9ba5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1db7713dbf1a667967d3d5d76c7de9587cf961e6f816cd4059c7145424bcb5e9b3ec1e9129fe4ed23ebdeeb41a80e13475d7a96208fa940a1ff059a9d7accbc2
|
7
|
+
data.tar.gz: 49cadf1084cfad53323fdfbc9af3d1bb238e4ab40c838dc9c937ec422df87a5bda95efcc2221821a6eb3d8e77994afb6306d355e274ae16e22b3999d1c770dd6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2014 John Baldwin
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# Keyp
|
2
|
+
|
3
|
+
Keyp is an executable Ruby gem that manages user/machine specific key/value pairs for your Ruby application.
|
4
|
+
|
5
|
+
Web applications generally need sensitive information, like salts and keys. Managing these generally involves writing
|
6
|
+
a custom solution for your application. Approaches are typically environment variables, custom data files, or
|
7
|
+
environment setting scripts. Following best practices, we don't want to store these in version control with our
|
8
|
+
application source. So I'm creating Keyp to make it simple to manage keys for your application across the development,
|
9
|
+
testing, and production environments.
|
10
|
+
|
11
|
+
## Important
|
12
|
+
|
13
|
+
Keyp is still in early development and is experimental. As such it is very much a work in progress. Not all features
|
14
|
+
expressed in the documentation may be working. The documentation may be plain wrong. I don't recommend you use Keyp
|
15
|
+
for any kind of production code at this point in this gem's lifecycle.
|
16
|
+
|
17
|
+
## Quick Tour
|
18
|
+
|
19
|
+
Keyp manages key value pairs in collections. As of version 0.0.1, Keyp refers to collections as *bags*. There is a
|
20
|
+
default bag called *default*. Unless you specify a bag name, *default* will be used.
|
21
|
+
|
22
|
+
Here are some command line examples showing some basic Keyp functionality using the default bag. Here we set a couple
|
23
|
+
of keys, list all the key/value pairs in the default bag, and finally get the value for a single key.
|
24
|
+
|
25
|
+
$ keyp set cloud_access_id=BMT216AF63958
|
26
|
+
$ keyp set cloud_secret_key=eabca9a58834aec15af0578ac84abfbdab7c3795
|
27
|
+
|
28
|
+
$ keyp list
|
29
|
+
* bag:default
|
30
|
+
cloud_access_id: BMT216AF63958
|
31
|
+
cloud_secret_key: eabca9a58834aec15af0578ac84abfbdab7c3795
|
32
|
+
|
33
|
+
$ keyp get cloud_access_id
|
34
|
+
BMT216AF63958
|
35
|
+
|
36
|
+
In your Ruby application, you can use Keyp as follows:
|
37
|
+
|
38
|
+
# get keys from the default key collection and use
|
39
|
+
bag = Keyp.bag
|
40
|
+
my_account.authenticate(bag['cloud_access_id'],bag['cloud_secret_key'])
|
41
|
+
|
42
|
+
# or update ENV
|
43
|
+
bag = Keyp.bag
|
44
|
+
Keyp::add_to_env(bag)
|
45
|
+
|
46
|
+
my_account.authenticate(ENV['cloud_access_id'],ENV['cloud_secret_key'])
|
47
|
+
|
48
|
+
Keyp is not limited to just access keys. Any kind of string based name/value pair can be used.
|
49
|
+
|
50
|
+
TODO: Improve description
|
51
|
+
|
52
|
+
_CAVEAT:_ This gem is at a very early stage in development. Any and all functionality is subject to change.
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile:
|
57
|
+
|
58
|
+
gem 'keyp'
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install keyp
|
67
|
+
|
68
|
+
## Setup
|
69
|
+
|
70
|
+
Run the following to set up default installation
|
71
|
+
$ keyp init
|
72
|
+
This will create a $HOME/.keyp directory if one does not already exist, configure this director for use with Keyp.
|
73
|
+
|
74
|
+
### Customization
|
75
|
+
|
76
|
+
Keyp uses the ~/.keyp directory by default. To override, set the environment variable, KEYP_HOME to your choice
|
77
|
+
of directory.
|
78
|
+
|
79
|
+
TODO: add Bash config setting to export Keyp ENV vars to the ENV or make ENV vars accessible via Keyp hashes
|
80
|
+
|
81
|
+
## Usage
|
82
|
+
|
83
|
+
TODO: Write detailed usage instructions here
|
84
|
+
|
85
|
+
###
|
86
|
+
|
87
|
+
|
88
|
+
### Command line interface
|
89
|
+
|
90
|
+
TODO: Write more detailed documentation, For now, see the quick start above and run the following to see CLI options
|
91
|
+
$ keyp --help
|
92
|
+
|
93
|
+
## Development plan/Features to implement
|
94
|
+
|
95
|
+
* Get basic functionality working soundly backed with effective tests
|
96
|
+
* Implement .keyp directory setup
|
97
|
+
* Add ENV vars to bag
|
98
|
+
* Add bag vars to ENV
|
99
|
+
* Incorporate with shell configuration
|
100
|
+
* Write very clear documentation
|
101
|
+
* Implement import/Export to YAML and/or JSON
|
102
|
+
* Add API interfaces for other language, starting with Python
|
103
|
+
* Consider: Add configuration options for storage (YAML,JSON, SQLITE)
|
104
|
+
|
105
|
+
|
106
|
+
## Contributing
|
107
|
+
|
108
|
+
1. Fork it ( http://github.com/<my-github-username>/keyp/fork )
|
109
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
110
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
111
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
112
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/keyp
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
#
|
5
|
+
# NOTE: This is my first ruby gem and first executable ruby gem. I see a bunch of different
|
6
|
+
# implementations. A good number and a few well known use Thor, so seems like a good idea to
|
7
|
+
# figure out how to use Thor as well.
|
8
|
+
#
|
9
|
+
# I'm trying out some of the different impementations for Keyp, learning how they work, pros and cons
|
10
|
+
# Then I'll pick a single approach after exploring them
|
11
|
+
|
12
|
+
# TO run in the project dir:
|
13
|
+
# ruby -Ilib ./bin/keyp
|
14
|
+
|
15
|
+
# resolve bin path, ignoring symlinks
|
16
|
+
require 'pathname'
|
17
|
+
bin_file = Pathname.new(__FILE__).realpath
|
18
|
+
|
19
|
+
# http://davetron5000.github.io/gli/
|
20
|
+
require 'gli'
|
21
|
+
|
22
|
+
require 'pp'
|
23
|
+
|
24
|
+
require 'keyp'
|
25
|
+
include GLI::App
|
26
|
+
|
27
|
+
# keyp set key=value
|
28
|
+
# keyp get key
|
29
|
+
# keyp
|
30
|
+
|
31
|
+
|
32
|
+
# keyp create bag
|
33
|
+
|
34
|
+
program_desc 'A command line linterface for the Keyp key/value manager'
|
35
|
+
|
36
|
+
flag [:b,:bag], default_value: 'default'
|
37
|
+
switch [:d, :debug]
|
38
|
+
|
39
|
+
# TODO: fix this because this implies that we either already have a bag or it will create a new one
|
40
|
+
pre do |global_options, command, options, args|
|
41
|
+
# initialize our store
|
42
|
+
$debug = global_options[:debug]
|
43
|
+
if $debug
|
44
|
+
puts "pre-hook"
|
45
|
+
puts "command:"
|
46
|
+
pp command
|
47
|
+
puts "options:"
|
48
|
+
puts options
|
49
|
+
puts "args: #{args}"
|
50
|
+
puts "args.class=#{args.class}"
|
51
|
+
puts "---- end prehook ---"
|
52
|
+
end
|
53
|
+
|
54
|
+
unless command.name == :setup
|
55
|
+
|
56
|
+
# check if we are configured
|
57
|
+
unless Keyp.configured?
|
58
|
+
puts "Please run setup to use Keyp. Run with --help to view help menu"
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
|
62
|
+
$bag = Keyp.bag(global_options[:bag])
|
63
|
+
else
|
64
|
+
$bag = nil
|
65
|
+
end
|
66
|
+
true # hack to prevent
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Key,value commands
|
71
|
+
#arg_name 'keys', :multiple
|
72
|
+
command :get do |c|
|
73
|
+
c.action do |global_options,options,args|
|
74
|
+
puts "--- get args= #{args}" if $debug
|
75
|
+
if args.length == 0
|
76
|
+
puts "Usage: keyp get KEY"
|
77
|
+
puts "Must specify a key."
|
78
|
+
else
|
79
|
+
val = $bag.data[args[0]]
|
80
|
+
unless val.nil?
|
81
|
+
puts val
|
82
|
+
else
|
83
|
+
puts ''
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# TODO: set is not optimal. Right now it only handles a single key/value pair and
|
90
|
+
# any global or command parameters after the key=value will be sucked into the value
|
91
|
+
# Se we'll need to implement values in quotes if the value contains whitespace
|
92
|
+
|
93
|
+
arg_name 'values', :multiple
|
94
|
+
command :set do |c|
|
95
|
+
c.action do |global_options,options,args|
|
96
|
+
# When/if implementing multiple key assignments
|
97
|
+
# check args.length % 2
|
98
|
+
# parse the args,
|
99
|
+
|
100
|
+
# concatenate all the args
|
101
|
+
# then parse on =
|
102
|
+
buff = ''
|
103
|
+
args.each do |arg|
|
104
|
+
buff << arg << ' '
|
105
|
+
buff
|
106
|
+
end
|
107
|
+
|
108
|
+
nvp = Keyp.parse_arg_string buff
|
109
|
+
|
110
|
+
if nvp
|
111
|
+
$bag[nvp[:key]] = nvp[:value]
|
112
|
+
else
|
113
|
+
# puts "Usage keyp set KEY1=VALUE1 [KEY2=VALUE2 ...]"
|
114
|
+
puts "Usage: keyp set KEY=VALUE"
|
115
|
+
end
|
116
|
+
|
117
|
+
# $bag.data[]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
command :unset do |c|
|
122
|
+
c.action do |global_options,options, args|
|
123
|
+
if args.length == 0
|
124
|
+
# puts 'Usage: keyp unset KEY1 [KEY2 ...]'
|
125
|
+
puts 'Usage: keyp unset KEY1'
|
126
|
+
puts 'Must specify a key to unset.'
|
127
|
+
else
|
128
|
+
printf "Unsetting #{args[0]}..."
|
129
|
+
# if delete
|
130
|
+
val = $bag.delete(args[0])
|
131
|
+
if val
|
132
|
+
puts "unset #{args[0]}: #{val}"
|
133
|
+
printf "done.\n"
|
134
|
+
else
|
135
|
+
puts "key \"#{args[0]}\" not found"
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
command :list do |c|
|
143
|
+
c.action do
|
144
|
+
puts "* bag:#{$bag.name}"
|
145
|
+
|
146
|
+
unless $bag.empty?
|
147
|
+
$bag.data.each do |key,value|
|
148
|
+
#printf("$s : $s\n", key,value)
|
149
|
+
puts "#{key}: #{value}"
|
150
|
+
end
|
151
|
+
else
|
152
|
+
puts "You have an empty bag."
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
command :setup do |c|
|
160
|
+
c.action do |global_options, options, args|
|
161
|
+
puts "keyp setup..."
|
162
|
+
Keyp::setup
|
163
|
+
$bag = Keyp::create_store 'default'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
post do |global_options, command, options, args|
|
168
|
+
# if bag.data is dirty, then save
|
169
|
+
if $debug
|
170
|
+
puts "post-hook, state of bag.dirty = #{$bag.dirty}"
|
171
|
+
end
|
172
|
+
|
173
|
+
unless command.name == :setup
|
174
|
+
$bag.save
|
175
|
+
else
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
end
|
182
|
+
# bag management commands
|
183
|
+
|
184
|
+
# create a bag
|
185
|
+
# command :create
|
186
|
+
|
187
|
+
# delete a bag (with confirmation)
|
188
|
+
|
189
|
+
# show bags
|
190
|
+
# command :show
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
exit run(ARGV)
|
data/keyp.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'keyp/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "keyp"
|
8
|
+
spec.version = Keyp::VERSION
|
9
|
+
spec.authors = ["John Baldwin"]
|
10
|
+
spec.email = ["jlbaldwin@gmail.com"]
|
11
|
+
spec.summary = %q{Manage environment/machine specific key/value pairs for your Ruby application.}
|
12
|
+
spec.description = spec.summary=
|
13
|
+
spec.homepage = "https://github.com/johnbaldwin/keyp-ruby"
|
14
|
+
spec.license = "Apache v2"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
#spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.1'
|
23
|
+
spec.add_development_dependency 'gli', '~> 2.9'
|
24
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
25
|
+
spec.executables = 'keyp'
|
26
|
+
|
27
|
+
end
|
data/lib/keyp/cli.rb
ADDED
data/lib/keyp/version.rb
ADDED
data/lib/keyp.rb
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
require "keyp/version"
|
2
|
+
require 'json'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
#
|
6
|
+
# TODO: build out cli.rb to trim the exe script, keyp to its bare minimum
|
7
|
+
#
|
8
|
+
module Keyp
|
9
|
+
# Your code goes here...
|
10
|
+
ORIGINAL_ENV = ENV.to_hash
|
11
|
+
|
12
|
+
# TODO:
|
13
|
+
# Put this in initializer so testing can create its own directory and not muck
|
14
|
+
# up the operational default
|
15
|
+
|
16
|
+
|
17
|
+
KEYP_HOME = File.join(ENV['HOME'], '.keyp')
|
18
|
+
DEFAULT_STORE = 'default'
|
19
|
+
DEFAULT_EXT = '.yml'
|
20
|
+
# This method sets up the keyp director
|
21
|
+
#def self.setup
|
22
|
+
# # check if keyp directory exists. If not, set it up
|
23
|
+
# unless Dir.exist?(File.join(DEFAULT_KEYP_DIRNAME))
|
24
|
+
#
|
25
|
+
# end
|
26
|
+
#end
|
27
|
+
|
28
|
+
def self.configured?
|
29
|
+
Dir.exist?(KEYP_HOME)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.setup(options ={})
|
33
|
+
|
34
|
+
# check if keyp directory exists
|
35
|
+
|
36
|
+
unless Dir.exist?(KEYP_HOME)
|
37
|
+
puts "making directory #{KEYP_HOME}"
|
38
|
+
Dir.mkdir(KEYP_HOME, 0700)
|
39
|
+
else
|
40
|
+
Puts "#{KEYP_HOME} already exists"
|
41
|
+
end
|
42
|
+
|
43
|
+
KEYP_HOME
|
44
|
+
=begin
|
45
|
+
if config_path == DEFAULT_STORE
|
46
|
+
# create the default file
|
47
|
+
|
48
|
+
f = File.open(DEFAULT_STORE,'w')
|
49
|
+
#f.puts("default:")
|
50
|
+
f.close
|
51
|
+
return {}
|
52
|
+
else
|
53
|
+
raise "Non default stores not yet implemented"
|
54
|
+
end
|
55
|
+
=end
|
56
|
+
end
|
57
|
+
|
58
|
+
# NOTE: No copy method
|
59
|
+
# No method to explicitly copy one bag to another
|
60
|
+
# Too prone to unwanted overwriting
|
61
|
+
# Instead, use create and pass in the other bag as a
|
62
|
+
# named parameter
|
63
|
+
|
64
|
+
# create a new bag persist
|
65
|
+
# TODO: check options for a has to write to the bag
|
66
|
+
def self.create(bag, options={})
|
67
|
+
# two root sections in a bag
|
68
|
+
# meta:
|
69
|
+
# meta will contain information for use by keyp about this particular bag
|
70
|
+
# such as encoding rules, case sensitivity
|
71
|
+
# data:
|
72
|
+
end
|
73
|
+
|
74
|
+
# Convenience method to create a new Bag
|
75
|
+
def self.bag(name='default', options = {})
|
76
|
+
keyper = Keyper.new(name, options)
|
77
|
+
keyper
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def self.add_to_env(bag)
|
82
|
+
# TODO: Add checking, upcase
|
83
|
+
bag.data.each do |key,value|
|
84
|
+
ENV[key] = value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.create_store(name, options = {} )
|
89
|
+
filepath = File.join(KEYP_HOME, name + DEFAULT_EXT)
|
90
|
+
|
91
|
+
time_now = Time.now.utc.iso8601
|
92
|
+
file_data = {}
|
93
|
+
file_data['meta'] = {
|
94
|
+
'name' => name,
|
95
|
+
'description' => '',
|
96
|
+
'created_at' => time_now,
|
97
|
+
'updated_at' => time_now
|
98
|
+
}
|
99
|
+
file_data['data'] = nil
|
100
|
+
unless File.exist? filepath
|
101
|
+
File.open(filepath, 'w') do |f|
|
102
|
+
f.write file_data.to_yaml
|
103
|
+
f.chmod(0600)
|
104
|
+
end
|
105
|
+
else
|
106
|
+
raise "Unable to create a new store at #{filepath}. One already exists."
|
107
|
+
end
|
108
|
+
bag name
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.parse_arg_string(arg_string, options={})
|
112
|
+
|
113
|
+
mode = options[:token_mode] || :default
|
114
|
+
|
115
|
+
case mode
|
116
|
+
when :unstable
|
117
|
+
arg_re = /(?<key>\w*)\=(?<value>.*)/
|
118
|
+
nvp = arg_re.match(arg_string)
|
119
|
+
when :default
|
120
|
+
parsed = arg_string.partition('=')
|
121
|
+
if parsed[1] == '='
|
122
|
+
nvp = { key: parsed[0], value: parsed[2] }
|
123
|
+
else
|
124
|
+
# Consider: adding token_mode to error message
|
125
|
+
raise "unable to create a key/value pair from #{arg_string}"
|
126
|
+
end
|
127
|
+
else
|
128
|
+
raise "parse_arg_string unsupported mode #{mode}"
|
129
|
+
end
|
130
|
+
nvp
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# ----------------------------------------------
|
135
|
+
|
136
|
+
# Some inspiration:
|
137
|
+
# http://stackoverflow.com/questions/2680523/dry-ruby-initialization-with-hash-argument
|
138
|
+
#
|
139
|
+
# TODO: add handling so that keys (hierarchical keys too) are accessed as members instead of
|
140
|
+
# hash access
|
141
|
+
#
|
142
|
+
# TODO: move to own file, rename to "Bag"
|
143
|
+
# TODO: i18n error messages
|
144
|
+
#
|
145
|
+
class Keyper
|
146
|
+
|
147
|
+
attr_reader :keypdir, :dirty
|
148
|
+
attr_accessor :name, :data, :file_hash
|
149
|
+
|
150
|
+
def config_path
|
151
|
+
File.join(@keypdir, @name)
|
152
|
+
end
|
153
|
+
|
154
|
+
# We expect
|
155
|
+
# I'm not happy with how creating instance variables works. There must be a cleaner way
|
156
|
+
def initialize(name, options = {})
|
157
|
+
@name = name
|
158
|
+
options.each do |k,v|
|
159
|
+
puts "processing options #{k} = #{v}"
|
160
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
161
|
+
end
|
162
|
+
# set attributes not set by params
|
163
|
+
|
164
|
+
@keypdir ||= Keyp::KEYP_HOME
|
165
|
+
@read_only ||= false
|
166
|
+
@ext ||= '.yml'
|
167
|
+
@keypfile = config_path+@ext
|
168
|
+
# load our resource
|
169
|
+
|
170
|
+
# load config file into hashes
|
171
|
+
# not the most efficient thing, but simpler and safe enough for now
|
172
|
+
|
173
|
+
unless File.exist? @keypfile
|
174
|
+
puts "Keuper.initialize, create_store #{@keypfile}"
|
175
|
+
Keyp::create_store(@keypfile)
|
176
|
+
end
|
177
|
+
file_data = load(@keypfile)
|
178
|
+
|
179
|
+
@meta = file_data[:meta]
|
180
|
+
@data = file_data[:data]|| {}
|
181
|
+
@file_hash = file_data[:file_hash]
|
182
|
+
@dirty = false
|
183
|
+
end
|
184
|
+
|
185
|
+
def [](key)
|
186
|
+
@data[key]
|
187
|
+
end
|
188
|
+
|
189
|
+
def []=(key, value)
|
190
|
+
set_prop(key, value)
|
191
|
+
end
|
192
|
+
|
193
|
+
def set_prop(key, value)
|
194
|
+
unless @read_only
|
195
|
+
# TODO: check if data has been modified
|
196
|
+
# maybe there is a way hash tells us its been modified. If not then
|
197
|
+
# just check if key,val is already in hash and matches
|
198
|
+
@data[key] = value
|
199
|
+
@dirty = true
|
200
|
+
else
|
201
|
+
raise "Keyper #{@name} is read only"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def delete(key)
|
206
|
+
unless @read_only
|
207
|
+
if @data.key? key
|
208
|
+
@dirty = true
|
209
|
+
end
|
210
|
+
val = @data.delete(key)
|
211
|
+
else
|
212
|
+
raise "Bag #{@name} is read only"
|
213
|
+
end
|
214
|
+
val
|
215
|
+
end
|
216
|
+
|
217
|
+
def empty?
|
218
|
+
@data.empty?
|
219
|
+
end
|
220
|
+
|
221
|
+
# Give full path, attempt to load
|
222
|
+
# sticking with YAML format for now
|
223
|
+
# May add new file format later in which case we'll
|
224
|
+
# TODO: consider changing to class method
|
225
|
+
def load (config_path)
|
226
|
+
#config_data = {}
|
227
|
+
# Get extension
|
228
|
+
file_ext = File.extname(config_path)
|
229
|
+
|
230
|
+
# check
|
231
|
+
# only YAML supported for initial version. Will consider adapters to
|
232
|
+
# abstract the persistence layer
|
233
|
+
|
234
|
+
|
235
|
+
# TODO: make this hardcoded case a hash of helpers
|
236
|
+
# TODO: Add two sections: Meta and data, then return as hash
|
237
|
+
|
238
|
+
if file_ext.downcase == '.yml'
|
239
|
+
|
240
|
+
# Either we are arbitrarily creating directories when
|
241
|
+
# given a path for a file that doesn't exist
|
242
|
+
# or we have special behavior for the default dir
|
243
|
+
# or we just fault and let the caller deal with it
|
244
|
+
unless File.exist? config_path
|
245
|
+
raise "Keyp config file not found: #{config_path}"
|
246
|
+
end
|
247
|
+
|
248
|
+
file_data = YAML.load_file(config_path)
|
249
|
+
|
250
|
+
else
|
251
|
+
raise "Keyp version x only supports YAML for config files. You tried a #{file_ext}"
|
252
|
+
end
|
253
|
+
{ meta: file_data['meta'], data: file_data['data']||{}, file_hash: file_data.hash }
|
254
|
+
end
|
255
|
+
|
256
|
+
# NOT thread safe
|
257
|
+
# TODO: make thread safe
|
258
|
+
def save
|
259
|
+
if @read_only
|
260
|
+
raise "This bag instance is read only"
|
261
|
+
end
|
262
|
+
if @dirty
|
263
|
+
# lock file
|
264
|
+
# read checksum
|
265
|
+
# if checksum matches our saved checksum then update file and release lock
|
266
|
+
# otherwise, raise
|
267
|
+
# TODO: implement merge from updated file and raise only if conflict
|
268
|
+
begin
|
269
|
+
file_data = { 'meta' => @meta, 'data' => @data }
|
270
|
+
|
271
|
+
if File.exist? @keypfile
|
272
|
+
read_file_data = load(@keypfile)
|
273
|
+
unless @file_hash == read_file_data[:file_hash]
|
274
|
+
raise "Will not write to #{@keypfile}\nHashes differ. Expected hash =#{@file_hash}\n" +
|
275
|
+
"found hash #{read_file_data[:file_hash]}"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
File.open(@keypfile, 'w') do |f|
|
279
|
+
f.write file_data.to_yaml
|
280
|
+
end
|
281
|
+
@dirty = false
|
282
|
+
rescue
|
283
|
+
# TODO: log error
|
284
|
+
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
# Hmm, do we really need this or can we suffice with the meta hash in the bag
|
292
|
+
class Meta
|
293
|
+
attr_accessor :name, :description, :created, :updated
|
294
|
+
def initialize(hash, options = {})
|
295
|
+
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
data/spec/keyp_spec.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Keyp do
|
4
|
+
it 'should return correct version string' do
|
5
|
+
#Keyp.version_string.should == "Keyp version #{Keyp::VERSION}"
|
6
|
+
Keyp::VERSION.should == '0.0.1'
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should return correct default keyp dir' do
|
10
|
+
# DEFAULT_KEYP_DIRNAME = ENV['KEYP_DIRNAME'] || '.keyp'
|
11
|
+
Keyp::KEYP_HOME.should == File.join(ENV['HOME'], '.keyp')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should specify default store' do
|
15
|
+
Keyp::DEFAULT_STORE.should == 'default'
|
16
|
+
end
|
17
|
+
it 'should specify default store file' do
|
18
|
+
Keyp::DEFAULT_STORE_FILE.should == 'default.yml'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be able to override default keyp dir'
|
22
|
+
|
23
|
+
it 'should return default bag' do
|
24
|
+
keyper = Keyp::bag
|
25
|
+
keyper.bag.should == 'default'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should return a key with data member'
|
29
|
+
it 'should return a key acting as a hash'
|
30
|
+
it 'should allow assigning a key if not read only'
|
31
|
+
it 'should not allow assigning a key if read only'
|
32
|
+
it 'should set the dirty flag if a new key is created'
|
33
|
+
it 'should set the dirty flag if a key is given a new value'
|
34
|
+
it 'should not set the dirty flag if a key is assigned a value equal to its existing value'
|
35
|
+
|
36
|
+
# Braindump of tests
|
37
|
+
# Should show no keys if empty bag
|
38
|
+
# should create default.yml if .keyp already exists but default.yml does not when run from command line
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: keyp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Baldwin
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: gli
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.14'
|
69
|
+
description: https://github.com/johnbaldwin/keyp-ruby
|
70
|
+
email:
|
71
|
+
- jlbaldwin@gmail.com
|
72
|
+
executables:
|
73
|
+
- keyp
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/keyp
|
83
|
+
- keyp.gemspec
|
84
|
+
- lib/keyp.rb
|
85
|
+
- lib/keyp/cli.rb
|
86
|
+
- lib/keyp/version.rb
|
87
|
+
- spec/keyp_spec.rb
|
88
|
+
- spec/spec_helper.rb
|
89
|
+
homepage: https://github.com/johnbaldwin/keyp-ruby
|
90
|
+
licenses:
|
91
|
+
- Apache v2
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.2.1
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: https://github.com/johnbaldwin/keyp-ruby
|
113
|
+
test_files:
|
114
|
+
- spec/keyp_spec.rb
|
115
|
+
- spec/spec_helper.rb
|