chef-dk 0.6.2 → 0.7.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/lib/chef-dk/builtin_commands.rb +7 -0
  4. data/lib/chef-dk/command/env.rb +90 -0
  5. data/lib/chef-dk/command/export.rb +22 -1
  6. data/lib/chef-dk/command/generate.rb +1 -1
  7. data/lib/chef-dk/command/provision.rb +43 -0
  8. data/lib/chef-dk/command/push_archive.rb +126 -0
  9. data/lib/chef-dk/command/show_policy.rb +166 -0
  10. data/lib/chef-dk/command/verify.rb +58 -1
  11. data/lib/chef-dk/cookbook_omnifetch.rb +3 -2
  12. data/lib/chef-dk/exceptions.rb +27 -0
  13. data/lib/chef-dk/helpers.rb +29 -0
  14. data/lib/chef-dk/policyfile/chef_repo_cookbook_source.rb +8 -0
  15. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +8 -0
  16. data/lib/chef-dk/policyfile/community_cookbook_source.rb +8 -0
  17. data/lib/chef-dk/policyfile/cookbook_locks.rb +76 -6
  18. data/lib/chef-dk/policyfile/dsl.rb +10 -5
  19. data/lib/chef-dk/policyfile/lister.rb +230 -0
  20. data/lib/chef-dk/policyfile/null_cookbook_source.rb +8 -0
  21. data/lib/chef-dk/policyfile_compiler.rb +35 -2
  22. data/lib/chef-dk/policyfile_lock.rb +43 -0
  23. data/lib/chef-dk/policyfile_services/clean_policies.rb +94 -0
  24. data/lib/chef-dk/policyfile_services/export_repo.rb +103 -16
  25. data/lib/chef-dk/policyfile_services/push_archive.rb +173 -0
  26. data/lib/chef-dk/policyfile_services/show_policy.rb +237 -0
  27. data/lib/chef-dk/service_exceptions.rb +21 -0
  28. data/lib/chef-dk/skeletons/code_generator/files/default/chefignore +1 -0
  29. data/lib/chef-dk/skeletons/code_generator/files/default/repo/README.md +2 -40
  30. data/lib/chef-dk/skeletons/code_generator/recipes/app.rb +0 -2
  31. data/lib/chef-dk/skeletons/code_generator/templates/default/kitchen.yml.erb +2 -2
  32. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe_spec.rb.erb +1 -1
  33. data/lib/chef-dk/version.rb +1 -1
  34. data/spec/unit/command/env_spec.rb +52 -0
  35. data/spec/unit/command/exec_spec.rb +2 -2
  36. data/spec/unit/command/export_spec.rb +13 -0
  37. data/spec/unit/command/provision_spec.rb +56 -0
  38. data/spec/unit/command/push_archive_spec.rb +153 -0
  39. data/spec/unit/command/show_policy_spec.rb +235 -0
  40. data/spec/unit/command/verify_spec.rb +1 -0
  41. data/spec/unit/helpers_spec.rb +68 -0
  42. data/spec/unit/policyfile/cookbook_locks_spec.rb +107 -1
  43. data/spec/unit/policyfile/lister_spec.rb +256 -0
  44. data/spec/unit/policyfile_demands_spec.rb +202 -10
  45. data/spec/unit/policyfile_evaluation_spec.rb +30 -4
  46. data/spec/unit/policyfile_lock_serialization_spec.rb +45 -0
  47. data/spec/unit/policyfile_services/clean_policies_spec.rb +236 -0
  48. data/spec/unit/policyfile_services/export_repo_spec.rb +99 -6
  49. data/spec/unit/policyfile_services/push_archive_spec.rb +345 -0
  50. data/spec/unit/policyfile_services/show_policy_spec.rb +839 -0
  51. metadata +139 -8
@@ -93,7 +93,13 @@ module ChefDK
93
93
  c.base_dir = "test-kitchen"
94
94
  c.unit_test { sh("bundle exec rake unit") }
