chef-dk 2.3.4 → 2.4.17

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/Gemfile +22 -18
  3. data/Gemfile.lock +184 -254
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/acceptance/Gemfile.lock +27 -32
  7. data/lib/chef-dk/chef_server_api_multi.rb +73 -0
  8. data/lib/chef-dk/command/update.rb +5 -12
  9. data/lib/chef-dk/configurable.rb +19 -0
  10. data/lib/chef-dk/cookbook_omnifetch.rb +1 -0
  11. data/lib/chef-dk/exceptions.rb +11 -0
  12. data/lib/chef-dk/generator.rb +1 -1
  13. data/lib/chef-dk/policyfile/attribute_merge_checker.rb +110 -0
  14. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +5 -4
  15. data/lib/chef-dk/policyfile/chef_server_lock_fetcher.rb +164 -0
  16. data/lib/chef-dk/policyfile/cookbook_location_specification.rb +3 -3
  17. data/lib/chef-dk/policyfile/dsl.rb +16 -0
  18. data/lib/chef-dk/policyfile/included_policies_cookbook_source.rb +156 -0
  19. data/lib/chef-dk/policyfile/local_lock_fetcher.rb +122 -0
  20. data/lib/chef-dk/policyfile/lock_applier.rb +80 -0
  21. data/lib/chef-dk/policyfile/null_cookbook_source.rb +4 -0
  22. data/lib/chef-dk/policyfile/policyfile_location_specification.rb +122 -0
  23. data/lib/chef-dk/policyfile_compiler.rb +129 -16
  24. data/lib/chef-dk/policyfile_lock.rb +30 -0
  25. data/lib/chef-dk/policyfile_services/install.rb +7 -1
  26. data/lib/chef-dk/policyfile_services/update_attributes.rb +10 -2
  27. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe_spec.rb.erb +14 -1
  28. data/lib/chef-dk/version.rb +1 -1
  29. data/omnibus_overrides.rb +6 -6
  30. data/spec/unit/chef_server_api_multi_spec.rb +120 -0
  31. data/spec/unit/command/update_spec.rb +3 -3
  32. data/spec/unit/configurable_spec.rb +27 -0
  33. data/spec/unit/policyfile/attribute_merge_checker_spec.rb +80 -0
  34. data/spec/unit/policyfile/chef_server_lock_fetcher_spec.rb +161 -0
  35. data/spec/unit/policyfile/cookbook_location_specification_spec.rb +48 -0
  36. data/spec/unit/policyfile/included_policies_cookbook_source_spec.rb +242 -0
  37. data/spec/unit/policyfile/local_lock_fetcher_spec.rb +161 -0
  38. data/spec/unit/policyfile/lock_applier_spec.rb +100 -0
  39. data/spec/unit/policyfile_demands_spec.rb +1 -1
  40. data/spec/unit/policyfile_includes_dsl_spec.rb +159 -0
  41. data/spec/unit/policyfile_includes_spec.rb +720 -0
  42. data/spec/unit/policyfile_install_with_includes_spec.rb +232 -0
  43. data/spec/unit/policyfile_lock_build_spec.rb +11 -2
  44. data/spec/unit/policyfile_services/update_attributes_spec.rb +13 -0
  45. metadata +28 -3
@@ -0,0 +1,80 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 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
+ module ChefDK
19
+ module Policyfile
20
+
21
+ # A class that can apply constraints from a lock to a compiler
22
+ class LockApplier
23
+ attr_reader :unlocked_policies
24
+ attr_reader :policyfile_lock
25
+ attr_reader :policyfile_compiler
26
+
27
+ # Initialize a LockApplier. By default, all locked data this class knows
28
+ # about will be applied to the compiler. Currently, it applies locks only
29
+ # for included policies.
30
+ #
31
+ # @param policyfile_lock [PolicyfileLock] contains the locked data to use
32
+ # @param policyfile_compiler [PolicyfileCompiler] the compiler to apply the locked data
33
+ def initialize(policyfile_lock, policyfile_compiler)
34
+ @policyfile_lock = policyfile_lock
35
+ @policyfile_compiler = policyfile_compiler
36
+ @unlocked_policies = []
37
+ end
38
+
39
+ # Unlocks included policies
40
+ #
41
+ # @param policies [:all] Unconstrain all policies
42
+ # @param policies [Array<String>] Unconstrain a specific policy by name
43
+ def with_unlocked_policies(policies)
44
+ if policies == :all || unlocked_policies == :all
45
+ @unlocked_policies = :all
46
+ else
47
+ policies.each do |policy|
48
+ @unlocked_policies << policy
49
+ end
50
+ end
51
+ self
52
+ end
53
+
54
+ # Apply locks described in policyfile_lock allowing for the deviations asked
55
+ # for.
56
+ #
57
+ # @note No changes are applied until apply! is invoked
58
+ def apply!
59
+ prepare_constraints_for_policies
60
+ end
61
+
62
+ private
63
+
64
+ def prepare_constraints_for_policies
65
+ if unlocked_policies == :all
66
+ return
67
+ end
68
+
69
+ policyfile_compiler.included_policies.each do |policy|
70
+ if !unlocked_policies.include?(policy.name)
71
+ lock = policyfile_lock.included_policy_locks.find do |policy_lock|
72
+ policy_lock["name"] == policy.name
73
+ end
74
+ policy.apply_locked_source_options(lock["source_options"])
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -36,6 +36,10 @@ module ChefDK
36
36
  true
