chef-dk 0.5.0.rc.1 → 0.5.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -24
  3. data/lib/chef-dk/builtin_commands.rb +2 -0
  4. data/lib/chef-dk/command/diff.rb +312 -0
  5. data/lib/chef-dk/command/push.rb +1 -1
  6. data/lib/chef-dk/command/shell_init.rb +21 -3
  7. data/lib/chef-dk/command/update.rb +28 -5
  8. data/lib/chef-dk/configurable.rb +1 -1
  9. data/lib/chef-dk/exceptions.rb +3 -0
  10. data/lib/chef-dk/pager.rb +106 -0
  11. data/lib/chef-dk/policyfile/chef_repo_cookbook_source.rb +114 -0
  12. data/lib/chef-dk/policyfile/comparison_base.rb +124 -0
  13. data/lib/chef-dk/policyfile/cookbook_sources.rb +1 -0
  14. data/lib/chef-dk/policyfile/differ.rb +266 -0
  15. data/lib/chef-dk/policyfile/dsl.rb +26 -3
  16. data/lib/chef-dk/policyfile/uploader.rb +4 -5
  17. data/lib/chef-dk/policyfile_compiler.rb +8 -0
  18. data/lib/chef-dk/policyfile_lock.rb +135 -3
  19. data/lib/chef-dk/policyfile_services/install.rb +1 -0
  20. data/lib/chef-dk/policyfile_services/update_attributes.rb +104 -0
  21. data/lib/chef-dk/service_exceptions.rb +12 -0
  22. data/lib/chef-dk/ui.rb +8 -0
  23. data/lib/chef-dk/version.rb +1 -1
  24. data/spec/spec_helper.rb +6 -0
  25. data/spec/test_helpers.rb +4 -0
  26. data/spec/unit/command/diff_spec.rb +283 -0
  27. data/spec/unit/command/shell_init_spec.rb +19 -2
  28. data/spec/unit/command/update_spec.rb +96 -0
  29. data/spec/unit/command/verify_spec.rb +0 -6
  30. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/Berksfile +3 -0
  31. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/README.md +4 -0
  32. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/chefignore +96 -0
  33. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/metadata.rb +9 -0
  34. data/spec/unit/fixtures/local_path_cookbooks/cookbook-with-a-dep/recipes/default.rb +8 -0
  35. data/spec/unit/pager_spec.rb +119 -0
  36. data/spec/unit/policyfile/chef_repo_cookbook_source_spec.rb +66 -0
  37. data/spec/unit/policyfile/comparison_base_spec.rb +343 -0
  38. data/spec/unit/policyfile/differ_spec.rb +687 -0
  39. data/spec/unit/policyfile_evaluation_spec.rb +87 -0
  40. data/spec/unit/policyfile_lock_build_spec.rb +247 -8
  41. data/spec/unit/policyfile_lock_serialization_spec.rb +47 -0
  42. data/spec/unit/policyfile_services/export_repo_spec.rb +2 -0
  43. data/spec/unit/policyfile_services/push_spec.rb +2 -0
  44. data/spec/unit/policyfile_services/update_attributes_spec.rb +217 -0
  45. metadata +62 -6
@@ -18,3 +18,4 @@
18
18
  require 'chef-dk/policyfile/null_cookbook_source'
19
19
  require 'chef-dk/policyfile/community_cookbook_source'
20
20
  require 'chef-dk/policyfile/chef_server_cookbook_source'
