sfn 0.0.1 → 0.3.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 +4 -4
- data/CHANGELOG.md +107 -0
- data/LICENSE +13 -0
- data/README.md +142 -61
- data/bin/sfn +43 -0
- data/lib/chef/knife/knife_plugin_seed.rb +117 -0
- data/lib/sfn.rb +17 -0
- data/lib/sfn/cache.rb +385 -0
- data/lib/sfn/command.rb +45 -0
- data/lib/sfn/command/create.rb +87 -0
- data/lib/sfn/command/describe.rb +87 -0
- data/lib/sfn/command/destroy.rb +74 -0
- data/lib/sfn/command/events.rb +98 -0
- data/lib/sfn/command/export.rb +103 -0
- data/lib/sfn/command/import.rb +117 -0
- data/lib/sfn/command/inspect.rb +160 -0
- data/lib/sfn/command/list.rb +59 -0
- data/lib/sfn/command/promote.rb +17 -0
- data/lib/sfn/command/update.rb +95 -0
- data/lib/sfn/command/validate.rb +34 -0
- data/lib/sfn/command_module.rb +9 -0
- data/lib/sfn/command_module/base.rb +150 -0
- data/lib/sfn/command_module/stack.rb +166 -0
- data/lib/sfn/command_module/template.rb +147 -0
- data/lib/sfn/config.rb +106 -0
- data/lib/sfn/config/create.rb +35 -0
- data/lib/sfn/config/describe.rb +19 -0
- data/lib/sfn/config/destroy.rb +9 -0
- data/lib/sfn/config/events.rb +25 -0
- data/lib/sfn/config/export.rb +29 -0
- data/lib/sfn/config/import.rb +24 -0
- data/lib/sfn/config/inspect.rb +37 -0
- data/lib/sfn/config/list.rb +25 -0
- data/lib/sfn/config/promote.rb +23 -0
- data/lib/sfn/config/update.rb +20 -0
- data/lib/sfn/config/validate.rb +49 -0
- data/lib/sfn/monkey_patch.rb +8 -0
- data/lib/sfn/monkey_patch/stack.rb +200 -0
- data/lib/sfn/provider.rb +224 -0
- data/lib/sfn/utils.rb +23 -0
- data/lib/sfn/utils/debug.rb +31 -0
- data/lib/sfn/utils/json.rb +37 -0
- data/lib/sfn/utils/object_storage.rb +28 -0
- data/lib/sfn/utils/output.rb +79 -0
- data/lib/sfn/utils/path_selector.rb +99 -0
- data/lib/sfn/utils/ssher.rb +29 -0
- data/lib/sfn/utils/stack_exporter.rb +275 -0
- data/lib/sfn/utils/stack_parameter_scrubber.rb +37 -0
- data/lib/sfn/utils/stack_parameter_validator.rb +124 -0
- data/lib/sfn/version.rb +4 -0
- data/sfn.gemspec +19 -0
- metadata +110 -4
data/bin/sfn
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bogo-cli'
|
4
|
+
require 'sfn'
|
5
|
+
|
6
|
+
Bogo::Cli::Setup.define do
|
7
|
+
|
8
|
+
on :v, :version, 'Print version ' do
|
9
|
+
puts "sfn - SparkleFormation CLI - [Version: #{Sfn::VERSION}]"
|
10
|
+
exit
|
11
|
+
end
|
12
|
+
|
13
|
+
Sfn::Config.constants.map do |konst|
|
14
|
+
const = Sfn::Config.const_get(konst)
|
15
|
+
if(const.is_a?(Class) && const.ancestors.include?(Bogo::Config))
|
16
|
+
const
|
17
|
+
end
|
18
|
+
end.compact.sort_by(&:to_s).each do |klass|
|
19
|
+
|
20
|
+
klass_name = klass.name.split('::').last.downcase
|
21
|
+
|
22
|
+
command klass_name do
|
23
|
+
if(klass.const_defined?(:DESCRIPTION))
|
24
|
+
description klass.const_get(:DESCRIPTION)
|
25
|
+
end
|
26
|
+
|
27
|
+
Sfn::Config.options_for(klass).each do |name, info|
|
28
|
+
on_name = info[:boolean] ? info[:long] : "#{info[:long]}="
|
29
|
+
opts = Smash.new.tap do |o|
|
30
|
+
o[:default] = info[:default] if info[:default]
|
31
|
+
o[:as] = Array if info[:multiple]
|
32
|
+
end
|
33
|
+
on info[:short], on_name, info[:description], :default => info[:default]
|
34
|
+
end
|
35
|
+
|
36
|
+
run do |opts, args|
|
37
|
+
Bogo::Utility.constantize(klass.to_s.sub('Config', 'Command')).new(opts, args).execute!
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
begin
|
2
|
+
kcfn = Gem::Specification.find_by_name('knife-cloudformation')
|
3
|
+
$stderr.puts "[WARN]: Deprecated gem detected: #{kcfn.name} [V: #{kcfn.version}]"
|
4
|
+
$stderr.puts '[WARN]: Uninstall gem to prevent any conflicts (`gem uninstall knife-cloudformation -a`)'
|
5
|
+
rescue Gem::LoadError => e
|
6
|
+
# ignore
|
7
|
+
end
|
8
|
+
|
9
|
+
unless(defined?(Chef::Knife::CloudformationCreate))
|
10
|
+
|
11
|
+
require 'sfn'
|
12
|
+
require 'bogo'
|
13
|
+
|
14
|
+
Chef::Config[:knife][:cloudformation] = {
|
15
|
+
:options => {},
|
16
|
+
:create => {},
|
17
|
+
:update => {}
|
18
|
+
}
|
19
|
+
Chef::Config[:knife][:sparkleformation] = Chef::Config[:knife][:cloudformation]
|
20
|
+
|
21
|
+
VALID_PREFIX = ['cloudformation', 'sparkleformation']
|
22
|
+
|
23
|
+
|
24
|
+
Sfn::Config.constants.map do |konst|
|
25
|
+
const = Sfn::Config.const_get(konst)
|
26
|
+
if(const.is_a?(Class) && const.ancestors.include?(Bogo::Config))
|
27
|
+
const
|
28
|
+
end
|
29
|
+
end.compact.sort_by(&:to_s).each do |klass|
|
30
|
+
|
31
|
+
VALID_PREFIX.each do |prefix|
|
32
|
+
|
33
|
+
klass_name = klass.name.split('::').last
|
34
|
+
command_class = "#{prefix.capitalize}#{klass_name}"
|
35
|
+
|
36
|
+
knife_klass = Class.new(Chef::Knife)
|
37
|
+
knife_klass.class_eval do
|
38
|
+
|
39
|
+
include Bogo::AnimalStrings
|
40
|
+
|
41
|
+
# Stub in names so knife will detect
|
42
|
+
def self.name
|
43
|
+
@name
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.sfn_class
|
47
|
+
@sfn_class
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
self.class.name
|
52
|
+
end
|
53
|
+
|
54
|
+
# Properly load in configurations and execute command
|
55
|
+
def run
|
56
|
+
knife = Chef::Config[:knife]
|
57
|
+
if(knife.respond_to?(:hash_dup))
|
58
|
+
knife = knife.hash_dup
|
59
|
+
end
|
60
|
+
base = knife.to_smash
|
61
|
+
keys = VALID_PREFIX.dup
|
62
|
+
cmd_config = keys.unshift(keys.delete(snake(self.class.name.split('::').last).to_s.split('_').first)).map do |k|
|
63
|
+
base[k]
|
64
|
+
end.compact.first || {}
|
65
|
+
cmd_config = cmd_config.to_smash
|
66
|
+
reconfig = config.find_all do |k,v|
|
67
|
+
!v.nil?
|
68
|
+
end
|
69
|
+
# Split up options provided multiple arguments
|
70
|
+
reconfig.map! do |k,v|
|
71
|
+
if(v.is_a?(String) && v.include?(','))
|
72
|
+
v = v.split(',').map(&:strip)
|
73
|
+
end
|
74
|
+
[k,v]
|
75
|
+
end
|
76
|
+
config = Smash[reconfig]
|
77
|
+
cmd_config = cmd_config.deep_merge(config)
|
78
|
+
self.class.sfn_class.new(cmd_config, name_args).execute!
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
knife_klass.instance_variable_set(:@name, "Chef::Knife::#{command_class}")
|
83
|
+
knife_klass.instance_variable_set(
|
84
|
+
:@sfn_class,
|
85
|
+
Bogo::Utility.constantize(klass.name.sub('Config', 'Command'))
|
86
|
+
)
|
87
|
+
knife_klass.banner "knife #{prefix} #{Bogo::Utility.snake(klass_name)}"
|
88
|
+
|
89
|
+
Sfn::Config.options_for(klass).each do |name, info|
|
90
|
+
if(info[:boolean])
|
91
|
+
short = "-#{info[:short]}"
|
92
|
+
long = "--[no-]#{info[:long]}"
|
93
|
+
else
|
94
|
+
val = 'VALUE'
|
95
|
+
if(info[:multiple])
|
96
|
+
val << '[,VALUE]'
|
97
|
+
end
|
98
|
+
short = "-#{info[:short]} #{val}"
|
99
|
+
long = "--#{info[:long]} #{val}"
|
100
|
+
end
|
101
|
+
knife_klass.option(
|
102
|
+
name.to_sym, {
|
103
|
+
:short => short,
|
104
|
+
:long => long,
|
105
|
+
:boolean => info[:boolean],
|
106
|
+
:default => info[:default],
|
107
|
+
:description => info[:description]
|
108
|
+
}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
# Set the class as a proper constant
|
112
|
+
Chef::Knife.const_set(command_class, knife_klass)
|
113
|
+
# Force knife to pick up as a subcommand
|
114
|
+
Chef::Knife.inherited(knife_klass)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/sfn.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'sfn/version'
|
2
|
+
require 'miasma'
|
3
|
+
require 'bogo'
|
4
|
+
|
5
|
+
module Sfn
|
6
|
+
|
7
|
+
autoload :Provider, 'sfn/provider'
|
8
|
+
autoload :Cache, 'sfn/cache'
|
9
|
+
autoload :Config, 'sfn/config'
|
10
|
+
autoload :Export, 'sfn/export'
|
11
|
+
autoload :Utils, 'sfn/utils'
|
12
|
+
autoload :MonkeyPatch, 'sfn/monkey_patch'
|
13
|
+
autoload :Knife, 'sfn/knife'
|
14
|
+
autoload :Command, 'sfn/command'
|
15
|
+
autoload :CommandModule, 'sfn/command_module'
|
16
|
+
|
17
|
+
end
|
data/lib/sfn/cache.rb
ADDED
@@ -0,0 +1,385 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'thread'
|
3
|
+
require 'sfn'
|
4
|
+
|
5
|
+
module Sfn
|
6
|
+
# Data caching helper
|
7
|
+
class Cache
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Configure the caching approach to use
|
12
|
+
#
|
13
|
+
# @param type [Symbol] :redis or :local
|
14
|
+
# @param args [Hash] redis connection arguments if used
|
15
|
+
def configure(type, args={})
|
16
|
+
type = type.to_sym
|
17
|
+
case type
|
18
|
+
when :redis
|
19
|
+
begin
|
20
|
+
require 'redis-objects'
|
21
|
+
rescue LoadError
|
22
|
+
$stderr.puts 'The `redis-objects` gem is required for Cache support!'
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
@_pid = Process.pid
|
26
|
+
Redis::Objects.redis = Redis.new(args)
|
27
|
+
when :local
|
28
|
+
else
|
29
|
+
raise TypeError.new("Unsupported caching type: #{type}")
|
30
|
+
end
|
31
|
+
enable(type)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Set enabled caching type
|
35
|
+
#
|
36
|
+
# @param type [Symbol]
|
37
|
+
# @return [Symbol]
|
38
|
+
def enable(type)
|
39
|
+
@type = type.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Symbol] type of caching enabled
|
43
|
+
def type
|
44
|
+
@type || :local
|
45
|
+
end
|
46
|
+
|
47
|
+
# Set/get time limit on data type
|
48
|
+
#
|
49
|
+
# @param kind [String, Symbol] data type
|
50
|
+
# @param seconds [Integer]
|
51
|
+
# return [Integer] seconds
|
52
|
+
def apply_limit(kind, seconds=nil)
|
53
|
+
@apply_limit ||= {}
|
54
|
+
if(seconds)
|
55
|
+
@apply_limit[kind.to_sym] = seconds.to_i
|
56
|
+
end
|
57
|
+
@apply_limit[kind.to_sym].to_i
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Hash] default limits
|
61
|
+
def default_limits
|
62
|
+
(@apply_limit || {}).dup
|
63
|
+
end
|
64
|
+
|
65
|
+
# Ping the redis connection and reconnect if dead
|
66
|
+
def redis_ping!
|
67
|
+
if((@_pid && @_pid != Process.pid) || !Redis::Objects.redis.connected?)
|
68
|
+
Redis::Objects.redis.client.reconnect
|
69
|
+
@_pid = Process.pid
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String] custom key for this cache
|
76
|
+
attr_reader :key
|
77
|
+
|
78
|
+
# Create new instance
|
79
|
+
#
|
80
|
+
# @param key [String, Array]
|
81
|
+
def initialize(key)
|
82
|
+
if(key.respond_to?(:sort))
|
83
|
+
key = key.flatten if key.respond_to?(:flatten)
|
84
|
+
key = key.map(&:to_s).sort
|
85
|
+
end
|
86
|
+
@key = Digest::SHA256.hexdigest(key.to_s)
|
87
|
+
@apply_limit = self.class.default_limits
|
88
|
+
end
|
89
|
+
|
90
|
+
# Initialize a new data type
|
91
|
+
#
|
92
|
+
# @param name [Symbol] name of data
|
93
|
+
# @param kind [Symbol] data type
|
94
|
+
# @param args [Hash] options for data type
|
95
|
+
def init(name, kind, args={})
|
96
|
+
get_storage(self.class.type, kind, name, args)
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Hash] data registry
|
101
|
+
def registry
|
102
|
+
get_storage(self.class.type, :hash, "registry_#{key}")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Clear data
|
106
|
+
#
|
107
|
+
# @param args [Symbol] list of names to delete
|
108
|
+
# @return [TrueClass]
|
109
|
+
# @note clears all data if no names provided
|
110
|
+
def clear!(*args)
|
111
|
+
internal_lock do
|
112
|
+
args = registry.keys if args.empty?
|
113
|
+
args.each do |key|
|
114
|
+
value = self[key]
|
115
|
+
if(value.respond_to?(:clear))
|
116
|
+
value.clear
|
117
|
+
elsif(value.respond_to?(:value))
|
118
|
+
value.value = nil
|
119
|
+
end
|
120
|
+
registry.delete(key)
|
121
|
+
end
|
122
|
+
yield if block_given?
|
123
|
+
end
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
# Fetch item from storage
|
128
|
+
#
|
129
|
+
# @param store_type [Symbol]
|
130
|
+
# @param data_type [Symbol]
|
131
|
+
# @param name [Symbol] name of data
|
132
|
+
# @param args [Hash] options for underlying storage
|
133
|
+
# @return [Object]
|
134
|
+
def get_storage(store_type, data_type, name, args={})
|
135
|
+
full_name = "#{key}_#{name}"
|
136
|
+
result = nil
|
137
|
+
case store_type.to_sym
|
138
|
+
when :redis
|
139
|
+
result = get_redis_storage(data_type, full_name.to_s, args)
|
140
|
+
when :local
|
141
|
+
@_local_cache ||= {}
|
142
|
+
unless(@_local_cache[full_name.to_s])
|
143
|
+
@_local_cache[full_name.to_s] = get_local_storage(data_type, full_name.to_s, args)
|
144
|
+
end
|
145
|
+
result = @_local_cache[full_name.to_s]
|
146
|
+
else
|
147
|
+
raise TypeError.new("Unsupported caching storage type encountered: #{store_type}")
|
148
|
+
end
|
149
|
+
unless(full_name == "#{key}_registry_#{key}")
|
150
|
+
registry[name.to_s] = data_type
|
151
|
+
end
|
152
|
+
result
|
153
|
+
end
|
154
|
+
|
155
|
+
# Fetch item from redis storage
|
156
|
+
#
|
157
|
+
# @param data_type [Symbol]
|
158
|
+
# @param full_name [Symbol]
|
159
|
+
# @param args [Hash]
|
160
|
+
# @return [Object]
|
161
|
+
def get_redis_storage(data_type, full_name, args={})
|
162
|
+
self.class.redis_ping!
|
163
|
+
case data_type.to_sym
|
164
|
+
when :array
|
165
|
+
Redis::List.new(full_name, {:marshal => true}.merge(args))
|
166
|
+
when :hash
|
167
|
+
Redis::HashKey.new(full_name)
|
168
|
+
when :value
|
169
|
+
Redis::Value.new(full_name, {:marshal => true}.merge(args))
|
170
|
+
when :lock
|
171
|
+
Redis::Lock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
|
172
|
+
when :stamped
|
173
|
+
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_redis_storage(:value, full_name), self)
|
174
|
+
else
|
175
|
+
raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Fetch item from local storage
|
180
|
+
#
|
181
|
+
# @param data_type [Symbol]
|
182
|
+
# @param full_name [Symbol]
|
183
|
+
# @param args [Hash]
|
184
|
+
# @return [Object]
|
185
|
+
# @todo make proper singleton for local storage
|
186
|
+
def get_local_storage(data_type, full_name, args={})
|
187
|
+
@storage ||= {}
|
188
|
+
@storage[full_name] ||= case data_type.to_sym
|
189
|
+
when :array
|
190
|
+
[]
|
191
|
+
when :hash
|
192
|
+
{}
|
193
|
+
when :value
|
194
|
+
LocalValue.new
|
195
|
+
when :lock
|
196
|
+
LocalLock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
|
197
|
+
when :stamped
|
198
|
+
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_local_storage(:value, full_name), self)
|
199
|
+
else
|
200
|
+
raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Execute block within internal lock
|
205
|
+
#
|
206
|
+
# @return [Object] result of yield
|
207
|
+
# @note for internal use
|
208
|
+
def internal_lock
|
209
|
+
get_storage(self.class.type, :lock, :internal_access, :timeout => 20, :expiration => 120).lock do
|
210
|
+
yield
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Fetch data
|
215
|
+
#
|
216
|
+
# @param name [String, Symbol]
|
217
|
+
# @return [Object, NilClass]
|
218
|
+
def [](name)
|
219
|
+
if(kind = registry[name.to_s])
|
220
|
+
get_storage(self.class.type, kind, name)
|
221
|
+
else
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Set data
|
227
|
+
#
|
228
|
+
# @param key [Object]
|
229
|
+
# @param val [Object]
|
230
|
+
# @note this will never work, thus you should never use it
|
231
|
+
def []=(key, val)
|
232
|
+
raise 'Setting backend data is not allowed'
|
233
|
+
end
|
234
|
+
|
235
|
+
# Check if cache time has expired
|
236
|
+
#
|
237
|
+
# @param key [String, Symbol] value key
|
238
|
+
# @param stamp [Time, Integer]
|
239
|
+
# @return [TrueClass, FalseClass]
|
240
|
+
def time_check_allow?(key, stamp)
|
241
|
+
Time.now.to_i - stamp.to_i > apply_limit(key)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Apply time limit for data type
|
245
|
+
#
|
246
|
+
# @param kind [String, Symbol] data type
|
247
|
+
# @param seconds [Integer]
|
248
|
+
# return [Integer]
|
249
|
+
def apply_limit(kind, seconds=nil)
|
250
|
+
@apply_limit ||= {}
|
251
|
+
if(seconds)
|
252
|
+
@apply_limit[kind.to_sym] = seconds.to_i
|
253
|
+
end
|
254
|
+
@apply_limit[kind.to_sym].to_i
|
255
|
+
end
|
256
|
+
|
257
|
+
# Perform action within lock
|
258
|
+
#
|
259
|
+
# @param lock_name [String, Symbol] name of lock
|
260
|
+
# @param raise_on_locked [TrueClass, FalseClass] raise execption if lock wait times out
|
261
|
+
# @return [Object] result of yield
|
262
|
+
def locked_action(lock_name, raise_on_locked=false)
|
263
|
+
begin
|
264
|
+
self[lock_name].lock do
|
265
|
+
yield
|
266
|
+
end
|
267
|
+
rescue => e
|
268
|
+
if(e.class.to_s == 'Redis::Lock::LockTimeout')
|
269
|
+
raise if raise_on_locked
|
270
|
+
else
|
271
|
+
raise
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# Simple value for memory cache
|
277
|
+
class LocalValue
|
278
|
+
# @return [Object] value
|
279
|
+
attr_accessor :value
|
280
|
+
def initialize(*args)
|
281
|
+
@value = nil
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Simple lock for memory cache
|
286
|
+
class LocalLock
|
287
|
+
|
288
|
+
# @return [Symbol] key name
|
289
|
+
attr_reader :_key
|
290
|
+
# @return [Numeric] timeout
|
291
|
+
attr_reader :_timeout
|
292
|
+
# @return [Mutex] underlying lock
|
293
|
+
attr_reader :_lock
|
294
|
+
|
295
|
+
# Create new instance
|
296
|
+
#
|
297
|
+
# @param name [Symbol] name of lock
|
298
|
+
# @param args [Hash]
|
299
|
+
# @option args [Numeric] :timeout
|
300
|
+
def initialize(name, args={})
|
301
|
+
@_key = name
|
302
|
+
@_timeout = args.fetch(:timeout, -1).to_f
|
303
|
+
@_lock = Mutex.new
|
304
|
+
end
|
305
|
+
|
306
|
+
# Aquire lock and yield
|
307
|
+
#
|
308
|
+
# @yield block to execute within lock
|
309
|
+
# @return [Object] result of yield
|
310
|
+
def lock
|
311
|
+
locked = false
|
312
|
+
attempt_start = Time.now.to_f
|
313
|
+
while(!locked && (_timeout < 0 || Time.now.to_f - attempt_start < _timeout))
|
314
|
+
locked = _lock.try_lock
|
315
|
+
end
|
316
|
+
if(locked)
|
317
|
+
begin
|
318
|
+
yield
|
319
|
+
ensure
|
320
|
+
_lock.unlock if _lock.locked?
|
321
|
+
end
|
322
|
+
else
|
323
|
+
raise Redis::Lock::LockTimeout.new "Timeout on lock #{_key} exceeded #{_timeout} sec"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Clear the lock
|
328
|
+
#
|
329
|
+
# @note this is a noop
|
330
|
+
def clear
|
331
|
+
# noop
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Wrapper to auto stamp values
|
336
|
+
class Stamped
|
337
|
+
|
338
|
+
# Create new instance
|
339
|
+
#
|
340
|
+
# @param name [String, Symbol]
|
341
|
+
# @param base [Redis::Value, LocalValue]
|
342
|
+
# @param cache [Cache]
|
343
|
+
def initialize(name, base, cache)
|
344
|
+
@name = name.to_sym
|
345
|
+
@base = base
|
346
|
+
@cache = cache
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [Object] value stored
|
350
|
+
def value
|
351
|
+
@base.value[:value] if set?
|
352
|
+
end
|
353
|
+
|
354
|
+
# Store value and update timestamp
|
355
|
+
#
|
356
|
+
# @param v [Object] value
|
357
|
+
# @return [Object]
|
358
|
+
def value=(v)
|
359
|
+
@base.value = {:stamp => Time.now.to_f, :value => v}
|
360
|
+
v
|
361
|
+
end
|
362
|
+
|
363
|
+
# @return [TrueClass, FalseClass] is value set
|
364
|
+
def set?
|
365
|
+
@base.value.is_a?(Hash)
|
366
|
+
end
|
367
|
+
|
368
|
+
# @return [Float] timestamp of last set (or 0.0 if unset)
|
369
|
+
def stamp
|
370
|
+
set? ? @base.value[:stamp] : 0.0
|
371
|
+
end
|
372
|
+
|
373
|
+
# Force a timestamp update
|
374
|
+
def restamp!
|
375
|
+
self.value = value
|
376
|
+
end
|
377
|
+
|
378
|
+
# @return [TrueClass, FalseClass] update is allowed based on stamp and limits
|
379
|
+
def update_allowed?
|
380
|
+
!set? || @cache.time_check_allow?(@name, @base.value[:stamp])
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
end
|