37
37
  end
38
38
 
39
+ def preferred_cookbooks
40
+ []
41
+ end
42
+
39
43
  def desc
40
44
  "null_cookbook_source"
41
45
  end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 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 "chef-dk/policyfile_lock"
19
+ require "chef-dk/policyfile/local_lock_fetcher"
20
+ require "chef-dk/policyfile/chef_server_lock_fetcher"
21
+ require "chef-dk/exceptions"
22
+
23
+ module ChefDK
24
+ module Policyfile
25
+ # A PolicyfileLocationSpecification specifies where a policyfile lock is to be fetched from.
26
+ # Using this information, it provides a fetcher that is capable loading the policyfile
27
+ # lock.
28
+ class PolicyfileLocationSpecification
29
+
30
+ # @return [String] the name of the policyfile
31
+ attr_reader :name
32
+
33
+ # @return [Hash] options describing how to get the policyfile lock
34
+ attr_reader :source_options
35
+
36
+ attr_reader :storage_config
37
+ attr_reader :chef_config
38
+ attr_reader :ui
39
+
40
+ LOCATION_TYPES = [:path, :server]
41
+
42
+ # Intialize a location spec
43
+ #
44
+ # @param name [String] the name of the policyfile
45
+ # @param source_options [Hash] options describing where the policyfile lock lives
46
+ # @param storage_config [Poilcyfile::StorageConfig]
47
+ # @param chef_config [Chef::Config] chef config that will be used when communication
48
+ # with a chef server is required
49
+ def initialize(name, source_options, storage_config, chef_config = nil)
50
+ @name = name
51
+ @source_options = source_options
52
+ @storage_config = storage_config
53
+ @ui = nil
54
+ @chef_config = chef_config
55
+ end
56
+
57
+ # @return The revision id from the fetched lock
58
+ def revision_id
59
+ fetcher.lock_data["revision_id"]
60
+ end
61
+
62
+ # @return A policyfile lock fetcher compatible with the given source_options
63
+ def fetcher
64
+ @fetcher ||= begin
65
+ if source_options[:path]
66
+ Policyfile::LocalLockFetcher.new(name, source_options, storage_config)
67
+ elsif source_options[:server]
68
+ Policyfile::ChefServerLockFetcher.new(name, source_options, chef_config)
69
+ else
70
+ raise ChefDK::InvalidPolicyfileLocation.new(
71
+ "Invalid policyfile lock location type. The supported locations are: #{LOCATION_TYPES.join(", ")}")
72
+ end
73
+ end
74
+ end
75
+
76
+ # @return [True] if there were no errors with the provided source_options
77
+ # @return [False] if there were errors with the provided source_options
78
+ def valid?
79
+ errors.empty?
80
+ end
81
+
82
+ # Check the options provided when craeting this class for errors
83
+ #
84
+ # @return [Array<String>] A list of errors found
85
+ def errors
86
+ error_messages = []
87
+
88
+ if LOCATION_TYPES.all? { |l| source_options[l].nil? }
89
+ error_messages << "include_policy must use one of the following sources: #{LOCATION_TYPES.join(', ')}"
90
+ else
91
+ if !fetcher.nil?
92
+ error_messages += fetcher.errors
93
+ end
94
+ end
95
+
96
+ error_messages
97
+ end
98
+
99
+ # Fetches and loads the policyfile lock
100
+ #
101
+ # @return [PolicyfileLock] the loaded policyfile lock
102
+ def policyfile_lock
103
+ @policyfile_lock ||= begin
104
+ PolicyfileLock.new(storage_config, ui: ui).build_from_lock_data(fetcher.lock_data)
105
+ end
106
+ end
107
+
108
+ # @return [Hash] The source_options that describe how to fetch this exact lock again
109
+ def source_options_for_lock
110
+ fetcher.source_options_for_lock
111
+ end
112
+
113
+ # Applies source options from a lock file. This is used to make sure that the same
114
+ # policyfile lock is loaded that was locked
115
+ #
116
+ # @param options_from_lock [Hash] The source options loaded from a policyfile lock
117
+ def apply_locked_source_options(options_from_lock)
118
+ fetcher.apply_locked_source_options(options_from_lock)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -20,8 +20,11 @@ require "forwardable"
20
20
 