21
+ require 'chef-dk/policyfile/chef_repo_cookbook_source'
@@ -0,0 +1,266 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2015 Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'diff/lcs'
19
+ require 'diff/lcs/hunk'
20
+ require 'paint'
21
+ require 'ffi_yajl'
22
+
23
+ module ChefDK
24
+ module Policyfile
25
+ class Differ
26
+
27
+ POLICY_SECTIONS = %w{ revision_id run_list named_run_lists cookbook_locks default_attributes override_attributes }.freeze
28
+ LINES_OF_CONTEXT = 3
29
+ INITIAL_FILE_LENGTH_DIFFERENCE = 0
30
+ FORMAT = :unified
31
+
32
+
33
+ attr_reader :old_lock
34
+ attr_reader :old_name
35
+ attr_reader :new_lock
36
+ attr_reader :new_name
37
+ attr_reader :ui
38
+
39
+ def initialize(old_name: nil, old_lock: nil, new_name: nil, new_lock: nil, ui: nil)
40
+ @old_lock = old_lock
41
+ @new_lock = new_lock
42
+ @old_name = old_name
43
+ @new_name = new_name
44
+ @ui = ui
45
+
46
+ @added_cookbooks = nil
47
+ @removed_cookbooks = nil
48
+ @modified_cookbooks = nil
49
+ end
50
+
51
+ def lock_name
52
+ old_lock["name"]
53
+ end
54
+
55
+ def old_cookbook_locks
56
+ old_lock["cookbook_locks"]
57
+ end
58
+
59
+ def new_cookbook_locks
60
+ new_lock["cookbook_locks"]
61
+ end
62
+
63
+ def updated_sections
64
+ @updated_sections ||= POLICY_SECTIONS.select do |key|
65
+ old_lock[key] != new_lock[key]
66
+ end
67
+ end
68
+
69
+ def different?
70
+ !updated_sections.empty?
71
+ end
72
+
73
+ def run_report
74
+ unless different?
75
+ ui.err("No changes for policy lock '#{lock_name}' between '#{old_name}' and '#{new_name}'")
76
+ return true
77
+ end
78
+
79
+ ui.print("Policy lock '#{lock_name}' differs between '#{old_name}' and '#{new_name}':\n\n")
80
+
81
+ report_rev_id_changes
82
+ report_run_list_changes
83
+ report_added_cookbooks
84
+ report_removed_cookbooks
85
+ report_modified_cookbooks
86
+ report_default_attribute_changes
87
+ report_override_attribute_changes
88
+ end
89
+
90
+ def report_rev_id_changes
91
+ h1('REVISION ID CHANGED')
92
+ old_rev = old_lock["revision_id"]
93
+ new_rev = new_lock["revision_id"]
94
+ diff_lines([ old_rev ], [ new_rev ])
95
+ end
96
+
97
+ def report_run_list_changes
98
+ return nil unless updated_sections.include?("run_list")
99
+ h1("RUN LIST CHANGED")
100
+
101
+ old_run_list = old_lock["run_list"]
102
+ new_run_list = new_lock["run_list"]
103
+
104
+ diff_lines(old_run_list, new_run_list)
105
+ end
106
+
107
+ def report_removed_cookbooks
108
+ return nil if removed_cookbooks.empty?
109
+ h1("REMOVED COOKBOOKS")
110
+ removed_cookbooks.each do |cb_name|
111
+ ui.print("\n")
112
+ old_lock = pretty_json(old_cookbook_locks[cb_name])
113
+ new_lock = []
114
+ h2(cb_name)
115
+ diff_lines(old_lock, new_lock)
116
+ end
117
+ end
118
+
119
+ def report_added_cookbooks
120
+ return nil if added_cookbooks.empty?
121
+ h1("ADDED COOKBOOKS")
122
+ added_cookbooks.each do |cb_name|
123
+ ui.print("\n")
124
+ old_lock = []
125
+ new_lock = pretty_json(new_cookbook_locks[cb_name])
126
+ h2(cb_name)
127
+ diff_lines(old_lock, new_lock)
128
+ end
129
+ end
130
+
131
+ def report_modified_cookbooks
132
+ return nil if modified_cookbooks.empty?
133
+ h1("MODIFIED COOKBOOKS")
134
+ modified_cookbooks.each do |cb_name|
135
+ ui.print("\n")
136
+ old_lock = pretty_json(old_cookbook_locks[cb_name])
137
+ new_lock = pretty_json(new_cookbook_locks[cb_name])
138
+ h2(cb_name)
139
+ diff_lines(old_lock, new_lock)
140
+ end
141
+ end
142
+
143
+ def report_default_attribute_changes
144
+ return nil unless updated_sections.include?("default_attributes")
145
+
146
+ h1("DEFAULT ATTRIBUTES CHANGED")
147
+
148
+ old_default = pretty_json(old_lock["default_attributes"])
149
+ new_default = pretty_json(new_lock["default_attributes"])
150
+ diff_lines(old_default, new_default)
151
+ end
152
+
153
+ def report_override_attribute_changes
154
+ return nil unless updated_sections.include?("override_attributes")
155
+
156
+ h1("OVERRIDE ATTRIBUTES CHANGED")
157
+
158
+ old_override = pretty_json(old_lock["override_attributes"])
159
+ new_override = pretty_json(new_lock["override_attributes"])
160
+ diff_lines(old_override, new_override)
161
+ end
162
+
163
+ def added_cookbooks
164
+ detect_cookbook_changes if @added_cookbooks.nil?
165
+ @added_cookbooks
166
+ end
167
+
168
+ def removed_cookbooks
169
+ detect_cookbook_changes if @removed_cookbooks.nil?
170
+ @removed_cookbooks
171
+ end
172
+
173
+ def modified_cookbooks
174
+ detect_cookbook_changes if @modified_cookbooks.nil?
175
+ @modified_cookbooks
176
+ end
177
+
178
+ private
179
+
180
+ def h1(str)
181
+ ui.msg(str)
182
+ ui.msg('=' * str.size)
183
+ end
184
+
185
+ def h2(str)
186
+ ui.msg(str)
187
+ ui.msg('-' * str.size)
188
+ end
189
+
190
+ def diff_lines(old_lines, new_lines)
191
+ file_length_difference = INITIAL_FILE_LENGTH_DIFFERENCE
192
+
193
+ previous_hunk = nil
194
+
195
+ diffs = Diff::LCS.diff(old_lines, new_lines)
196
+
197
+ ui.print("\n")
198
+
199
+ diffs.each do |piece|
200
+ hunk = Diff::LCS::Hunk.new(old_lines, new_lines, piece, LINES_OF_CONTEXT, file_length_difference)
201
+
202
+ file_length_difference = hunk.file_length_difference
203
+
204
+ maybe_contiguous_hunks = (previous_hunk.nil? || hunk.merge(previous_hunk))
205
+
206
+ if !maybe_contiguous_hunks
207
+ print_color_diff("#{previous_hunk.diff(FORMAT)}\n")
208
+ end
209
+ previous_hunk = hunk
210
+ end
211
+ print_color_diff("#{previous_hunk.diff(FORMAT)}\n") unless previous_hunk.nil?
212
+ ui.print("\n")
213
+ end
214
+
215
+ def print_color_diff(hunk)
216
+ hunk.to_s.each_line do |line|
217
+ ui.print(Paint[line, color_for_line(line)])
218
+ end
219
+ end
220
+
221
+ def color_for_line(line)
222
+ case line[0].chr
223
+ when "+"
224
+ :green
225
+ when "-"
226
+ :red
227
+ when "@"
228
+ line[1].chr == "@" ? :blue : nil
229
+ else
230
+ nil
231
+ end
232
+ end
233
+
234
+ def pretty_json(data)
235
+ FFI_Yajl::Encoder.encode(data, pretty: true).lines.map { |l| l.chomp }
236
+ end
237
+
238
+ def detect_cookbook_changes
239
+ all_locked_cookbooks = old_cookbook_locks.keys | new_cookbook_locks.keys
240
+
241
+ @added_cookbooks = []
242
+ @removed_cookbooks = []
243
+ @modified_cookbooks = []
244
+
245
+ all_locked_cookbooks.each do |cb_name|
246
+ if old_cookbook_locks.key?(cb_name) && new_cookbook_locks.key?(cb_name)
247
+ old_cb_lock = old_cookbook_locks[cb_name]
248
+ new_cb_lock = new_cookbook_locks[cb_name]
249
+ if old_cb_lock != new_cb_lock
250
+ @modified_cookbooks << cb_name
251
+ end
252
+ elsif old_cookbook_locks.key?(cb_name)
253
+ @removed_cookbooks << cb_name
254
+ elsif new_cookbook_locks.key?(cb_name)
255
+ @added_cookbooks << cb_name
256
+ else
257
+ raise "Bug: cookbook lock #{cb_name} cannot be determined as new/removed/modified/unmodified"
258
+ end
259
+ end
260
+ end
261
+
262
+
263
+ end
264
+ end
265
+ end
266
+
@@ -19,6 +19,8 @@ require 'chef-dk/policyfile/cookbook_sources'
19
19
  require 'chef-dk/policyfile/cookbook_location_specification'
