chef-dk 0.10.0 → 0.11.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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +19 -4
  4. data/Rakefile +9 -0
  5. data/chef-dk.gemspec +3 -1
  6. data/lib/chef-dk/chef_runner.rb +9 -0
  7. data/lib/chef-dk/command/export.rb +6 -0
  8. data/lib/chef-dk/command/generator_commands.rb +3 -3
  9. data/lib/chef-dk/command/generator_commands/base.rb +27 -0
  10. data/lib/chef-dk/command/update.rb +19 -0
  11. data/lib/chef-dk/configurable.rb +13 -1
  12. data/lib/chef-dk/exceptions.rb +3 -0
  13. data/lib/chef-dk/policyfile/cookbook_location_specification.rb +13 -0
  14. data/lib/chef-dk/policyfile/cookbook_locks.rb +1 -1
  15. data/lib/chef-dk/policyfile/dsl.rb +40 -2
  16. data/lib/chef-dk/policyfile_compiler.rb +43 -4
  17. data/lib/chef-dk/policyfile_services/export_repo.rb +156 -51
  18. data/lib/chef-dk/policyfile_services/install.rb +1 -0
  19. data/lib/chef-dk/policyfile_services/push_archive.rb +33 -2
  20. data/lib/chef-dk/skeletons/code_generator/files/default/chefignore +7 -5
  21. data/lib/chef-dk/skeletons/code_generator/files/default/repo/policies/README.md +1 -1
  22. data/lib/chef-dk/version.rb +1 -1
  23. data/lib/kitchen/provisioner/policyfile_zero.rb +8 -3
  24. data/spec/shared/custom_generator_cookbook.rb +15 -2
  25. data/spec/unit/chef_runner_spec.rb +28 -0
  26. data/spec/unit/command/export_spec.rb +11 -0
  27. data/spec/unit/command/generator_commands/base_spec.rb +136 -0
  28. data/spec/unit/command/update_spec.rb +24 -0
  29. data/spec/unit/configurable_spec.rb +41 -0
  30. data/spec/unit/fixtures/configurable/test_config_loader.rb +5 -0
  31. data/spec/unit/fixtures/configurable/test_configurable.rb +10 -0
  32. data/spec/unit/policyfile/cookbook_location_specification_spec.rb +21 -1
  33. data/spec/unit/policyfile/cookbook_locks_spec.rb +1 -1
  34. data/spec/unit/policyfile_demands_spec.rb +206 -0
  35. data/spec/unit/policyfile_evaluation_spec.rb +85 -0
  36. data/spec/unit/policyfile_lock_serialization_spec.rb +1 -1
  37. data/spec/unit/policyfile_services/export_repo_spec.rb +78 -36
  38. data/spec/unit/policyfile_services/install_spec.rb +20 -0
  39. data/spec/unit/policyfile_services/push_archive_spec.rb +41 -8
  40. metadata +27 -11
@@ -21,6 +21,8 @@ require 'zlib'
21
21
 
22
22
  require 'archive/tar/minitar'
23
23
 
24
+ require 'chef/cookbook/chefignore'
25
+
24
26
  require 'chef-dk/service_exceptions'
25
27
  require 'chef-dk/policyfile_lock'
26
28
  require 'chef-dk/policyfile/storage_config'
@@ -95,9 +97,11 @@ module ChefDK
95
97
  with_staging_dir do
96
98
  create_repo_structure
97
99
  copy_cookbooks
98
- create_policyfile_data_item
100
+ create_policyfile_repo_item
101
+ create_policy_group_repo_item
99
102
  copy_policyfile_lock
100
103
  create_client_rb
104
+ create_readme_md
101
105
  if archive?
102
106
  create_archive
103
107
  else
@@ -138,8 +142,10 @@ module ChefDK
138
142
 
139
143
  def create_repo_structure
140
144
  FileUtils.mkdir_p(export_dir)
