chef-dk 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
+