20
20
  require 'chef-dk/policyfile/storage_config'
21
21
 
22
+ require 'chef/node/attribute'
23
+
22
24
  module ChefDK
23
25
  module Policyfile
24
26
  class DSL
@@ -33,6 +35,7 @@ module ChefDK
33
35
  attr_reader :cookbook_location_specs
34
36
 
35
37
  attr_reader :named_run_lists
38
+ attr_reader :node_attributes
36
39
 
37
40
  attr_reader :storage_config
38
41
 
@@ -44,6 +47,8 @@ module ChefDK
44
47
  @default_source = NullCookbookSource.new
45
48
  @cookbook_location_specs = {}
46
49
  @storage_config = storage_config
50
+
51
+ @node_attributes = Chef::Node::Attribute.new({}, {}, {}, {})
47
52
  end
48
53
 
49
54
  def name(name = nil)
@@ -63,13 +68,15 @@ module ChefDK
63
68
  @named_run_lists[name] = run_list_items.flatten
64
69
  end
65
70
 
66
- def default_source(source_type = nil, source_uri = nil)
71
+ def default_source(source_type = nil, source_argument = nil)
67
72
  return @default_source if source_type.nil?
68
73
  case source_type
69
74
  when :community
70
- set_default_community_source(source_uri)
75
+ set_default_community_source(source_argument)
71
76
  when :chef_server