95
95
  c.integration_test { sh("bundle exec rake features") }
96
- c.smoke_test { run_in_tmpdir("kitchen init") }
96
+
97
+ # NOTE: By default, kitchen tries to be helpful and install a driver
98
+ # gem for you. This causes a race condition when running the tests
99
+ # concurrently, because rubygems breaks when there are partially
100
+ # installed gems in the gem repository. Instructing kitchen to create a
101
+ # gemfile instead avoids the gem installation.
102
+ c.smoke_test { run_in_tmpdir("kitchen init --create-gemfile") }
97
103
  end
98
104
 
99
105
  add_component "chef-client" do |c|
@@ -115,6 +121,57 @@ module ChefDK
115
121
  c.smoke_test { run_in_tmpdir("chef generate cookbook example") }
116
122
  end
117
123
 
124
+ # entirely possible this needs to be driven by a utility method in chef-provisioning.
125
+ add_component "chef-provisioning" do |c|
126
+ c.base_dir = "chef-dk"
127
+
128
+ c.smoke_test do
129
+ # ------------
130
+ # we want to avoid hard-coding driver names, but calling Gem::Specification produces a warning; this
131
+ # seems to be the best way to silence it.
132
+ verbose = $VERBOSE
133
+ $VERBOSE = nil
134
+
135
+ drivers = Gem::Specification.all.map { |gs| gs.name }.
136
+ select { |n| n =~ /^chef-provisioning-/ }.
137
+ uniq
138
+
139
+ versions = Gem::Specification.find_all_by_name("chef-provisioning").map { |s| s.version }
140
+ $VERBOSE = verbose
141
+ # ------------
142
+ failures = []
143
+
144
+ tmpdir do |cwd|
145
+ versions.each do |provisioning_version|
146
+ gemfile = "chef-provisioning-#{provisioning_version}-chefdk-test.gemfile"
147
+
148
+ # write out the gemfile for this chef-provisioning version, and see if Bundler can make it go.
149
+ with_file(File.join(cwd, gemfile)) do |f|
150
+ f.puts %Q(gem "chef-provisioning", "= #{provisioning_version}")
151
+ drivers.each { |d| f.puts %Q(gem "#{d}") }
152
+ end
153
+
154
+ result = sh("bundle install --local --quiet", cwd: cwd, env: {"BUNDLE_GEMFILE" => gemfile })
155
+
156
+ if result.exitstatus != 0
157
+ failures << result
158
+ end
159
+
160
+ end # end provisioning versions.
161
+
162
+ if failures.size > 0
163
+ failures.each { |fail| puts fail.stdout }
164
+ puts "\nDriver list (no version restrictions):\n #{drivers.join("\n ")}"
165
+ end
166
+
167
+ # dubious on Windows.
168
+ # this is weird, but we seem to require a Mixlib::ShellOut as the return value. suggestions
169
+ # welcome.
170
+ sh(failures.size > 0 ? "false" : "true")
171
+ end
172
+ end
173
+ end
174
+
118
175
  add_component "chefspec" do |c|
119
176
  c.gem_base_dir = "chefspec"
120
177
  c.unit_test { sh("rake unit") }
@@ -18,13 +18,14 @@
18
18
  require 'cookbook-omnifetch'
19
19
  require 'chef-dk/shell_out'
20
20
  require 'chef-dk/cookbook_metadata'
21
+ require 'chef-dk/helpers'
21
22
 
22
23
  require 'chef/http/simple'
23
24
 
24
25
  # Configure CookbookOmnifetch's dependency injection settings to use our classes and config.
25
26
  CookbookOmnifetch.configure do |c|
26
- c.cache_path = File.expand_path('~/.chefdk/cache')
27
- c.storage_path = Pathname.new(File.expand_path('~/.chefdk/cache/cookbooks'))
27
+ c.cache_path = File.expand_path(File.join(ChefDK::Helpers.chefdk_home, 'cache'))
28
+ c.storage_path = Pathname.new(File.expand_path(File.join(ChefDK::Helpers.chefdk_home, 'cache', 'cookbooks')))
28
29
  c.shell_out_class = ChefDK::ShellOut