141
- FileUtils.mkdir_p(cookbooks_staging_dir)
142
- FileUtils.mkdir_p(policyfiles_data_bag_staging_dir)
145
+ FileUtils.mkdir_p(dot_chef_staging_dir)
146
+ FileUtils.mkdir_p(cookbook_artifacts_staging_dir)
147
+ FileUtils.mkdir_p(policies_staging_dir)
148
+ FileUtils.mkdir_p(policy_groups_staging_dir)
143
149
  end
144
150
 
145
151
  def copy_cookbooks
@@ -149,13 +155,13 @@ module ChefDK
149
155
  end
150
156
 
151
157
  def copy_cookbook(lock)
152
- dirname = "#{lock.name}-#{lock.dotted_decimal_identifier}"
153
- export_path = File.join(staging_dir, "cookbooks", dirname)
158
+ dirname = "#{lock.name}-#{lock.identifier}"
159
+ export_path = File.join(staging_dir, "cookbook_artifacts", dirname)
154
160
  metadata_rb_path = File.join(export_path, "metadata.rb")
155
- FileUtils.cp_r(lock.cookbook_path, export_path)
161
+ FileUtils.mkdir(export_path) if not File.directory?(export_path)
162
+ copy_unignored_cookbook_files(lock, export_path)
156
163
  FileUtils.rm_f(metadata_rb_path)
157
164
  metadata = lock.cookbook_version.metadata
158
- metadata.version(lock.dotted_decimal_identifier)
159
165
 
160
166
  metadata_json_path = File.join(export_path, "metadata.json")
161
167
 
@@ -164,23 +170,47 @@ module ChefDK
164
170
  end
165
171
  end
166
172
 
167
- def create_policyfile_data_item
168
- lock_data = policyfile_lock.to_lock.dup
173
+ def copy_unignored_cookbook_files(lock, export_path)
174
+ cookbook_files_to_copy(lock.cookbook_path).each do |rel_path|
175
+ full_source_path = File.join(lock.cookbook_path, rel_path)
176
+ full_dest_path = File.join(export_path, rel_path)
177
+ dest_dirname = File.dirname(full_dest_path)
178
+ FileUtils.mkdir_p(dest_dirname) unless File.directory?(dest_dirname)
179
+ FileUtils.cp(full_source_path, full_dest_path)
180
+ end
181
+ end
182
+
183
+ def cookbook_files_to_copy(cookbook_path)
184
+ cookbook_loader_for(cookbook_path).cookbook_version.manifest_records_by_path.keys
185
+ end
169
186
 
170
- lock_data["id"] = policy_id
187
+ def cookbook_loader_for(cookbook_path)
188
+ loader = Chef::Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore_for(cookbook_path))
189
+ loader.load!
190
+ loader
191
+ end
171
192
 