72
- set_default_chef_server_source(source_uri)
77
+ set_default_chef_server_source(source_argument)
78
+ when :chef_repo
79
+ set_default_chef_repo_source(source_argument)
73
80
  else
74
81
  @errors << "Invalid default_source type '#{source_type.inspect}'"
75
82
  end
@@ -98,6 +105,14 @@ module ChefDK
98
105
  end
99
106
  end
100
107
 
108
+ def default
109
+ @node_attributes.default
110
+ end
111
+
112
+ def override
113
+ @node_attributes.override
114
+ end
115
+
101
116
  def eval_policyfile(policyfile_string)
102
117
  @policyfile_filename = policyfile_filename
103
118
  instance_eval(policyfile_string, policyfile_filename)
@@ -136,6 +151,14 @@ module ChefDK
136
151
  end
137
152
  end
138
153
 
154
+ def set_default_chef_repo_source(path)
155
+ if path.nil?
156
+ @errors << "You must specify the path to the chef-repo when using a default_source :chef_repo"
157
+ else
158
+ @default_source = ChefRepoCookbookSource.new(path)
159
+ end
160
+ end
161
+
139
162
  def validate!
140
163
  if @run_list.empty?
141
164
  @errors << "Invalid run_list. run_list cannot be empty"
@@ -51,13 +51,12 @@ module ChefDK
51
51
  def upload
52
52
  ui.msg("Uploading policy to policy group #{policy_group}")
53
53
 
54
- if using_policy_document_native_api?
54
+ if !using_policy_document_native_api?
55
55
  ui.msg(<<-DRAGONS)
56
- WARN: Using native policy API preview mode. You may be required to delete and
57
- re-upload this data when upgrading to the final release version of the feature.
56
+ WARN: Uploading policy to policy group #{policy_group} in compatibility mode.
57
+ Cookbooks will be uploaded with very large version numbers, which may be picked
58
+ up by existing nodes.
58
59
  DRAGONS
59
- else
60
- ui.msg("WARN: Uploading policy to policy group #{policy_group} in compatibility mode")
61
60
  end
62
61
 
63
62
  upload_cookbooks
@@ -97,6 +97,14 @@ module ChefDK
97
97
  end
98
98
  end
99
99
 
100
+ def default_attributes
101
+ dsl.node_attributes.combined_default.to_hash
102
+ end
103
+
104
+ def override_attributes
105
+ dsl.node_attributes.combined_override.to_hash
106
+ end
107
+
100
108
  def lock
101
109
  @policyfile_lock ||= PolicyfileLock.build_from_compiler(self, storage_config)
102
110
  end
@@ -1,3 +1,4 @@
1
+ # -*- coding: UTF-8 -*-
1
2
  #
2
3
  # Copyright:: Copyright (c) 2014 Chef Software Inc.
3
4
  # License:: Apache License, Version 2.0
@@ -15,7 +16,7 @@
15
16
  # limitations under the License.
16
17
  #
17
18
 
18
- require 'digest/sha1'
19
+ require 'digest/sha2'
19
20
 
20
21
  require 'chef-dk/policyfile/storage_config'
21
22
  require 'chef-dk/policyfile/cookbook_locks'
@@ -31,7 +32,7 @@ module ChefDK
31
32
  attr_reader :ui
32
33
  attr_reader :policyfile_lock
33
34
 
34
- def initialize(ui: ui, policyfile_lock: nil)
35
+ def initialize(ui: nil, policyfile_lock: nil)
35
36
  @ui = ui
36
37
  @policyfile_lock = policyfile_lock
37
38
 