29
30
  c.cached_cookbook_class = ChefDK::CookbookMetadata
30
31
  end
@@ -71,4 +71,31 @@ module ChefDK
71
71
  class BUG < RuntimeError
72
72
  end
73
73
 
74
+ class CookbookSourceConflict < StandardError
75
+
76
+ attr_reader :conflicting_cookbooks
77
+
78
+ attr_reader :cookbook_sources
79
+
80
+ def initialize(conflicting_cookbooks, cookbook_sources)
81
+ @conflicting_cookbooks = conflicting_cookbooks
82
+ @cookbook_sources = cookbook_sources
83
+ super(compute_message)
84
+ end
85
+
86
+ private
87
+
88
+ def compute_message
89
+ conflicting_cookbook_sets = cookbook_sources.combination(2).map do |source_a, source_b|
90
+ overlapping_cookbooks = conflicting_cookbooks.select do |cookbook_name|
91
+ source_a.universe_graph.key?(cookbook_name) && source_b.universe_graph.key?(cookbook_name)
92
+ end
93
+ "Source #{source_a.desc} and #{source_b.desc} contain conflicting cookbooks:\n" +
94
+ overlapping_cookbooks.sort.map {|c| "- #{c}"}.join("\n")
95
+ end
96
+ conflicting_cookbook_sets.join("\n") + "\n"
97
+ end
98
+
99
+ end
100
+
74
101
  end
@@ -20,6 +20,7 @@ require 'chef-dk/exceptions'
20
20
 
21
21
  module ChefDK
22
22
  module Helpers
23
+ extend self
23
24
 
24
25
  #
25
26
  # Runs given commands using mixlib-shellout
@@ -74,6 +75,17 @@ module ChefDK
74
75
  @omnibus_chefdk_location ||= File.expand_path('embedded/apps/chef-dk', expected_omnibus_root)
75
76
  end
76
77
 
78
+ def chefdk_home
79
+ @chefdk_home ||= begin
80
+ chefdk_home_set = !([nil, ''].include? ENV['CHEFDK_HOME'])
81
+ if chefdk_home_set
82
+ ENV['CHEFDK_HOME']
83
+ else
84
+ default_chefdk_home
85
+ end
86
+ end
87
+ end
88
+
77
89
  private
78
90
 
79
91
  def omnibus_expand_path(*paths)
@@ -86,6 +98,14 @@ module ChefDK
86
98
  File.expand_path(File.join(Gem.ruby, "..", "..", ".."))
87
99
  end
88
100
 
101
+ def default_chefdk_home
102
+ if Chef::Platform.windows?
103
+ File.join(ENV['LOCALAPPDATA'], 'chefdk')
104
+ else
105
+ File.expand_path('~/.chefdk')
106
+ end
107
+ end
108
+
89
109
  #
90
110
  # environment vars for omnibus
91
111
  #
@@ -109,5 +129,14 @@ module ChefDK
109
129
  def with_file(path, mode='wb+', &block)
110
130
  File.open(path, mode, &block)
111
131
  end
132
+
133
+ #@api private
134
+ # This method resets all the instance variables used. It
135
+ # should only be used for testing
136
+ def reset!
137
+ self.instance_variables.each do |ivar|
138
+ self.instance_variable_set(ivar, nil)
139
+ end
140
+ end
112
141
  end
113
142
  end
@@ -57,6 +57,14 @@ module ChefDK
57
57
  { path: cookbook_version_paths[cookbook_name][cookbook_version], version: cookbook_version }
58
58
  end
59
59
 
60
+ def null?
61
+ false
62
+ end
63
+
64
+ def desc
65
+ "chef_repo(#{path})"
66
+ end
67
+
60
68
  private