21
21
  require "solve"
22
22
  require "chef/run_list"
23
+ require "chef/mixin/deep_merge"
23
24
 
24
25
  require "chef-dk/policyfile/dsl"
26
+ require "chef-dk/policyfile/attribute_merge_checker"
27
+ require "chef-dk/policyfile/included_policies_cookbook_source"
25
28
  require "chef-dk/policyfile_lock"
26
29
  require "chef-dk/ui"
27
30
  require "chef-dk/policyfile/reports/install"
@@ -49,8 +52,8 @@ module ChefDK
49
52
  def_delegator :@dsl, :named_run_list
50
53
  def_delegator :@dsl, :named_run_lists
51
54
  def_delegator :@dsl, :errors
52
- def_delegator :@dsl, :default_source
53
55
  def_delegator :@dsl, :cookbook_location_specs
56
+ def_delegator :@dsl, :included_policies
54
57
 
55
58
  attr_reader :dsl
56
59
  attr_reader :storage_config
@@ -67,6 +70,19 @@ module ChefDK
67
70
  @install_report = Policyfile::Reports::Install.new(ui: @ui, policyfile_compiler: self)
68
71
  end
69
72
 
73
+ def default_source(source_type = nil, source_argument = nil, &block)
74
+ if source_type.nil?
75
+ prepend_array = if included_policies.length > 0
76
+ [included_policies_cookbook_source]
77
+ else
78
+ []
79
+ end
80
+ prepend_array + dsl.default_source
81
+ else
82
+ dsl.default_source(source_type, source_argument, &block)
83
+ end
84
+ end
85
+
70
86
  def error!
71
87
  unless errors.empty?
72
88
  raise PolicyfileError, errors.join("\n")
@@ -79,7 +95,17 @@ module ChefDK
79
95
 
80
96
  def expanded_run_list
81
97
  # doesn't support roles yet...
82
- Chef::RunList.new(*run_list)
98
+ concated_runlist = Chef::RunList.new
99
+ included_policies.each do |policy_spec|
100
+ lock = policy_spec.policyfile_lock
101
+ lock.run_list.each do |run_list_item|
102
+ concated_runlist << run_list_item
103
+ end
104
+ end
105
+ run_list.each do |run_list_item|
106
+ concated_runlist << run_list_item
107
+ end
108
+ concated_runlist
83
109
  end
84
110
 
85
111
  # copy of the expanded_run_list, properly formatted for use in a lockfile
@@ -88,8 +114,23 @@ module ChefDK
88
114
  end
89
115
 
90
116
  def expanded_named_run_lists
91
- named_run_lists.inject({}) do |expanded, (name, run_list_items)|
92
- expanded[name] = Chef::RunList.new(*run_list_items)
117
+ included_policies_named_runlists = included_policies.inject({}) do |acc, policy_spec|
118
+ lock = policy_spec.policyfile_lock
119
+ lock.named_run_lists.inject(acc) do |expanded, (name, run_list_items)|
120
+ expanded[name] ||= Chef::RunList.new
121
+ run_list_items.each do |run_list_item|
122
+ expanded[name] << run_list_item
123
+ end
124
+ expanded
125
+ end
126
+ acc
127
+ end
128
+
129
+ named_run_lists.inject(included_policies_named_runlists) do |expanded, (name, run_list_items)|
130
+ expanded[name] ||= Chef::RunList.new
131
+ run_list_items.each do |run_list_item|
132
+ expanded[name] << run_list_item
133
+ end
93
134
  expanded