@@ -83,6 +84,8 @@ module ChefDK
83
84
  attr_accessor :name
84
85
  attr_accessor :run_list
85
86
  attr_accessor :named_run_lists
87
+ attr_accessor :default_attributes
88
+ attr_accessor :override_attributes
86
89
 
87
90
  attr_reader :solution_dependencies
88
91
 
@@ -101,6 +104,9 @@ module ChefDK
101
104
  @storage_config = storage_config
102
105
  @ui = ui || UI.null
103
106
 
107
+ @default_attributes = {}
108
+ @override_attributes = {}
109
+
104
110
  @solution_dependencies = Policyfile::SolutionDependencies.new
105
111
  @install_report = InstallReport.new(ui: @ui, policyfile_lock: self)
106
112
  end
@@ -132,6 +138,8 @@ module ChefDK
132
138
  lock["run_list"] = run_list
133
139
  lock["named_run_lists"] = named_run_lists unless named_run_lists.empty?
134
140
  lock["cookbook_locks"] = cookbook_locks_for_lockfile
141
+ lock["default_attributes"] = default_attributes
142
+ lock["override_attributes"] = override_attributes
135
143
  lock["solution_dependencies"] = solution_dependencies.to_lock
136
144
  end
137
145
  end
@@ -139,7 +147,7 @@ module ChefDK
139
147
  # Returns a fingerprint of the PolicyfileLock by computing the SHA1 hash of
140
148
  # #canonical_revision_string
141
149
  def revision_id
142
- Digest::SHA1.new.hexdigest(canonical_revision_string)
150
+ Digest::SHA256.new.hexdigest(canonical_revision_string)
143
151
  end
144
152
 
145
153
  # Generates a string representation of the lock data in a specialized
@@ -172,6 +180,10 @@ module ChefDK
172
180
  canonical_rev_text << "cookbook:#{name};id:#{lock["identifier"]}\n"
173
181
  end
174
182
 
183
+ canonical_rev_text << "default_attributes:#{canonicalize(default_attributes)}\n"
184
+
185
+ canonical_rev_text << "override_attributes:#{canonicalize(override_attributes)}\n"
186
+
175
187
  canonical_rev_text
176
188
  end
177
189
 
@@ -232,6 +244,9 @@ module ChefDK
232
244
  end
233
245
  end
234
246
 
247
+ @default_attributes = compiler.default_attributes
248
+ @override_attributes = compiler.override_attributes
249
+
235
250
  @solution_dependencies = compiler.solution_dependencies
236
251
 
237
252
  self
@@ -241,6 +256,7 @@ module ChefDK
241
256
  set_name_from_lock_data(lock_data)
242
257
  set_run_list_from_lock_data(lock_data)
243
258
  set_cookbook_locks_from_lock_data(lock_data)
259
+ set_attributes_from_lock_data(lock_data)
244
260
  set_solution_dependencies_from_lock_data(lock_data)
245
261
  self
246
262
  end
@@ -264,6 +280,97 @@ module ChefDK
264
280
 
265
281
  private
266
282
 