61
69
 
62
70
  # Setter for setting the path. It may either be a full chef-repo with
@@ -39,6 +39,14 @@ module ChefDK
39
39
  raise UnsupportedFeature, 'ChefDK does not support chef-server cookbook default sources at this time'
40
40
  end
41
41
 
42
+ def null?
43
+ false
44
+ end
45
+
46
+ def desc
47
+ "chef_server(#{uri})"
48
+ end
49
+
42
50
  end
43
51
  end
44
52
  end
@@ -56,6 +56,14 @@ module ChefDK
56
56
  { artifactserver: base_uri, version: cookbook_version }
57
57
  end
58
58
 
59
+ def null?
60
+ false
61
+ end
62
+
63
+ def desc
64
+ "supermarket(#{uri})"
65
+ end
66
+
59
67
  private
60
68
 
61
69
  def http_connection_for(base_url)
@@ -15,6 +15,8 @@
15
15
  # limitations under the License.
16
16
  #
17
17
 
18
+ require 'forwardable'
19
+
18
20
  require 'chef-dk/exceptions'
19
21
 
20
22
  require 'chef-dk/cookbook_profiler/null_scm'
@@ -98,11 +100,16 @@ module ChefDK
98
100
  end
99
101
 
100
102
  def cookbook_path
101
- raise NotImplementedError, "#{self.class} must override #to_lock with a specific implementation"
103
+ raise NotImplementedError, "#{self.class} must override #cookbook_path with a specific implementation"
102
104
  end
103
105
 
104
106
  def to_lock
105
- raise NotImplementedError, "#{self.class} must override #to_lock with a specific implementation"
107
+ validate!
108
+ lock_data
109
+ end
110
+
111
+ def lock_data
112
+ raise NotImplementedError, "#{self.class} must override #lock_data a specific implementation"
106
113
  end
107
114
 
108
115
  def build_from_lock_data(lock_data)
@@ -227,8 +234,7 @@ module ChefDK
227
234
  @source_options = symbolize_source_options_keys(lock_data["source_options"])
228
235
  end
229
236
 