94
135
  end
95
136
  end
@@ -102,11 +143,19 @@ module ChefDK
102
143
  end
103
144
 
104
145
  def default_attributes
105
- dsl.node_attributes.combined_default.to_hash
146
+ check_for_default_attribute_conflicts!
147
+ included_policies.map { |p| p.policyfile_lock }.inject(
148
+ dsl.node_attributes.combined_default.to_hash) do |acc, lock|
149
+ Chef::Mixin::DeepMerge.merge(acc, lock.default_attributes)
150
+ end
106
151
  end
107
152
 
108
153
  def override_attributes
109
- dsl.node_attributes.combined_override.to_hash
154
+ check_for_override_attribute_conflicts!
155
+ included_policies.map { |p| p.policyfile_lock }.inject(
156
+ dsl.node_attributes.combined_override.to_hash) do |acc, lock|
157
+ Chef::Mixin::DeepMerge.merge(acc, lock.override_attributes)
158
+ end
110
159
  end
111
160
 
112
161
  def lock
@@ -208,16 +257,9 @@ module ChefDK
208
257
  end
209
258
 
210
259
  def graph_demands
211
- cookbooks_for_demands.map do |cookbook_name|
212
- spec = cookbook_location_spec_for(cookbook_name)
213
- if spec.nil?
214
- [ cookbook_name, DEFAULT_DEMAND_CONSTRAINT ]
215
- elsif spec.version_fixed?
216
- [ cookbook_name, "= #{spec.version}" ]
217
- else
218
- [ cookbook_name, spec.version_constraint.to_s ]
219
- end
220
- end
260
+ ## TODO: By merging cookbooks from the current policyfile and included policies,
261
+ # we lose the ability to know where a conflict came from
262
+ (cookbook_demands_from_current + cookbook_demands_from_policies)
221
263
  end
222
264
 
223
265
  def artifacts_graph
@@ -411,5 +453,76 @@ module ChefDK
411
453
  dependency_set
412
454
  end
413
455
 
456
+ def check_for_default_attribute_conflicts!
457
+ checker = Policyfile::AttributeMergeChecker.new
458
+ checker.with_attributes("user-specified", dsl.node_attributes.combined_default)
459
+ included_policies.map do |policy_spec|
460
+ lock = policy_spec.policyfile_lock
461
+ checker.with_attributes(policy_spec.name, lock.default_attributes)
462
+ end
463
+ checker.check!
464
+ end
465
+
466
+ def check_for_override_attribute_conflicts!
467
+ checker = Policyfile::AttributeMergeChecker.new
468
+ checker.with_attributes("user-specified", dsl.node_attributes.combined_override)
469
+ included_policies.map do |policy_spec|
470
+ lock = policy_spec.policyfile_lock
471
+ checker.with_attributes(policy_spec.name, lock.override_attributes)
472
+ end
473
+ checker.check!
474
+ end
475
+
476
+ def cookbook_demands_from_policies
477
+ included_policies.flat_map do |policy_spec|
478
+ lock = policy_spec.policyfile_lock
479
+ lock.solution_dependencies.to_lock["Policyfile"]
480
+ end
481
+ end
482
+
483
+ def cookbook_demands_from_current
484
+ cookbooks_for_demands.map do |cookbook_name|
485
+ spec = cookbook_location_spec_for(cookbook_name)
486
+ if spec.nil?
487
+ [ cookbook_name, DEFAULT_DEMAND_CONSTRAINT ]
488
+ elsif spec.version_fixed?
489
+ [ cookbook_name, "= #{spec.version}" ]
490
+ else
491
+ [ cookbook_name, spec.version_constraint.to_s ]
492
+ end
493
+ end
494
+ end
495
+
496
+ def included_policies_cookbook_source
497
+ @included_policies_cookbook_source ||= begin
498
+ source = Policyfile::IncludedPoliciesCookbookSource.new(included_policies)
499
+ handle_included_policies_preferred_cookbook_conflicts(source)
500
+ source
501
+ end
502
+ end
503
+
504
+ def handle_included_policies_preferred_cookbook_conflicts(included_policies_source)
505
+ # All cookbooks in the included policies are preferred.
506
+ conflicting_source_messages = []
507
+ dsl.default_source.reject { |s| s.null? }.each do |source_b|
508
+ conflicting_preferences = included_policies_source.preferred_cookbooks & source_b.preferred_cookbooks
509
+ next if conflicting_preferences.empty?
510
+ next if conflicting_preferences.all? do |cookbook_name|
511
+ version = included_policies_source.universe_graph[cookbook_name].keys.first
512
+ if included_policies_source.source_options_for(cookbook_name, version) == source_b.source_options_for(cookbook_name, version)
513
+ true
514
+ else
515
+ false
516
+ end
517
+ end
518
+ conflicting_source_messages << "#{source_b.desc} sets a preferred for cookbook(s) #{conflicting_preferences.join(', ')}. This conflicts with an included policy."
519
+ end
520
+ unless conflicting_source_messages.empty?
521
+ msg = "You may not override the cookbook sources for any cookbooks required by included policies.\n"
522
+ msg << conflicting_source_messages.join("\n") << "\n"
523
+ raise IncludePolicyCookbookSourceConflict.new(msg)
524
+ end
525
+ end
526
+
414
527
  end
