1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
class Chef
module ChefFS
module DataHandler
#
# The base class for all *DataHandlers.
#
# DataHandlers' job is to know the innards of Chef objects and manipulate
# JSON for them, adding defaults and formatting them.
#
class DataHandlerBase
#
# Remove all default values from a Chef object's JSON so that the only
# thing you see are the values that have been explicitly set.
# Achieves this by calling normalize({}, entry) to get the list of
# defaults, and subtracting anything that is the same.
#
def minimize(object, entry)
default_object = default(entry)
object.each_pair do |key, value|
if default_object[key] == value && !preserve_key?(key)
object.delete(key)
end
end
object
end
def remove_file_extension(name, ext = ".*")
if %w{ .rb .json }.include?(File.extname(name))
File.basename(name, ext)
else
name
end
end
alias_method :remove_dot_json, :remove_file_extension
#
# Return true if minimize() should preserve a key even if it is the same
# as the default. Often used for ids and names.
#
def preserve_key?(key)
false
end
#
# Get the default value for an entry. Calls normalize({}, entry).
#
def default(entry)
normalize({}, entry)
end
#
# Utility function to help subclasses do normalize(). Pass in a hash
# and a list of keys with defaults, and normalize will:
#
# 1. Fill in the defaults
# 2. Put the actual values in the order of the defaults
# 3. Move any other values to the end
#
# @example
# normalize_hash({x: 100, c: 2, a: 1}, { a: 10, b: 20, c: 30})
# -> { a: 1, b: 20, c: 2, x: 100}
#
def normalize_hash(object, defaults)
# Make a normalized result in the specified order for diffing
result = {}
defaults.each_pair do |key, value|
result[key] = object.is_a?(Hash) && object.key?(key) ? object[key] : value
end
if object.is_a?(Hash)
object.each_pair do |key, value|
result[key] = value unless result.key?(key)
end
else
Chef::Log.warn "Encountered invalid object during normalization. Using these defaults #{defaults}"
end
result
end
# Specialized function to normalize an object before POSTing it, since
# some object types want slightly different values on POST.
# If not overridden, this just calls normalize()
def normalize_for_post(object, entry)
normalize(object, entry)
end
# Specialized function to normalize an object before PUTing it, since
# some object types want slightly different values on PUT.
# If not overridden, this just calls normalize().
def normalize_for_put(object, entry)
normalize(object, entry)
end
#
# normalize a run list (an array of run list items).
# Leaves recipe[name] and role[name] alone, and translates
# name to recipe[name]. Then calls uniq on the result.
#
def normalize_run_list(run_list)
run_list.map do |item|
case item.to_s
when /^recipe\[.*\]$/
item # explicit recipe
when /^role\[.*\]$/
item # explicit role
else
"recipe[#{item}]"
end
end.uniq
end
#
# Bring in an instance of this object from Ruby. (Like roles/x.rb)
#
def from_ruby(path)
r = chef_class.new
r.from_file(path)
r.to_h
end
#
# Turn a JSON hash into a bona fide Chef object (like Chef::Node).
#
def chef_object(object)
chef_class.from_hash(object)
end
#
# Write out the Ruby file for this instance. (Like roles/x.rb)
#
def to_ruby(object)
raise NotImplementedError
end
#
# Get the class for instances of this type. Must be overridden.
#
def chef_class
raise NotImplementedError
end
#
# Helper to write out a Ruby file for a JSON hash. Writes out only
# the keys specified in "keys"; anything else must be emitted by the
# caller.
#
# @example
# to_ruby_keys({"name" => "foo", "environment" => "desert", "foo": "bar"}, [ "name", "environment" ])
# ->
# 'name "foo"
# environment "desert"'
#
def to_ruby_keys(object, keys)
result = ""
keys.each do |key|
if object[key]
if object[key].is_a?(Hash)
if object[key].size > 0
result << key
first = true
object[key].each_pair do |k, v|
if first
first = false
else
result << " " * key.length
end
result << " #{k.inspect} => #{v.inspect}\n"
end
end
elsif object[key].is_a?(Array)
if object[key].size > 0
result << key
first = true
object[key].each do |value|
if first
first = false
else
result << ", "
end
result << value.inspect
end
result << "\n"
end
elsif !object[key].nil?
result << "#{key} #{object[key].inspect}\n"
end
end
end
result
end
# Verify that the JSON hash for this type has a key that matches its name.
#
# @param object [Object] JSON hash of the object
# @param entry [Chef::ChefFS::FileSystem::BaseFSObject] filesystem object we are verifying
# @yield [s] callback to handle errors
# @yieldparam [s<string>] error message
def verify_integrity(object, entry)
base_name = remove_file_extension(entry.name)
if object["name"] != base_name
yield("Name must be '#{base_name}' (is '#{object['name']}')")
end
end
end # class DataHandlerBase
end
end
end
|