knife-cloudformation 0.1.22 → 0.2.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 +7 -0
- data/CHANGELOG.md +6 -0
- data/README.md +56 -2
- data/knife-cloudformation.gemspec +4 -7
- data/lib/chef/knife/cloudformation_create.rb +105 -245
- data/lib/chef/knife/cloudformation_describe.rb +50 -26
- data/lib/chef/knife/cloudformation_destroy.rb +17 -18
- data/lib/chef/knife/cloudformation_events.rb +48 -14
- data/lib/chef/knife/cloudformation_export.rb +117 -34
- data/lib/chef/knife/cloudformation_import.rb +124 -18
- data/lib/chef/knife/cloudformation_inspect.rb +159 -71
- data/lib/chef/knife/cloudformation_list.rb +20 -24
- data/lib/chef/knife/cloudformation_promote.rb +40 -0
- data/lib/chef/knife/cloudformation_update.rb +132 -15
- data/lib/chef/knife/cloudformation_validate.rb +35 -0
- data/lib/knife-cloudformation.rb +28 -0
- data/lib/knife-cloudformation/cache.rb +213 -35
- data/lib/knife-cloudformation/knife.rb +9 -0
- data/lib/knife-cloudformation/knife/base.rb +179 -0
- data/lib/knife-cloudformation/knife/stack.rb +94 -0
- data/lib/knife-cloudformation/knife/template.rb +174 -0
- data/lib/knife-cloudformation/monkey_patch.rb +8 -0
- data/lib/knife-cloudformation/monkey_patch/stack.rb +195 -0
- data/lib/knife-cloudformation/provider.rb +225 -0
- data/lib/knife-cloudformation/utils.rb +18 -98
- data/lib/knife-cloudformation/utils/animal_strings.rb +28 -0
- data/lib/knife-cloudformation/utils/debug.rb +31 -0
- data/lib/knife-cloudformation/utils/json.rb +64 -0
- data/lib/knife-cloudformation/utils/object_storage.rb +28 -0
- data/lib/knife-cloudformation/utils/output.rb +79 -0
- data/lib/knife-cloudformation/utils/path_selector.rb +99 -0
- data/lib/knife-cloudformation/utils/ssher.rb +29 -0
- data/lib/knife-cloudformation/utils/stack_exporter.rb +271 -0
- data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +35 -0
- data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +124 -0
- data/lib/knife-cloudformation/version.rb +2 -4
- metadata +47 -94
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -90
- data/knife-cloudformation-0.1.20.gem +0 -0
- data/lib/knife-cloudformation/aws_commons.rb +0 -267
- data/lib/knife-cloudformation/aws_commons/stack.rb +0 -435
- data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +0 -79
- data/lib/knife-cloudformation/cloudformation_base.rb +0 -168
- data/lib/knife-cloudformation/export.rb +0 -174
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationPromote < Knife
|
6
|
+
|
7
|
+
include KnifeCloudformation::Knife::Base
|
8
|
+
|
9
|
+
banner 'knife cloudformation promote NEW_STACK_NAME DESTINATION'
|
10
|
+
|
11
|
+
option(:accounts,
|
12
|
+
:long => '--accounts-file PATH',
|
13
|
+
:short => '-A PATH',
|
14
|
+
:description => 'JSON account file',
|
15
|
+
:proc => lambda{|v|
|
16
|
+
Chef::Config[:knife][:cloudformation][:promote_accounts] = JSON.load(File.read(v))
|
17
|
+
}
|
18
|
+
)
|
19
|
+
|
20
|
+
option(:storage_bucket,
|
21
|
+
:long => '--exports-bucket NAME',
|
22
|
+
:description => 'Bucket name containing the exports',
|
23
|
+
:proc => lambda{|v| Chef::Config[:knife][:cloudformation][:promote_exports_bucket] = v }
|
24
|
+
)
|
25
|
+
|
26
|
+
option(:storage_prefix,
|
27
|
+
:long => '--exports-prefix PREFIX',
|
28
|
+
:description => 'Prefix of stack key',
|
29
|
+
:proc => lambda{|v| Chef::Config[:knife][:cloudformation][:promote_exports_prefix] = v }
|
30
|
+
)
|
31
|
+
|
32
|
+
|
33
|
+
def _run
|
34
|
+
stack_name, destination = name_args
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,30 +1,147 @@
|
|
1
|
-
require 'knife-cloudformation
|
2
|
-
require File.join(File.dirname(__FILE__), 'cloudformation_create')
|
1
|
+
require 'knife-cloudformation'
|
3
2
|
|
4
3
|
class Chef
|
5
4
|
class Knife
|
6
5
|
class CloudformationUpdate < Knife
|
7
|
-
banner 'knife cloudformation update NAME'
|
8
6
|
|
9
|
-
include KnifeCloudformation::
|
10
|
-
include
|
7
|
+
include KnifeCloudformation::Knife::Base
|
8
|
+
include KnifeCloudformation::Knife::Template
|
9
|
+
include KnifeCloudformation::Knife::Stack
|
10
|
+
|
11
|
+
option(:file_path_prompt,
|
12
|
+
:long => '--[no-]file-path-prompt',
|
13
|
+
:description => 'Interactive prompt for template path discovery',
|
14
|
+
:boolean => true,
|
15
|
+
:default => false,
|
16
|
+
:proc => lambda {|val|
|
17
|
+
Chef::Config[:knife][:cloudformation][:file_path_prompt] = val
|
18
|
+
}
|
19
|
+
)
|
20
|
+
option(:apply_stacks,
|
21
|
+
:long => '--apply-stack NAME_OR_ID',
|
22
|
+
:description => 'Autofill parameters using existing stack outputs. Can be used multiple times',
|
23
|
+
:proc => lambda {|val|
|
24
|
+
Chef::Config[:knife][:cloudformation][:update] ||= Mash.new
|
25
|
+
Chef::Config[:knife][:cloudformation][:update][:apply_stacks] ||= []
|
26
|
+
Chef::Config[:knife][:cloudformation][:update][:apply_stacks].push(val).uniq!
|
27
|
+
}
|
28
|
+
)
|
11
29
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
30
|
+
banner 'knife cloudformation update NAME'
|
31
|
+
|
32
|
+
# Run the stack creation command
|
33
|
+
def _run
|
34
|
+
name = name_args.first
|
35
|
+
unless(name)
|
36
|
+
ui.fatal "Formation name must be specified!"
|
18
37
|
exit 1
|
19
38
|
end
|
39
|
+
|
40
|
+
stack = provider.stacks.get(name)
|
41
|
+
|
42
|
+
if(stack)
|
43
|
+
ui.info "#{ui.color('Cloud Formation:', :bold)} #{ui.color('update', :green)}"
|
44
|
+
file = load_template_file(:allow_missing)
|
45
|
+
stack_info = "#{ui.color('Name:', :bold)} #{name}"
|
46
|
+
|
47
|
+
if(Chef::Config[:knife][:cloudformation][:file])
|
48
|
+
stack_info << " #{ui.color('Path:', :bold)} #{Chef::Config[:knife][:cloudformation][:file]}"
|
49
|
+
else
|
50
|
+
stack_info << " #{ui.color('(no temlate update)', :yellow)}"
|
51
|
+
end
|
52
|
+
ui.info " -> #{stack_info}"
|
53
|
+
|
54
|
+
apply_stacks!(stack)
|
55
|
+
|
56
|
+
if(file)
|
57
|
+
redefault_stack_parameters(file, stack)
|
58
|
+
populate_parameters!(file)
|
59
|
+
file = translate_template(file)
|
60
|
+
stack.template = file
|
61
|
+
stack.parameters = Chef::Config[:knife][:cloudformation][:parameters]
|
62
|
+
else
|
63
|
+
stack_parameters_update!(stack)
|
64
|
+
end
|
65
|
+
|
66
|
+
stack.save
|
67
|
+
|
68
|
+
if(Chef::Config[:knife][:cloudformation][:poll])
|
69
|
+
poll_stack(stack.name)
|
70
|
+
provider.fetch_stacks
|
71
|
+
if(stack.success?)
|
72
|
+
ui.info "Stack update complete: #{ui.color('SUCCESS', :green)}"
|
73
|
+
provider.fetch_stacks
|
74
|
+
knife_output = Chef::Knife::CloudformationDescribe.new
|
75
|
+
knife_output.name_args.push(name)
|
76
|
+
knife_output.config[:outputs] = true
|
77
|
+
knife_output.run
|
78
|
+
else
|
79
|
+
ui.fatal "Update of stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
|
80
|
+
ui.info ""
|
81
|
+
knife_inspect = Chef::Knife::CloudformationInspect.new
|
82
|
+
knife_inspect.name_args.push(name)
|
83
|
+
knife_inspect.config[:instance_failure] = true
|
84
|
+
knife_inspect.run
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
else
|
88
|
+
ui.warn 'Stack state polling has been disabled.'
|
89
|
+
ui.info "Stack update initialized for #{ui.color(name, :green)}"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
ui.fatal "Failed to locate requested stack: #{ui.color(name, :red, :bold)}"
|
93
|
+
exit -1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Update default values for parameters in template with
|
98
|
+
# currently used parameters on the existing stack
|
99
|
+
#
|
100
|
+
# @param template [Hash] stack template
|
101
|
+
# @param stack [Fog::Orchestration::Stack]
|
102
|
+
# @return [Hash]
|
103
|
+
def redefault_stack_parameters(template, stack)
|
104
|
+
stack.parameters.each do |key, value|
|
105
|
+
if(template['Parameters'][key])
|
106
|
+
template['Parameters'][key]['Default'] = value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
template
|
20
110
|
end
|
21
111
|
|
22
|
-
|
23
|
-
|
112
|
+
# Apply any defined remote stacks
|
113
|
+
#
|
114
|
+
# @param stack [Miasma::Models::Orchestration::Stack]
|
115
|
+
# @return [Miasma::Models::Orchestration::Stack]
|
116
|
+
def apply_stacks!(stack)
|
117
|
+
remote_stacks = Chef::Config[:knife][:cloudformation].
|
118
|
+
fetch(:update, {}).fetch(:apply_stacks, [])
|
119
|
+
remote_stacks.each do |stack_name|
|
120
|
+
remote_stack = provider.stacks.get(stack_name)
|
121
|
+
if(remote_stack)
|
122
|
+
remote_stack.parameters.each do |key, value|
|
123
|
+
next if Chef::Config[:knife][:cloudformation][:stacks][:ignore_parameters].include?(key)
|
124
|
+
if(stack.parameters.has_key?(key))
|
125
|
+
stack.parameters[key] = value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
else
|
129
|
+
ui.error "Failed to apply requested stack. Unable to locate. (#{stack_name})"
|
130
|
+
exit 1
|
131
|
+
end
|
132
|
+
end
|
133
|
+
stack
|
24
134
|
end
|
25
135
|
|
26
|
-
|
27
|
-
|
136
|
+
# Update parameters within existing stack
|
137
|
+
#
|
138
|
+
# @param stack [Miasma::Models::Orchestration::Stack]
|
139
|
+
# @return [Miasma::Models::Orchestration::Stack]
|
140
|
+
def stack_parameters_update!(stack)
|
141
|
+
stack.parameters.each do |key, value|
|
142
|
+
answer = ui.ask_question("#{key.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')}: ", :default => value)
|
143
|
+
stack.parameters[key] = answer
|
144
|
+
end
|
28
145
|
end
|
29
146
|
|
30
147
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'sparkle_formation'
|
3
|
+
require 'knife-cloudformation'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
class Knife
|
7
|
+
# Cloudformation validate command
|
8
|
+
class CloudformationValidate < Knife
|
9
|
+
|
10
|
+
include KnifeCloudformation::Knife::Base
|
11
|
+
include KnifeCloudformation::Knife::Template
|
12
|
+
|
13
|
+
banner 'knife cloudformation validate'
|
14
|
+
|
15
|
+
def _run
|
16
|
+
file = load_template_file
|
17
|
+
ui.info "#{ui.color('Cloud Formation Validation: ', :bold)} #{Chef::Config[:knife][:cloudformation][:file].sub(Dir.pwd, '').sub(%r{^/}, '')}"
|
18
|
+
file = KnifeCloudformation::Utils::StackParameterScrubber.scrub!(file)
|
19
|
+
file = translate_template(file)
|
20
|
+
begin
|
21
|
+
result = provider.stacks.build(
|
22
|
+
:name => 'validation-stack',
|
23
|
+
:template => file
|
24
|
+
).validate
|
25
|
+
ui.info ui.color(' -> VALID', :bold, :green)
|
26
|
+
rescue => e
|
27
|
+
ui.info ui.color(' -> INVALID', :bold, :red)
|
28
|
+
ui.fatal e.message
|
29
|
+
failed = true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/knife-cloudformation.rb
CHANGED
@@ -1 +1,29 @@
|
|
1
1
|
require 'knife-cloudformation/version'
|
2
|
+
require 'miasma'
|
3
|
+
|
4
|
+
module KnifeCloudformation
|
5
|
+
|
6
|
+
autoload :Provider, 'knife-cloudformation/provider'
|
7
|
+
autoload :Cache, 'knife-cloudformation/cache'
|
8
|
+
autoload :Export, 'knife-cloudformation/export'
|
9
|
+
autoload :Utils, 'knife-cloudformation/utils'
|
10
|
+
autoload :MonkeyPatch, 'knife-cloudformation/monkey_patch'
|
11
|
+
autoload :Knife, 'knife-cloudformation/knife'
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class Chef
|
16
|
+
class Knife
|
17
|
+
autoload :CloudformationCreate, 'chef/knife/cloudformation_create'
|
18
|
+
autoload :CloudformationDescribe, 'chef/knife/cloudformation_describe'
|
19
|
+
autoload :CloudformationDestroy, 'chef/knife/cloudformation_destroy'
|
20
|
+
autoload :CloudformationEvents, 'chef/knife/cloudformation_events'
|
21
|
+
autoload :CloudformationExport, 'chef/knife/cloudformation_export'
|
22
|
+
autoload :CloudformationImport, 'chef/knife/cloudformation_import'
|
23
|
+
autoload :CloudformationInspect, 'chef/knife/cloudformation_inspect'
|
24
|
+
autoload :CloudformationList, 'chef/knife/cloudformation_list'
|
25
|
+
autoload :CloudformationPromote, 'chef/knife/cloudformation_promote'
|
26
|
+
autoload :CloudformationUpdate, 'chef/knife/cloudformation_update'
|
27
|
+
autoload :CloudformationValidate, 'chef/knife/cloudformation_validate'
|
28
|
+
end
|
29
|
+
end
|
@@ -1,15 +1,29 @@
|
|
1
1
|
require 'digest/sha2'
|
2
|
+
require 'thread'
|
3
|
+
require 'knife-cloudformation'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'redis-objects'
|
7
|
+
rescue LoadError
|
8
|
+
$stderr.puts 'The `redis-objects` gem is required for Cache support!'
|
9
|
+
raise
|
10
|
+
end
|
2
11
|
|
3
12
|
module KnifeCloudformation
|
13
|
+
# Data caching helper
|
4
14
|
class Cache
|
5
15
|
|
6
16
|
class << self
|
7
17
|
|
18
|
+
# Configure the caching approach to use
|
19
|
+
#
|
20
|
+
# @param type [Symbol] :redis or :local
|
21
|
+
# @param args [Hash] redis connection arguments if used
|
8
22
|
def configure(type, args={})
|
9
23
|
type = type.to_sym
|
10
24
|
case type
|
11
25
|
when :redis
|
12
|
-
|
26
|
+
@_pid = Process.pid
|
13
27
|
Redis::Objects.redis = Redis.new(args)
|
14
28
|
when :local
|
15
29
|
else
|
@@ -18,14 +32,24 @@ module KnifeCloudformation
|
|
18
32
|
enable(type)
|
19
33
|
end
|
20
34
|
|
35
|
+
# Set enabled caching type
|
36
|
+
#
|
37
|
+
# @param type [Symbol]
|
38
|
+
# @return [Symbol]
|
21
39
|
def enable(type)
|
22
40
|
@type = type.to_sym
|
23
41
|
end
|
24
42
|
|
43
|
+
# @return [Symbol] type of caching enabled
|
25
44
|
def type
|
26
45
|
@type || :local
|
27
46
|
end
|
28
47
|
|
48
|
+
# Set/get time limit on data type
|
49
|
+
#
|
50
|
+
# @param kind [String, Symbol] data type
|
51
|
+
# @param seconds [Integer]
|
52
|
+
# return [Integer] seconds
|
29
53
|
def apply_limit(kind, seconds=nil)
|
30
54
|
@apply_limit ||= {}
|
31
55
|
if(seconds)
|
@@ -34,62 +58,109 @@ module KnifeCloudformation
|
|
34
58
|
@apply_limit[kind.to_sym].to_i
|
35
59
|
end
|
36
60
|
|
61
|
+
# @return [Hash] default limits
|
37
62
|
def default_limits
|
38
63
|
(@apply_limit || {}).dup
|
39
64
|
end
|
40
65
|
|
66
|
+
# Ping the redis connection and reconnect if dead
|
67
|
+
def redis_ping!
|
68
|
+
if((@_pid && @_pid != Process.pid) || !Redis::Objects.redis.connected?)
|
69
|
+
Redis::Objects.redis.client.reconnect
|
70
|
+
@_pid = Process.pid
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
41
74
|
end
|
42
75
|
|
76
|
+
# @return [String] custom key for this cache
|
43
77
|
attr_reader :key
|
44
|
-
attr_reader :direct_store
|
45
78
|
|
79
|
+
# Create new instance
|
80
|
+
#
|
81
|
+
# @param key [String, Array]
|
46
82
|
def initialize(key)
|
47
83
|
if(key.respond_to?(:sort))
|
48
84
|
key = key.flatten if key.respond_to?(:flatten)
|
49
85
|
key = key.map(&:to_s).sort
|
50
86
|
end
|
51
87
|
@key = Digest::SHA256.hexdigest(key.to_s)
|
52
|
-
@direct_store = {}
|
53
88
|
@apply_limit = self.class.default_limits
|
54
89
|
end
|
55
90
|
|
91
|
+
# Initialize a new data type
|
92
|
+
#
|
93
|
+
# @param name [Symbol] name of data
|
94
|
+
# @param kind [Symbol] data type
|
95
|
+
# @param args [Hash] options for data type
|
56
96
|
def init(name, kind, args={})
|
57
|
-
|
58
|
-
unless(@direct_store[name])
|
59
|
-
full_name = [key, name.to_s].join('_')
|
60
|
-
@direct_store[name] = get_storage(self.class.type, kind, full_name, args)
|
61
|
-
end
|
97
|
+
get_storage(self.class.type, kind, name, args)
|
62
98
|
true
|
63
99
|
end
|
64
100
|
|
101
|
+
# @return [Hash] data registry
|
102
|
+
def registry
|
103
|
+
get_storage(self.class.type, :hash, "registry_#{key}")
|
104
|
+
end
|
105
|
+
|
106
|
+
# Clear data
|
107
|
+
#
|
108
|
+
# @param args [Symbol] list of names to delete
|
109
|
+
# @return [TrueClass]
|
110
|
+
# @note clears all data if no names provided
|
65
111
|
def clear!(*args)
|
66
112
|
internal_lock do
|
67
|
-
args =
|
113
|
+
args = registry.keys if args.empty?
|
68
114
|
args.each do |key|
|
69
|
-
value =
|
115
|
+
value = self[key]
|
70
116
|
if(value.respond_to?(:clear))
|
71
117
|
value.clear
|
72
118
|
elsif(value.respond_to?(:value))
|
73
119
|
value.value = nil
|
74
120
|
end
|
121
|
+
registry.delete(key)
|
75
122
|
end
|
76
123
|
yield if block_given?
|
77
124
|
end
|
78
125
|
true
|
79
126
|
end
|
80
127
|
|
81
|
-
|
128
|
+
# Fetch item from storage
|
129
|
+
#
|
130
|
+
# @param store_type [Symbol]
|
131
|
+
# @param data_type [Symbol]
|
132
|
+
# @param name [Symbol] name of data
|
133
|
+
# @param args [Hash] options for underlying storage
|
134
|
+
# @return [Object]
|
135
|
+
def get_storage(store_type, data_type, name, args={})
|
136
|
+
full_name = "#{key}_#{name}"
|
137
|
+
result = nil
|
82
138
|
case store_type.to_sym
|
83
139
|
when :redis
|
84
|
-
get_redis_storage(data_type, full_name, args)
|
140
|
+
result = get_redis_storage(data_type, full_name.to_s, args)
|
85
141
|
when :local
|
86
|
-
|
142
|
+
@_local_cache ||= {}
|
143
|
+
unless(@_local_cache[full_name.to_s])
|
144
|
+
@_local_cache[full_name.to_s] = get_local_storage(data_type, full_name.to_s, args)
|
145
|
+
end
|
146
|
+
result = @_local_cache[full_name.to_s]
|
87
147
|
else
|
88
148
|
raise TypeError.new("Unsupported caching storage type encountered: #{store_type}")
|
89
149
|
end
|
150
|
+
unless(full_name == "#{key}_registry_#{key}")
|
151
|
+
registry[name.to_s] = data_type
|
152
|
+
end
|
153
|
+
result
|
90
154
|
end
|
91
155
|
|
156
|
+
# Fetch item from redis storage
|
157
|
+
#
|
158
|
+
# @param data_type [Symbol]
|
159
|
+
# @param full_name [Symbol]
|
160
|
+
# @param args [Hash]
|
161
|
+
# @return [Object]
|
92
162
|
def get_redis_storage(data_type, full_name, args={})
|
163
|
+
self.class.redis_ping!
|
93
164
|
case data_type.to_sym
|
94
165
|
when :array
|
95
166
|
Redis::List.new(full_name, {:marshal => true}.merge(args))
|
@@ -98,7 +169,7 @@ module KnifeCloudformation
|
|
98
169
|
when :value
|
99
170
|
Redis::Value.new(full_name, {:marshal => true}.merge(args))
|
100
171
|
when :lock
|
101
|
-
Redis::Lock.new(full_name, {:expiration =>
|
172
|
+
Redis::Lock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
|
102
173
|
when :stamped
|
103
174
|
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_redis_storage(:value, full_name), self)
|
104
175
|
else
|
@@ -106,43 +177,76 @@ module KnifeCloudformation
|
|
106
177
|
end
|
107
178
|
end
|
108
179
|
|
180
|
+
# Fetch item from local storage
|
181
|
+
#
|
182
|
+
# @param data_type [Symbol]
|
183
|
+
# @param full_name [Symbol]
|
184
|
+
# @param args [Hash]
|
185
|
+
# @return [Object]
|
186
|
+
# @todo make proper singleton for local storage
|
109
187
|
def get_local_storage(data_type, full_name, args={})
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
188
|
+
@storage ||= {}
|
189
|
+
@storage[full_name] ||= case data_type.to_sym
|
190
|
+
when :array
|
191
|
+
[]
|
192
|
+
when :hash
|
193
|
+
{}
|
194
|
+
when :value
|
195
|
+
LocalValue.new
|
196
|
+
when :lock
|
197
|
+
LocalLock.new(full_name, {:expiration => 60, :timeout => 0.1}.merge(args))
|
198
|
+
when :stamped
|
199
|
+
Stamped.new(full_name.sub("#{key}_", '').to_sym, get_local_storage(:value, full_name), self)
|
200
|
+
else
|
201
|
+
raise TypeError.new("Unsupported caching data type encountered: #{data_type}")
|
202
|
+
end
|
124
203
|
end
|
125
204
|
|
205
|
+
# Execute block within internal lock
|
206
|
+
#
|
207
|
+
# @return [Object] result of yield
|
208
|
+
# @note for internal use
|
126
209
|
def internal_lock
|
127
|
-
get_storage(self.class.type, :lock, :internal_access, :timeout => 20).lock do
|
210
|
+
get_storage(self.class.type, :lock, :internal_access, :timeout => 20, :expiration => 120).lock do
|
128
211
|
yield
|
129
212
|
end
|
130
213
|
end
|
131
214
|
|
215
|
+
# Fetch data
|
216
|
+
#
|
217
|
+
# @param name [String, Symbol]
|
218
|
+
# @return [Object, NilClass]
|
132
219
|
def [](name)
|
133
|
-
|
134
|
-
|
220
|
+
if(kind = registry[name.to_s])
|
221
|
+
get_storage(self.class.type, kind, name)
|
222
|
+
else
|
223
|
+
nil
|
135
224
|
end
|
136
225
|
end
|
137
226
|
|
227
|
+
# Set data
|
228
|
+
#
|
229
|
+
# @param key [Object]
|
230
|
+
# @param val [Object]
|
231
|
+
# @note this will never work, thus you should never use it
|
138
232
|
def []=(key, val)
|
139
233
|
raise 'Setting backend data is not allowed'
|
140
234
|
end
|
141
235
|
|
236
|
+
# Check if cache time has expired
|
237
|
+
#
|
238
|
+
# @param key [String, Symbol] value key
|
239
|
+
# @param stamp [Time, Integer]
|
240
|
+
# @return [TrueClass, FalseClass]
|
142
241
|
def time_check_allow?(key, stamp)
|
143
242
|
Time.now.to_i - stamp.to_i > apply_limit(key)
|
144
243
|
end
|
145
244
|
|
245
|
+
# Apply time limit for data type
|
246
|
+
#
|
247
|
+
# @param kind [String, Symbol] data type
|
248
|
+
# @param seconds [Integer]
|
249
|
+
# return [Integer]
|
146
250
|
def apply_limit(kind, seconds=nil)
|
147
251
|
@apply_limit ||= {}
|
148
252
|
if(seconds)
|
@@ -151,50 +255,124 @@ module KnifeCloudformation
|
|
151
255
|
@apply_limit[kind.to_sym].to_i
|
152
256
|
end
|
153
257
|
|
258
|
+
# Perform action within lock
|
259
|
+
#
|
260
|
+
# @param lock_name [String, Symbol] name of lock
|
261
|
+
# @param raise_on_locked [TrueClass, FalseClass] raise execption if lock wait times out
|
262
|
+
# @return [Object] result of yield
|
263
|
+
def locked_action(lock_name, raise_on_locked=false)
|
264
|
+
begin
|
265
|
+
self[lock_name].lock do
|
266
|
+
yield
|
267
|
+
end
|
268
|
+
rescue Redis::Lock::LockTimeout
|
269
|
+
raise if raise_on_locked
|
270
|
+
end
|
271
|
+
end
|
154
272
|
|
273
|
+
# Simple value for memory cache
|
155
274
|
class LocalValue
|
275
|
+
# @return [Object] value
|
156
276
|
attr_accessor :value
|
157
277
|
def initialize(*args)
|
158
278
|
@value = nil
|
159
279
|
end
|
160
280
|
end
|
161
281
|
|
282
|
+
# Simple lock for memory cache
|
162
283
|
class LocalLock
|
163
|
-
|
284
|
+
|
285
|
+
# @return [Symbol] key name
|
286
|
+
attr_reader :_key
|
287
|
+
# @return [Numeric] timeout
|
288
|
+
attr_reader :_timeout
|
289
|
+
# @return [Mutex] underlying lock
|
290
|
+
attr_reader :_lock
|
291
|
+
|
292
|
+
# Create new instance
|
293
|
+
#
|
294
|
+
# @param name [Symbol] name of lock
|
295
|
+
# @param args [Hash]
|
296
|
+
# @option args [Numeric] :timeout
|
297
|
+
def initialize(name, args={})
|
298
|
+
@_key = name
|
299
|
+
@_timeout = args.fetch(:timeout, -1).to_f
|
300
|
+
@_lock = Mutex.new
|
164
301
|
end
|
165
302
|
|
303
|
+
# Aquire lock and yield
|
304
|
+
#
|
305
|
+
# @yield block to execute within lock
|
306
|
+
# @return [Object] result of yield
|
166
307
|
def lock
|
167
|
-
|
308
|
+
locked = false
|
309
|
+
attempt_start = Time.now.to_f
|
310
|
+
while(!locked && (_timeout < 0 || Time.now.to_f - attempt_start < _timeout))
|
311
|
+
locked = _lock.try_lock
|
312
|
+
end
|
313
|
+
if(locked)
|
314
|
+
begin
|
315
|
+
yield
|
316
|
+
ensure
|
317
|
+
_lock.unlock if _lock.locked?
|
318
|
+
end
|
319
|
+
else
|
320
|
+
raise Redis::Lock::LockTimeout.new "Timeout on lock #{_key} exceeded #{_timeout} sec"
|
321
|
+
end
|
168
322
|
end
|
169
323
|
|
324
|
+
# Clear the lock
|
325
|
+
#
|
326
|
+
# @note this is a noop
|
170
327
|
def clear
|
328
|
+
# noop
|
171
329
|
end
|
172
330
|
end
|
173
331
|
|
332
|
+
# Wrapper to auto stamp values
|
174
333
|
class Stamped
|
175
334
|
|
335
|
+
# Create new instance
|
336
|
+
#
|
337
|
+
# @param name [String, Symbol]
|
338
|
+
# @param base [Redis::Value, LocalValue]
|
339
|
+
# @param cache [Cache]
|
176
340
|
def initialize(name, base, cache)
|
177
341
|
@name = name.to_sym
|
178
342
|
@base = base
|
179
343
|
@cache = cache
|
180
344
|
end
|
181
345
|
|
346
|
+
# @return [Object] value stored
|
182
347
|
def value
|
183
348
|
@base.value[:value] if set?
|
184
349
|
end
|
185
350
|
|
351
|
+
# Store value and update timestamp
|
352
|
+
#
|
353
|
+
# @param v [Object] value
|
354
|
+
# @return [Object]
|
186
355
|
def value=(v)
|
187
|
-
@base.value = {:stamp => Time.now.
|
356
|
+
@base.value = {:stamp => Time.now.to_f, :value => v}
|
357
|
+
v
|
188
358
|
end
|
189
359
|
|
360
|
+
# @return [TrueClass, FalseClass] is value set
|
190
361
|
def set?
|
191
362
|
@base.value.is_a?(Hash)
|
192
363
|
end
|
193
364
|
|
365
|
+
# @return [Float] timestamp of last set (or 0.0 if unset)
|
194
366
|
def stamp
|
195
|
-
@base.value[:stamp]
|
367
|
+
set? ? @base.value[:stamp] : 0.0
|
368
|
+
end
|
369
|
+
|
370
|
+
# Force a timestamp update
|
371
|
+
def restamp!
|
372
|
+
self.value = value
|
196
373
|
end
|
197
374
|
|
375
|
+
# @return [TrueClass, FalseClass] update is allowed based on stamp and limits
|
198
376
|
def update_allowed?
|
199
377
|
!set? || @cache.time_check_allow?(@name, @base.value[:stamp])
|
200
378
|
end
|