415
528
  end
@@ -93,6 +93,8 @@ module ChefDK
93
93
 
94
94
  attr_reader :cookbook_locks
95
95
 
96
+ attr_reader :included_policy_locks
97
+
96
98
  attr_reader :install_report
97
99
 
98
100
  def initialize(storage_config, ui: nil)
@@ -108,6 +110,9 @@ module ChefDK
108
110
  @override_attributes = {}
109
111
 
110
112
  @solution_dependencies = Policyfile::SolutionDependencies.new
113
+
114
+ @included_policy_locks = []
115
+
111
116
  @install_report = InstallReport.new(ui: @ui, policyfile_lock: self)
112
117
  end
113
118
 
@@ -137,6 +142,7 @@ module ChefDK
137
142
  lock["name"] = name
138
143
  lock["run_list"] = run_list
139
144
  lock["named_run_lists"] = named_run_lists unless named_run_lists.empty?
145
+ lock["included_policy_locks"] = included_policy_locks
140
146
  lock["cookbook_locks"] = cookbook_locks_for_lockfile
141
147
  lock["default_attributes"] = default_attributes
142
148
  lock["override_attributes"] = override_attributes
@@ -249,6 +255,14 @@ module ChefDK
249
255
 
250
256
  @solution_dependencies = compiler.solution_dependencies
251
257
 
258
+ @included_policy_locks = compiler.included_policies.map do |policy|
259
+ {
260
+ "name" => policy.name,
261
+ "revision_id" => policy.revision_id,
262
+ "source_options" => policy.source_options_for_lock,
263
+ }
264
+ end
265
+
252
266
  self
253
267
  end
254
268
 
@@ -259,6 +273,7 @@ module ChefDK
259
273
  set_cookbook_locks_from_lock_data(lock_data)
260
274
  set_attributes_from_lock_data(lock_data)
261
275
  set_solution_dependencies_from_lock_data(lock_data)
276
+ set_included_policy_locks_from_lock_data(lock_data)
262
277
  self
263
278
  end
264
279
 
@@ -269,6 +284,7 @@ module ChefDK
269
284
  set_cookbook_locks_as_archives_from_lock_data(lock_data)
270
285
  set_attributes_from_lock_data(lock_data)
271
286
  set_solution_dependencies_from_lock_data(lock_data)
287
+ set_included_policy_locks_from_lock_data(lock_data)
272
288
  self
273
289
  end
274
290
 
@@ -517,6 +533,20 @@ module ChefDK
517
533
  @solution_dependencies = s
518
534
  end
519
535
 
536
+ def set_included_policy_locks_from_lock_data(lock_data)
537
+ locks = lock_data["included_policy_locks"]
538
+ if locks.nil?
539
+ @included_policy_locks = []
540
+ else
541
+ locks.each do |lock_info|
542
+ if !(%w{revision_id name source_options}.all? { |key| !lock_info[key].nil? })
543
+ raise InvalidLockfile, "lockfile included policy missing one of the required keys"
544
+ end
545
+ end
546
+ @included_policy_locks = locks
547
+ end
548
+ end
549
+
520
550
  def build_cookbook_lock_from_lock_data(name, lock_info)
521
551
  unless lock_info.kind_of?(Hash)
522
552
  raise InvalidLockfile, "lockfile cookbook_locks entries must be a Hash (JSON object). (got: #{lock_info.inspect})"