283
+ # Generates a canonical JSON representation of the attributes. Based on
284
+ # http://wiki.laptop.org/go/Canonical_JSON but not quite as strict, yet.
285
+ #
286
+ # In particular:
287
+ # - String encoding stuff isn't normalized
288
+ # - We allow floats that fit within the range/precision requirements of
289
+ # IEEE 754-2008 binary64 (double precision) numbers.
290
+ # - +/- Infinity and NaN are banned, but float/numeric size aren't checked.
291
+ # numerics should be in range [-(2**53)+1, (2**53)-1] to comply with
292
+ # IEEE 754-2008
293
+ #
294
+ # Recursive, so absurd nesting levels could cause a SystemError. Invalid
295
+ # input will cause an InvalidPolicyfileAttribute exception.
296
+ def canonicalize(attributes)
297
+ unless attributes.kind_of?(Hash)
298
+ raise "Top level attributes must be a Hash (you gave: #{attributes})"
299
+ end
300
+ canonicalize_elements(attributes)
301
+ end
302
+
303
+ def canonicalize_elements(item)
304
+ case item
305
+ when Hash
306
+ # Hash keys will sort differently based on the encoding, but after a
307
+ # JSON round trip everything will be UTF-8, so we have to normalize the
308
+ # keys to UTF-8 first so that the sort order uses the UTF-8 strings.
309
+ item_with_normalized_keys = item.inject({}) do |normalized_item, (key, value)|
310
+ validate_attr_key(key)
311
+ normalized_item[key.encode('utf-8')] = value
312
+ normalized_item
313
+ end
314
+ elements = item_with_normalized_keys.keys.sort.map do |key|
315
+ k = '"' << key << '":'
316
+ v = canonicalize_elements(item_with_normalized_keys[key])
317
+ k << v
318
+ end
319
+ "{" << elements.join(',') << "}"
320
+ when String
321
+ '"' << item.encode('utf-8') << '"'
322
+ when Array
323
+ elements = item.map { |i| canonicalize_elements(i) }
324
+ '[' << elements.join(',') << ']'
325
+ when Integer
326
+ item.to_s
327
+ when Float
328
+ unless item.finite?
329
+ raise InvalidPolicyfileAttribute, "Floating point numbers cannot be infinite or NaN. You gave #{item.inspect}"
330
+ end
331
+ # Support for floats assumes that any implementation of our JSON
332
+ # canonicalization routine will use IEEE-754 doubles. In decimal terms,
333
+ # doubles give 15-17 digits of precision, so we err on the safe side
334
+ # and only use 15 digits in the string conversion. We use the `g`
335
+ # format, which is a documented-enough "do what I mean" where floats
336
+ # >= 0.1 and < precsion are represented as floating point literals, and
337
+ # other numbers use the exponent notation with a lowercase 'e'. Note
338
+ # that both Ruby and Erlang document what their `g` does but have some
339
+ # differences both subtle and non-subtle:
340
+ #
341
+ # ```ruby
342
+ # format("%.15g", 0.1) #=> "0.1"
343
+ # format("%.15g", 1_000_000_000.0) #=> "1000000000"
344
+ # ```
345
+ #
346
+ # Whereas:
347
+ #
348
+ # ```erlang
349
+ # lists:flatten(io_lib:format("~.15g", [0.1])). %=> "0.100000000000000"
350
+ # lists:flatten(io_lib:format("~.15e", [1000000000.0])). %=> "1.00000000000000e+9"
351
+ # ```
352
+ #
353
+ # Other implementations should normalize to ruby's %.15g behavior.
354
+ Kernel.format("%.15g", item)
355
+ when NilClass
356
+ "null"
357
+ when TrueClass
358
+ "true"
359
+ when FalseClass
360
+ "false"
361
+ else
362
+ raise InvalidPolicyfileAttribute,
363
+ "Invalid type in attributes. Only Hash, Array, String, Integer, Float, true, false, and nil are accepted. You gave #{item.inspect} (#{item.class})"
364
+ end
365
+ end
366
+
367
+ def validate_attr_key(key)
368
+ unless key.kind_of?(String)
369
+ raise InvalidPolicyfileAttribute,
370
+ "Attribute keys must be Strings (other types are not allowed in JSON). You gave: #{key.inspect} (#{key.class})"
371
+ end
372
+ end
373
+
267
374
  def set_name_from_lock_data(lock_data)
268
375
  name_attribute = lock_data["name"]
269
376
 
@@ -316,6 +423,31 @@ module ChefDK
316
423
  end
317
424
  end
318
425
 
426
+ def set_attributes_from_lock_data(lock_data)
427
+ default_attr_data = lock_data["default_attributes"]
428
+
429
+ if default_attr_data.nil?
430
+ raise InvalidLockfile, "lockfile does not have a `default_attributes` attribute"
431
+ end
432
+
433
+ unless default_attr_data.kind_of?(Hash)
434
+ raise InvalidLockfile, "lockfile's `default_attributes` attribute must be a Hash (JSON object). (got: #{default_attr_data.inspect})"
435
+ end
436
+
437
+ override_attr_data = lock_data["override_attributes"]
438
+
439
+ if override_attr_data.nil?
440
+ raise InvalidLockfile, "lockfile does not have a `override_attributes` attribute"
441
+ end
442
+
443
+ unless override_attr_data.kind_of?(Hash)
444
+ raise InvalidLockfile, "lockfile's `override_attributes` attribute must be a Hash (JSON object). (got: #{override_attr_data.inspect})"
445
+ end
446
+
447
+ @default_attributes = default_attr_data
448
+ @override_attributes = override_attr_data
449
+ end
450
+
319
451
  def set_solution_dependencies_from_lock_data(lock_data)
320
452
  soln_deps = lock_data["solution_dependencies"]
321
453