230
- def to_lock
231
- validate!
237
+ def lock_data
232
238
  {
233
239
  "version" => version,
234
240
  "identifier" => identifier,
@@ -280,6 +286,7 @@ module ChefDK
280
286
  @identifier_updated = false
281
287
  @version_updated = false
282
288
  @cookbook_in_git_repo = nil
289
+ @scm_info = nil
283
290
  end
284
291
 
285
292
  def cookbook_path
@@ -295,11 +302,15 @@ module ChefDK
295
302
  end
296
303
 
297
304
  def scm_info
298
- scm_profiler.profile_data
305
+ @scm_info
299
306
  end
300
307
 
301
308
  def to_lock
302
- validate!
309
+ refresh_scm_info
310
+ super
311
+ end
312
+
313
+ def lock_data
303
314
  {
304
315
  "version" => version,
305
316
  "identifier" => identifier,
@@ -319,6 +330,7 @@ module ChefDK
319
330
  @dotted_decimal_identifier = lock_data["dotted_decimal_identifier"]
320
331
  @source = lock_data["source"]
321
332
  @source_options = symbolize_source_options_keys(lock_data["source_options"])
333
+ @scm_info = lock_data["scm_info"]
322
334
  end
323
335
 
324
336
  def validate!
@@ -361,6 +373,10 @@ module ChefDK
361
373
 
362
374
  private
363
375
 
376
+ def refresh_scm_info
377
+ @scm_info = scm_profiler.profile_data
378
+ end
379
+
364
380
  def assert_required_keys_valid!(lock_data)
365
381
  super
366
382
 
@@ -392,5 +408,59 @@ module ChefDK
392
408
  end
393
409
 
394
410
  end
411
+
412
+ class ArchivedCookbook < CookbookLock
413
+
414
+ extend Forwardable
415
+
416
+ def_delegator :@archived_lock, :name
417
+ def_delegator :@archived_lock, :source_options
418
+ def_delegator :@archived_lock, :identifier
419
+ def_delegator :@archived_lock, :dotted_decimal_identifier
420
+ def_delegator :@archived_lock, :version
421
+ def_delegator :@archived_lock, :source
422
+
423
+ # #to_lock calls #validate! which will typically ensure that the cookbook
424
+ # is present at the correct path for the lock type. For an archived
425
+ # cookbook, the cookbook is located in the archive, so it probably won't
426
+ # exist there. Therefore, we bypass #validate! and get the lock data
427
+ # directly.
428
+ def_delegator :@archived_lock, :lock_data, :to_lock
429
+
430
+ def initialize(archived_lock, storage_config)
431
+ @archived_lock = archived_lock
432
+ @storage_config = storage_config
433
+ end
434
+
435
+ def build_from_lock_data(lock_data)
436
+ raise NotImplementedError, "ArchivedCookbook cannot be built from lock data, it can only wrap an existing lock object"
437
+ end
438
+
439
+ def installed?
440
+ File.exist?(cookbook_path) && File.directory?(cookbook_path)
441
+ end
442
+
443
+ # The cookbook is assumed to be stored in a Chef Zero compatible repo as
444
+ # created by `chef export`. Currently that only creates "compatibility
445
+ # mode" repos since Chef Zero doesn't yet support cookbook_artifact APIs.
446
+ # So the cookbook will be located in a path like:
447
+ # cookbooks/nginx-111.222.333
448
+ def cookbook_path
449
+ File.join(relative_paths_root, "cookbooks", "#{name}-#{dotted_decimal_identifier}")
450
+ end
451
+
452
+ # We trust that archived cookbooks haven't been modified, so just return
453
+ # true for #validate!
454
+ def validate!
455
+ true
456
+ end
457
+
458
+ # We trust that archived cookbooks haven't been modified, so just return
459
+ # true for #refresh!
460
+ def refresh!
461
+ true
462
+ end
463
+ end
464
+
395
465
  end
396
466
  end
@@ -44,7 +44,7 @@ module ChefDK
44
44
  @errors = []
45
45
  @run_list = []
46
46
  @named_run_lists = {}
47
- @default_source = NullCookbookSource.new
47
+ @default_source = [ NullCookbookSource.new ]
48
48
  @cookbook_location_specs = {}
49
49
  @storage_config = storage_config
50
50
 
@@ -71,7 +71,7 @@ module ChefDK
71
71
  def default_source(source_type = nil, source_argument = nil)
72
72
  return @default_source if source_type.nil?
73
73
  case source_type
74
- when :community
74
+ when :community, :supermarket
75
75
  set_default_community_source(source_argument)
76
76
  when :chef_server
77
77
  set_default_chef_server_source(source_argument)
@@ -140,14 +140,14 @@ module ChefDK
140
140
  private
141
141
 
142
142
  def set_default_community_source(source_uri)
143
- @default_source = CommunityCookbookSource.new(source_uri)
143
+ set_default_source(CommunityCookbookSource.new(source_uri))
144
144
  end
145
145
 
146
146
  def set_default_chef_server_source(source_uri)
147
147
  if source_uri.nil?
148
148
  @errors << "You must specify the server's URI when using a default_source :chef_server"
149
149
  else
150
- @default_source = ChefServerCookbookSource.new(source_uri)
150
+ set_default_source(ChefServerCookbookSource.new(source_uri))
151
151
  end
152
152
  end
153
153
 
@@ -155,10 +155,15 @@ module ChefDK
155
155
  if path.nil?
156
156
  @errors << "You must specify the path to the chef-repo when using a default_source :chef_repo"
157
157
  else
158
- @default_source = ChefRepoCookbookSource.new(path)
158
+ set_default_source(ChefRepoCookbookSource.new(path))
159
159
  end
160
160
  end
161
161
 
162
+ def set_default_source(source)
163
+ @default_source.delete_at(0) if @default_source[0].null?
164
+ @default_source << source
165
+ end
166
+
162
167
  def validate!
163
168
  if @run_list.empty?
164
169
  @errors << "Invalid run_list. run_list cannot be empty"
@@ -0,0 +1,230 @@
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 'set'
19
+
20
+ require 'chef-dk/authenticated_http'
21
+ require 'chef-dk/service_exceptions'
22
+
23
+ module ChefDK
24
+ module Policyfile
25
+
26
+ class RevIDLockDataMap
27
+
28
+ attr_reader :policy_name
29
+ attr_reader :lock_info_by_rev_id
30
+
31
+ def initialize(policy_name, lock_info_by_rev_id)
32
+ @policy_name = policy_name
33
+ @lock_info_by_rev_id = lock_info_by_rev_id
34
+ end
35
+
36
+
37
+ def cb_info_for(rev_id, cookbook_name)
38
+ lock = lock_info_by_rev_id[rev_id]
39
+ cookbook_lock = lock["cookbook_locks"][cookbook_name]
40
+
41
+ if cookbook_lock
42
+ [cookbook_lock["version"], cookbook_lock["identifier"] ]
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ def cbs_with_differing_ids
49
+ cbs_with_differing_ids = Set.new
50
+ all_cookbook_names.each do |cookbook_name|
51
+ all_identifiers = lock_info_by_rev_id.inject(Set.new) do |id_set, (_rev_id, rev_info)|
52
+ cookbook_lock = rev_info["cookbook_locks"][cookbook_name]
53
+ identifier = cookbook_lock && cookbook_lock["identifier"]
54
+ id_set << identifier
55
+ end
56
+ cbs_with_differing_ids << cookbook_name if all_identifiers.size > 1
57
+ end
58
+ cbs_with_differing_ids
59
+ end
60
+
61
+ def all_cookbook_names
62
+ lock_info_by_rev_id.inject(Set.new) do |cb_set, (_rev_id, rev_info)|
63
+ cb_set.merge(rev_info["cookbook_locks"].keys)
64
+ end
65
+ end
66
+ end
67
+
68
+ class PolicyGroupRevIDMap
69
+
70
+ attr_reader :policy_name
71
+ attr_reader :revision_ids_by_group
72
+
73
+ def initialize(policy_name, revision_ids_by_group)
74
+ @policy_name = policy_name
75
+ @revision_ids_by_group = revision_ids_by_group
76
+ end
77
+
78
+ def unique_revision_ids
79
+ revision_ids_by_group.values.uniq
80
+ end
81
+
82
+ def policy_group_names
83
+ revision_ids_by_group.keys
84
+ end
85
+
86
+ def max_group_name_length
87
+ policy_group_names.map(&:size).max
88
+ end
89
+
90
+ def format_revision_ids
91
+ revision_ids_by_group.inject({}) do |map, (group_name, rev_id)|
92
+ map[group_name] = yield rev_id
93
+ map
94
+ end
95
+ end
96
+
97
+ def empty?
98
+ policy_group_names.empty?
99
+ end
100
+
101
+ def each
102
+ revision_ids_by_group.each do |group_name, rev_id|
103
+ yield group_name, rev_id
104
+ end
105
+ end
106
+ end
107
+
108
+ class Lister
109
+
110
+ attr_accessor :policy_lock_content
111
+
112
+ attr_reader :config
113
+
114
+ def initialize(config: nil)
115
+ @config = config
116
+ @policies_by_name = nil
117
+ @policies_by_group = nil
118
+ @policy_lock_content = {}
119
+ @active_revisions = nil
120
+ end
121
+
122
+ # A Hash with the following format
123
+ # {
124
+ # "appserver" => {
125
+ # "1111111111111111111111111111111111111111111111111111111111111111" => {},
126
+ # "2222222222222222222222222222222222222222222222222222222222222222" => {}
127
+ # },
128
+ def policies_by_name
129
+ @policies_by_name || fetch_policy_lists
130
+ @policies_by_name
131
+ end
132
+
133
+ # A Hash with the following format:
134
+ # "dev" => {
135
+ # "appserver" => "1111111111111111111111111111111111111111111111111111111111111111",
136
+ # "load-balancer" => "5555555555555555555555555555555555555555555555555555555555555555",
137
+ # "db" => "9999999999999999999999999999999999999999999999999999999999999999"
138
+ # }
139
+ def policies_by_group
140
+ @policies_by_group || fetch_policy_lists
141
+ @policies_by_group
142
+ end
143
+
144
+ def revision_info_for(policy_name, _revision_id_list)
145
+ RevIDLockDataMap.new(policy_name, policy_lock_content[policy_name])
146
+ end
147
+
148
+ def revision_ids_by_group_for_each_policy
149
+ policies_by_name.each do |policy_name, _policies|
150
+ rev_id_by_group = revision_ids_by_group_for(policy_name)
151
+ yield policy_name, rev_id_by_group
152
+ end
153
+ end
154
+
155
+ def revision_ids_by_group_for(policy_name)
156
+ map = policies_by_group.inject({}) do |rev_id_map, (group_name, rev_id_map_for_group)|
157
+ rev_id_map[group_name] = rev_id_map_for_group[policy_name]
158
+ rev_id_map
159
+ end
160
+ PolicyGroupRevIDMap.new(policy_name, map)
161
+ end
162
+
163
+ def orphaned_revisions(policy_name)
164
+ orphans = []
165
+ policies_by_name[policy_name].each do |rev_id, _data|
166
+ orphans << rev_id unless active_revisions.include?(rev_id)
167
+ end
168
+ orphans
169
+ end
170
+
171
+ def active_revisions
172
+ @active_revisions ||= policies_by_group.inject(Set.new) do |set, (_group, policy_name_rev_id_map)|
173
+ policy_name_rev_id_map.each do |policy_name, rev_id|
174
+ set << rev_id
175
+ end
176
+ set
177
+ end
178
+ end
179
+
180
+ def empty?
181
+ policies_by_name.empty? && policies_by_group.empty?
182
+ end
183
+
184
+ def http_client
185
+ @http_client ||= ChefDK::AuthenticatedHTTP.new(config.chef_server_url,
186
+ signing_key_filename: config.client_key,
187
+ client_name: config.node_name)
188
+ end
189
+
190
+ # @api private
191
+ # Sets internal copy of policyfile data to policies_by_name and
192
+ # policies_by_group. Used for internal testing.
193
+ def set!(policies_by_name, policies_by_group)
194
+ @policies_by_name = policies_by_name
195
+ @policies_by_group = policies_by_group
196
+ @active_revisions = nil
197
+ end
198
+
199
+ private
200
+
201
+ def fetch_policy_lists
202
+ policy_list_data = http_client.get("policies")
203
+ set_policies_by_name_from_api(policy_list_data)
204
+
205
+ policy_group_data = http_client.get("policy_groups")
206
+ set_policies_by_group_from_api(policy_group_data)
207
+ end
208
+
209
+ def set_policies_by_name_from_api(policy_list_data)
210
+ @policies_by_name = policy_list_data.inject({}) do |map, (policy_name, policy_info)|
211
+ map[policy_name] = policy_info["revisions"]
212
+ map
213
+ end
214
+ end
215
+
216
+ def set_policies_by_group_from_api(policy_group_data)
217
+ @policies_by_group = policy_group_data.inject({}) do |map, (policy_group, policy_info)|
218
+ map[policy_group] = policy_info["policies"].inject({}) do |rev_map, (policy_name, rev_info)|
219
+ rev_map[policy_name] = rev_info["revision_id"]; rev_map
220
+ end
221
+
222
+ map
223
+ end
224
+ end
225
+
226
+ end
227
+ end
228
+ end
229
+
230
+