172
- data_item = {
173
- "id" => policy_id,
174
- "name" => "data_bag_item_policyfiles_#{policy_id}",
175
- "data_bag" => "policyfiles",
176
- "raw_data" => lock_data,
177
- # we'd prefer to leave this out, but the "compatibility mode"
178
- # implementation in chef-client relies on magical class inflation
179
- "json_class" => "Chef::DataBagItem"
193
+ def chefignore_for(cookbook_path)
194
+ Chef::Cookbook::Chefignore.new(File.join(cookbook_path, "chefignore"))
195
+ end
196
+
197
+ def create_policyfile_repo_item
198
+ File.open(policyfile_repo_item_path, "wb+") do |f|
199
+ f.print(FFI_Yajl::Encoder.encode(policyfile_lock.to_lock, pretty: true ))
200
+ end
201
+ end
202
+
203
+ def create_policy_group_repo_item
204
+ data = {
205
+ "policies" => {
206
+ policyfile_lock.name => {
207
+ "revision_id" => policyfile_lock.revision_id
208
+ }
209
+ }
180
210
  }
181
211
 
182
- File.open(item_path, "wb+") do |f|
183
- f.print(FFI_Yajl::Encoder.encode(data_item, pretty: true ))
212
+ File.open(policy_group_repo_item_path, "wb+") do |f|
213
+ f.print(FFI_Yajl::Encoder.encode(data, pretty: true ))
184
214
  end
185
215
  end
186
216
 
@@ -197,32 +227,89 @@ module ChefDK
197
227
  # The settings in this file will configure chef to apply the exported policy in
198
228
  # this directory. To use it, run:
199
229
  #
200
- # chef-client -c client.rb -z
230
+ # chef-client -z
201
231
  #
202
232
 
203
- use_policyfile true
233
+ policy_name '#{policy_name}'
234
+ policy_group 'local'
204
235
 
205
- # compatibility mode settings are used because chef-zero doesn't yet support
206
- # native mode:
207
- deployment_group '#{policy_name}-local'
208
- versioned_cookbooks true
209
- policy_document_native_api false
236
+ use_policyfile true
237
+ policy_document_native_api true
238
+
239
+ # In order to use this repo, you need a version of Chef Client and Chef Zero
240
+ # that supports policyfile "native mode" APIs:
241
+ current_version = Gem::Version.new(Chef::VERSION)
242
+ unless Gem::Requirement.new(">= 12.7").satisfied_by?(current_version)
243
+ puts("!" * 80)
244
+ puts(<<-MESSAGE)
245
+ This Chef Repo requires features introduced in Chef 12.7, but you are using
246
+ Chef \#{Chef::VERSION}. Please upgrade to Chef 12.7 or later.
247
+ MESSAGE
248
+ puts("!" * 80)
249
+ exit!(1)
250
+ end
210
251
 
211
252
  CONFIG
212
253
  end
213
254
  end
214
255
 
256
+ def create_readme_md
257
+ File.open(readme_staging_path, "wb+") do |f|
258
+ f.print( <<-README )
259
+ # Exported Chef Repository for Policy '#{policy_name}'
260
+
261
+ Policy revision: #{policyfile_lock.revision_id}
262
+
263
+ This directory contains all the cookbooks and configuration necessary for Chef
264
+ to converge a system using this exported policy. To converge a system with the
265
+ exported policy, use a privileged account to run `chef-client -z` from the
266
+ directory containing the exported policy.
267
+
268
+ ## Contents:
269
+
270
+ ### Policyfile.lock.json
271
+
272
+ A copy of the exported policy, used by the `chef push-archive` command.
273
+
274
+ ### .chef/config.rb
275
+
276
+ A configuration file for Chef Client. This file configures Chef Client to use
277
+ the correct `policy_name` and `policy_group` for this exported repository. Chef
278
+ Client will use this configuration automatically if you've set your working
279
+ directory properly.
280
+
281
+ ### cookbook_artifacts/
282
+
283
+ All of the cookbooks required by the policy will be stored in this directory.
284
+
285
+ ### policies/
286
+
287
+ A different copy of the exported policy, used by the `chef-client` command.
288
+
289
+ ### policy_groups/
290
+
291
+ Policy groups are used by Chef Server to manage multiple revisions of the same
292
+ policy. However, exported policies contain only a single policy revision, so
293
+ this policy group name is hardcoded to "local" and should not be changed.
294
+
295
+ README
296
+ end
297
+ end
298
+
215
299
  def mv_staged_repo
216
300
  # If we got here, either these dirs are empty/don't exist or force is
217
301
  # set to true.
218
- FileUtils.rm_rf(cookbooks_dir)
219
- FileUtils.rm_rf(policyfiles_data_bag_dir)
220
-
221
- FileUtils.mv(cookbooks_staging_dir, export_dir)
222
- FileUtils.mkdir_p(export_data_bag_dir)
223
- FileUtils.mv(policyfiles_data_bag_staging_dir, export_data_bag_dir)
302
+ FileUtils.rm_rf(cookbook_artifacts_dir)
303
+ FileUtils.rm_rf(policies_dir)
304
+ FileUtils.rm_rf(policy_groups_dir)
305
+ FileUtils.rm_rf(dot_chef_dir)
306
+
307
+ FileUtils.mv(cookbook_artifacts_staging_dir, export_dir)
308
+ FileUtils.mv(policies_staging_dir, export_dir)
309
+ FileUtils.mv(policy_groups_staging_dir, export_dir)
224
310
  FileUtils.mv(lockfile_staging_path, export_dir)
225
- FileUtils.mv(client_rb_staging_path, export_dir)
311
+ FileUtils.mv(dot_chef_staging_dir, export_dir)
312
+ FileUtils.mv(readme_staging_path, export_dir)
226
313
  end
227
314
 
228
315
  def validate_lockfile
@@ -261,37 +348,51 @@ CONFIG
261
348
  end
262
349
 
263
350
  def conflicting_fs_entries
264
- Dir.glob(File.join(cookbooks_dir, "*")) +
265
- Dir.glob(File.join(policyfiles_data_bag_dir, "*")) +
351
+ Dir.glob(File.join(cookbook_artifacts_dir, "*")) +
352
+ Dir.glob(File.join(policies_dir, "*")) +
353
+ Dir.glob(File.join(policy_groups_dir, "*")) +
266
354
  Dir.glob(File.join(export_dir, "Policyfile.lock.json"))
267
355
  end
268
356
 
269
- def cookbooks_dir
270
- File.join(export_dir, "cookbooks")
357
+ def cookbook_artifacts_dir
358
+ File.join(export_dir, "cookbook_artifacts")
359
+ end
360
+
361
+ def policies_dir
362
+ File.join(export_dir, "policies")
363
+ end
364
+
365
+ def policy_groups_dir
366
+ File.join(export_dir, "policy_groups")
367
+ end
368
+
369
+ def dot_chef_dir
370
+ File.join(export_dir, ".chef")
271
371
  end
272
372
 
273
- def export_data_bag_dir
274
- File.join(export_dir, "data_bags")
373
+ def policyfile_repo_item_path
374
+ basename = "#{policyfile_lock.name}-#{policyfile_lock.revision_id}"
375
+ File.join(staging_dir, "policies", "#{basename}.json")
275
376
  end
276
377
 
277
- def policyfiles_data_bag_dir
278
- File.join(export_data_bag_dir, "policyfiles")
378
+ def policy_group_repo_item_path
379
+ File.join(staging_dir, "policy_groups", "local.json")
279
380
  end
280
381
 
281
- def policy_id
282
- "#{policyfile_lock.name}-#{POLICY_GROUP}"
382
+ def dot_chef_staging_dir
383
+ File.join(staging_dir, ".chef")
283
384
  end
284
385
 
285
- def item_path
286
- File.join(staging_dir, "data_bags", "policyfiles", "#{policy_id}.json")
386
+ def cookbook_artifacts_staging_dir
387
+ File.join(staging_dir, "cookbook_artifacts")
287
388
  end
288
389
 
289
- def cookbooks_staging_dir
290
- File.join(staging_dir, "cookbooks")
390
+ def policies_staging_dir
391
+ File.join(staging_dir, "policies")
291
392
  end
292
393
 
293
- def policyfiles_data_bag_staging_dir
294
- File.join(staging_dir, "data_bags", "policyfiles")
394
+ def policy_groups_staging_dir
395
+ File.join(staging_dir, "policy_groups")
295
396
  end
296
397
 
297
398
  def lockfile_staging_path
@@ -299,7 +400,11 @@ CONFIG
299
400
  end
300
401
 
301
402
  def client_rb_staging_path
302
- File.join(staging_dir, "client.rb")
403
+ File.join(dot_chef_staging_dir, "config.rb")
404
+ end
405
+
406
+ def readme_staging_path
407
+ File.join(staging_dir, "README.md")
303
408
  end
304
409
 
305
410
  end
@@ -104,6 +104,7 @@ module ChefDK
104
104
  ui.msg ""
105
105
 
106
106
  ui.msg "Lockfile written to #{policyfile_lock_expanded_path}"
107
+ ui.msg "Policy revision id: #{policyfile_lock.revision_id}"
107
108
  rescue => error
108
109
  raise PolicyfileInstallError.new("Failed to generate Policyfile.lock", error)
109
110
  end
@@ -86,12 +86,22 @@ module ChefDK
86
86
  def read_policyfile_lock(staging_dir)
87
87
  policyfile_lock_path = File.join(staging_dir, "Policyfile.lock.json")
88
88
 
89
+ if looks_like_old_format_archive?(staging_dir)
90
+ raise InvalidPolicyArchive, <<-MESSAGE
91
+ This archive is in an unsupported format.
92
+
93
+ This archive was created with an older version of ChefDK. This version of
94
+ ChefDK does not support archives in the older format. Re-create the archive
95
+ with a newer version of ChefDK or downgrade ChefDK.
96
+ MESSAGE
97
+ end
98
+
89
99
  unless File.exist?(policyfile_lock_path)
90
100
  raise InvalidPolicyArchive, "Archive does not contain a Policyfile.lock.json"
91
101
  end
92
102
 
93
- unless File.directory?(File.join(staging_dir, "cookbooks"))
94
- raise InvalidPolicyArchive, "Archive does not contain a cookbooks directory"
103
+ unless File.directory?(File.join(staging_dir, "cookbook_artifacts"))
104
+ raise InvalidPolicyArchive, "Archive does not contain a cookbook_artifacts directory"
95
105
  end
96
106
 
97
107
 
@@ -167,6 +177,27 @@ module ChefDK
167
177
  end
168
178
  end
169
179
 
180
+ def looks_like_old_format_archive?(staging_dir)
181
+ cookbooks_dir = File.join(staging_dir, "cookbooks")
182
+ data_bags_dir = File.join(staging_dir, "data_bags")
183
+
184
+ cookbook_artifacts_dir = File.join(staging_dir, "cookbook_artifacts")
185
+ policies_dir = File.join(staging_dir, "policies")
186
+ policy_groups_dir = File.join(staging_dir, "policy_groups")
187
+
188
+ # Old archives just had these two dirs
189
+ have_old_dirs = File.exist?(cookbooks_dir) && File.exist?(data_bags_dir)
190
+
191
+ # New archives created by `chef export` will have all of these; it's
192
+ # also possible we'll encounter an "artisanal" archive, which might
193
+ # only be missing one of these by accident. In that case we want to
194
+ # trigger a different error than we're detecting here.
195
+ have_any_new_dirs = File.exist?(cookbook_artifacts_dir) ||
196
+ File.exist?(policies_dir) ||
197
+ File.exist?(policy_groups_dir)
198
+
199
+ have_old_dirs && !have_any_new_dirs
200
+ end
170
201
 
171
202
  end
172
203
  end
@@ -1,5 +1,5 @@
1
1
  # Put files/directories that should be ignored in this file when uploading
2
- # or sharing to the community site.
2
+ # to a chef-server or supermarket.
3
3
  # Lines that start with '# ' are comments.
4
4
 
5
5
  # OS generated files #
@@ -56,6 +56,11 @@ Guardfile
56
56
  Procfile
57
57
  .kitchen*
58
58
  .rubocop.yml
59
+ spec/*
60
+ Rakefile
61
+ .travis.yml
62
+ .foodcritic
63
+ .codeclimate.yml
59
64
 
60
65
  # SCM #
61
66
  #######
@@ -82,6 +87,7 @@ tmp
82
87
  CONTRIBUTING*
83
88
  CHANGELOG*
84
89
  TESTING*
90
+ MAINTAINERS.toml
85
91
 
86
92
  # Strainer #
87
93
  ############
@@ -94,7 +100,3 @@ Strainerfile
94
100
  ###########
95
101
  .vagrant
96
102
  Vagrantfile
97
-
98
- # Travis #
99
- ##########
100
- .travis.yml
@@ -13,7 +13,7 @@ This will create a lockfile `policies/my-app-frontend.lock.json`.
13
13
  To update locked dependencies, run `chef update` like this:
14
14
 
15
15
  ```
16
- chef update policies/my-app-fronend.rb
16
+ chef update policies/my-app-frontend.rb
17
17
  ```
18
18
 
19
19
  You can upload the policy (with associated cookbooks) to the server
@@ -16,5 +16,5 @@
16
16
  #
17
17
 
18
18
  module ChefDK
19
- VERSION = "0.10.0"
19
+ VERSION = "0.11.0"
20
20
  end
@@ -62,6 +62,7 @@ module Kitchen
62
62
  # Since it makes no sense to modify these, they are hardcoded elsewhere.
63
63
  default_config :client_rb, {}
64
64
  default_config :json_attributes, true
65
+ default_config :named_run_list, nil
65
66
  default_config :chef_zero_host, nil
66
67
  default_config :chef_zero_port, 8889
67
68
  default_config :policyfile, "Policyfile.rb"
@@ -111,6 +112,10 @@ module Kitchen
111
112
  args << "--logfile #{config[:log_file]}"
112
113
  end
113
114
 
115
+ if config[:named_run_list]
116
+ args << "--named-run-list #{config[:named_run_list]}"
117
+ end
118
+
114
119
  wrap_shell_code(
115
120
  [cmd, *args].join(" ").
116
121
  tap { |str| str.insert(0, reload_ps1_path) if windows_os? }
@@ -172,9 +177,9 @@ module Kitchen
172
177
 
173
178
  data["use_policyfile"] = true
174
179
  data["versioned_cookbooks"] = true
175
- data["deployment_group"] = "#{policy_exporter.policy_name}-local"
176
- # TODO this will need to be updated when chef-zero supports erchef paths (policy_group vs policies)
177
- data["policy_document_native_api"] = false
180
+ data["policy_name"] = policy_exporter.policy_name
181
+ data["policy_group"] = "local"
182
+ data["policy_document_native_api"] = true
178
183
 
179
184
  info("Preparing client.rb")
180
185
  debug("Creating client.rb from #{data.inspect}")
@@ -10,6 +10,9 @@ shared_examples_for "custom generator cookbook" do
10
10
  let(:default_generator_cookbook_path) { File.expand_path('lib/chef-dk/skeletons/code_generator', project_root) }
11
11
 
12
12
  let(:generator_cookbook_path) { File.join(tempdir, 'a_generator_cookbook') }
13
+ let(:generator_copyright_holder) { 'Chef' }
14
+ let(:generator_email) { 'mail@chef.io' }
15
+ let(:generator_license) { 'Free as in Beer'}
13
16
 
14
17
  let(:argv) { [generator_arg, "--generator-cookbook", generator_cookbook_path] }
15
18
 
@@ -32,7 +35,18 @@ shared_examples_for "custom generator cookbook" do
32
35
 
33
36
  let(:argv) { [generator_arg] }
34
37
 
35
- let(:chefdk_config) { double("Mixlib::Config context for ChefDK", generator_cookbook: generator_cookbook_path) }
38
+ let(:generator_config) do
39
+ double("Generator Config Context",
40
+ license: generator_license,
41
+ copyright_holder: generator_copyright_holder,
42
+ email: generator_email)
43
+ end
44
+
45
+ let(:chefdk_config) do
46
+ double("Mixlib::Config context for ChefDK",
47
+ generator_cookbook: generator_cookbook_path,
48
+ generator: generator_config)
49
+ end
36
50
 
37
51
  before do
38
52
  allow(code_generator).to receive(:chefdk_config).and_return(chefdk_config)
@@ -114,4 +128,3 @@ shared_examples_for "custom generator cookbook" do
114
128
  end
115
129
  end
116
130
  end
117
-