chef-dk 0.2.1 → 0.3.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/CONTRIBUTING.md +2 -2
  3. data/README.md +25 -0
  4. data/lib/chef-dk/builtin_commands.rb +4 -0
  5. data/lib/chef-dk/cli.rb +46 -0
  6. data/lib/chef-dk/command/base.rb +4 -0
  7. data/lib/chef-dk/command/generator_commands/template.rb +2 -1
  8. data/lib/chef-dk/command/install.rb +105 -0
  9. data/lib/chef-dk/command/push.rb +123 -0
  10. data/lib/chef-dk/cookbook_profiler/identifiers.rb +5 -0
  11. data/lib/chef-dk/exceptions.rb +38 -0
  12. data/lib/chef-dk/generator.rb +16 -1
  13. data/lib/chef-dk/helpers.rb +1 -1
  14. data/lib/chef-dk/policyfile/cookbook_location_specification.rb +4 -0
  15. data/lib/chef-dk/policyfile/cookbook_locks.rb +73 -0
  16. data/lib/chef-dk/policyfile/reports/install.rb +70 -0
  17. data/lib/chef-dk/policyfile/reports/table_printer.rb +58 -0
  18. data/lib/chef-dk/policyfile/reports/upload.rb +70 -0
  19. data/lib/chef-dk/policyfile/solution_dependencies.rb +102 -8
  20. data/lib/chef-dk/policyfile/uploader.rb +37 -6
  21. data/lib/chef-dk/policyfile_compiler.rb +19 -5
  22. data/lib/chef-dk/policyfile_lock.rb +122 -9
  23. data/lib/chef-dk/policyfile_services/install.rb +131 -0
  24. data/lib/chef-dk/policyfile_services/push.rb +121 -0
  25. data/lib/chef-dk/skeletons/code_generator/recipes/cookbook.rb +6 -4
  26. data/lib/chef-dk/ui.rb +50 -0
  27. data/lib/chef-dk/version.rb +1 -1
  28. data/spec/shared/a_file_generator.rb +4 -1
  29. data/spec/test_helpers.rb +21 -0
  30. data/spec/unit/cli_spec.rb +100 -1
  31. data/spec/unit/command/base_spec.rb +23 -0
  32. data/spec/unit/command/exec_spec.rb +2 -2
  33. data/spec/unit/command/install_spec.rb +159 -0
  34. data/spec/unit/command/push_spec.rb +203 -0
  35. data/spec/unit/command/shell_init_spec.rb +1 -1
  36. data/spec/unit/policyfile/cookbook_location_specification_spec.rb +7 -0
  37. data/spec/unit/policyfile/cookbook_locks_spec.rb +13 -2
  38. data/spec/unit/policyfile/reports/install_spec.rb +115 -0
  39. data/spec/unit/policyfile/reports/upload_spec.rb +96 -0
  40. data/spec/unit/policyfile/solution_dependencies_spec.rb +1 -1
  41. data/spec/unit/policyfile/uploader_spec.rb +9 -12
  42. data/spec/unit/policyfile_lock_serialization_spec.rb +292 -0
  43. data/spec/unit/policyfile_services/install_spec.rb +170 -0
  44. data/spec/unit/policyfile_services/push_spec.rb +202 -0
  45. metadata +48 -6
@@ -22,7 +22,22 @@ module ChefDK
22
22
  # This is here to hold attr_accessor data for Generator context variables
23
23
  class Context
24
24
  def self.add_attr(name)
25
- attr_accessor(name)
25
+ @attributes ||= [ ]
26
+
27
+ if !@attributes.include?(name)
28
+ @attributes << name
29
+ attr_accessor(name)
30
+ end
31
+ end
32
+
33
+ def self.reset
34
+ return if @attributes.nil?
35
+
36
+ @attributes.each do |attr|
37
+ remove_method(attr)
38
+ end
39
+
40
+ @attributes = nil
26
41
  end
27
42
  end
28
43
 
@@ -85,7 +85,7 @@ module ChefDK
85
85
  {
86
86
  'PATH' => "#{omnibus_bin_dir}:#{user_bin_dir}:#{omnibus_embedded_bin_dir}:#{ENV['PATH']}",
87
87
  'GEM_ROOT' => Gem.default_dir.inspect,
88
- 'GEM_HOME' => Gem.paths.home,
88
+ 'GEM_HOME' => Gem.user_dir,
89
89
  'GEM_PATH' => Gem.path.join(':'),
90
90
  }
91
91
  end
@@ -64,6 +64,10 @@ module ChefDK
64
64
  [:git, :github, :artifactserver].include?(source_type)
65
65
  end
66
66
 
67
+ def installed?
68
+ installer.installed?
69
+ end
70
+
67
71
  def ensure_cached
68
72
  unless installer.installed?
69
73
  installer.install
@@ -1,3 +1,19 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 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
+ #
1
17
 
2
18
  require 'chef-dk/exceptions'
3
19
 
@@ -16,6 +32,10 @@ module ChefDK
16
32
  # Base class for CookbookLock implementations
17
33
  class CookbookLock
18
34
 
35
+ REQUIRED_LOCK_DATA_KEYS = %w{version identifier dotted_decimal_identifier cache_key source_options}
36
+ REQUIRED_LOCK_DATA_KEYS.each(&:freeze)
37
+ REQUIRED_LOCK_DATA_KEYS.freeze
38
+
19
39
  include Policyfile::StorageConfigDelegation
20
40
 
21
41
  # The cookbook name (without any version or other info suffixed)
@@ -49,6 +69,10 @@ module ChefDK
49
69
  @storage_config = storage_config
50
70
  end
51
71
 
72
+ def installed?
73
+ cookbook_location_spec.installed?
74
+ end
75
+
52
76
  def install_locked
53
77
  cookbook_location_spec.ensure_cached
54
78
  end
@@ -129,6 +153,36 @@ module ChefDK
129
153
  def chefignore
130
154
  @chefignore ||= Chef::Cookbook::Chefignore.new(File.join(cookbook_path, "chefignore"))
131
155
  end
156
+
157
+ private
158
+
159
+ def assert_required_keys_valid!(lock_data)
160
+ missing_keys = REQUIRED_LOCK_DATA_KEYS.reject {|key| lock_data.key?(key) }
161
+ unless missing_keys.empty?
162
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} missing required attributes `#{missing_keys.join("', `")}'"
163
+ end
164
+
165
+ version = lock_data["version"]
166
+ unless version.kind_of?(String)
167
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} `version' attribute must be a string (got: #{version})"
168
+ end
169
+
170
+ identifier = lock_data["identifier"]
171
+ unless identifier.kind_of?(String)
172
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} `identifier' attribute must be a string (got: #{identifier})"
173
+ end
174
+
175
+ cache_key = lock_data["cache_key"]
176
+ unless cache_key.kind_of?(String) || cache_key.nil?
177
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} `cache_key' attribute must be a string (got: #{cache_key})"
178
+ end
179
+
180
+ source_options = lock_data["source_options"]
181
+ unless source_options.kind_of?(Hash)
182
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} `source_options' attribute must be a Hash (JSON object) (got: #{source_options})"
183
+ end
184
+ end
185
+
132
186
  end
133
187
 
134
188
  # CachedCookbook objects represent a cookbook that has been fetched from an
@@ -163,6 +217,8 @@ module ChefDK
163
217
  end
164
218
 
165
219
  def build_from_lock_data(lock_data)
220
+ assert_required_keys_valid!(lock_data)
221
+
166
222
  @version = lock_data["version"]
167
223
  @identifier = lock_data["identifier"]
168
224
  @dotted_decimal_identifier = lock_data["dotted_decimal_identifier"]
@@ -255,6 +311,8 @@ module ChefDK
255
311
  end
256
312
 
257
313
  def build_from_lock_data(lock_data)
314
+ assert_required_keys_valid!(lock_data)
315
+
258
316
  @version = lock_data["version"]
259
317
  @identifier = lock_data["identifier"]
260
318
  @dotted_decimal_identifier = lock_data["dotted_decimal_identifier"]
@@ -300,6 +358,21 @@ module ChefDK
300
358
  @identifier_updated
301
359
  end
302
360
 
361
+ private
362
+
363
+ def assert_required_keys_valid!(lock_data)
364
+ super
365
+
366
+ source = lock_data["source"]
367
+ if source.nil?
368
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} is invalid. Lock data for a local cookbook must have a `source' attribute"
369
+ end
370
+
371
+ unless source.kind_of?(String)
372
+ raise InvalidLockfile, "Lockfile cookbook_lock for #{name} is invalid: `source' attribute must be a String (got: #{source.inspect})"
373
+ end
374
+ end
375
+
303
376
  end
304
377
  end
305
378
  end
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 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/reports/table_printer'
19
+
20
+ module ChefDK
21
+ module Policyfile
22
+ module Reports
23
+
24
+ class Install
25
+
26
+ attr_reader :ui
27
+ attr_reader :policyfile_compiler
28
+
29
+ def initialize(ui: nil, policyfile_compiler: nil)
30
+ @ui = ui
31
+ @policyfile_compiler = policyfile_compiler
32
+
33
+ @fixed_version_install_table = nil
34
+ @install_table = nil
35
+ end
36
+
37
+ def installing_fixed_version_cookbook(cookbook_spec)
38
+ verb = cookbook_spec.installed? ? "Using " : "Installing"
39
+ fixed_version_install_table.print_row(verb, cookbook_spec.name, cookbook_spec.version_constraint.to_s, "from #{cookbook_spec.source_type}")
40
+ end
41
+
42
+ def installing_cookbook(cookbook_spec)
43
+ verb = cookbook_spec.installed? ? "Using " : "Installing"
44
+ install_table.print_row(verb, cookbook_spec.name, cookbook_spec.version_constraint.version)
45
+ end
46
+
47
+ private
48
+
49
+ def fixed_version_install_table
50
+ @fixed_version_install_table ||= TablePrinter.new(ui) do |t|
51
+ t.column(["Using", "Installing"])
52
+ t.column(policyfile_compiler.fixed_version_cookbooks_specs.keys)
53
+ t.column
54
+ t.column
55
+ end
56
+ end
57
+
58
+ def install_table
59
+ @install_table ||= TablePrinter.new(ui) do |t|
60
+ t.column(["Using", "Installing"])
61
+ t.column(policyfile_compiler.graph_solution.keys)
62
+ t.column(policyfile_compiler.graph_solution.values)
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
70
+
@@ -0,0 +1,58 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 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
+ module Reports
21
+
22
+ # Defines a table with a flexible number of columns and prints rows in
23
+ # the table. Columns are defined ahead of time, by calling the #column
24
+ # method, individual rows are printed by calling #print_row with the data
25
+ # for each cell.
26
+ class TablePrinter
27
+
28
+ attr_reader :ui
29
+
30
+ def initialize(ui)
31
+ @ui = ui
32
+ @column_widths = []
33
+
34
+ yield self
35
+ end
36
+
37
+ # Defines a column. If a collection is given, it is mapped to an array
38
+ # of strings and the longest string is used as the left justify width
39
+ # for that column when rows are printed.
40
+ def column(collection = [])
41
+ @column_widths << (collection.map(&:to_s).map(&:size).max || 0)
42
+ end
43
+
44
+ # Print a row.
45
+ def print_row(*cells)
46
+ row = ""
47
+ cells.each_with_index do |cell_data, i|
48
+ row << cell_data.to_s.ljust(@column_widths[i])
49
+ row << " "
50
+ end
51
+ ui.msg(row.strip)
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 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/reports/table_printer'
19
+
20
+ module ChefDK
21
+ module Policyfile
22
+ module Reports
23
+ class Upload
24
+
25
+ attr_reader :reused_cbs
26
+ attr_reader :uploaded_cbs
27
+ attr_reader :ui
28
+
29
+ def initialize(reused_cbs: [], uploaded_cbs: [], ui: nil)
30
+ @reused_cbs = reused_cbs
31
+ @uploaded_cbs = uploaded_cbs
32
+ @ui = ui
33
+
34
+ @justify_name_width = nil
35
+ @justify_version_width = nil
36
+ end
37
+
38
+ def show
39
+ reused_cbs.each do |cb_with_lock|
40
+ lock = cb_with_lock.lock
41
+ table.print_row("Using", lock.name, lock.version, "(#{lock.identifier[0,8]})")
42
+ end
43
+
44
+ uploaded_cbs.each do |cb_with_lock|
45
+ lock = cb_with_lock.lock
46
+ table.print_row("Uploaded", lock.name, lock.version, "(#{lock.identifier[0,8]})")
47
+ end
48
+ end
49
+
50
+ def table
51
+ @table ||= TablePrinter.new(ui) do |t|
52
+ t.column(%w[ Using Uploaded ])
53
+ t.column(cookbook_names)
54
+ t.column(cookbook_version_numbers)
55
+ t.column
56
+ end
57
+ end
58
+
59
+ def cookbook_names
60
+ (reused_cbs + uploaded_cbs).map { |e| e.lock.name }
61
+ end
62
+
63
+ def cookbook_version_numbers
64
+ (reused_cbs + uploaded_cbs).map { |e| e.lock.version }
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -28,6 +28,12 @@ module ChefDK
28
28
 
29
29
  class Cookbook
30
30
 
31
+ VALID_STRING_FORMAT = /\A[^\s]+ \([^\s]+\)\Z/
32
+
33
+ def self.valid_str?(str)
34
+ !!(str =~ VALID_STRING_FORMAT)
35
+ end
36
+
31
37
  def self.parse(str)
32
38
  name, version_w_parens = str.split(' ')
33
39
  version = version_w_parens[/\(([^)]+)\)/, 1]
@@ -78,15 +84,13 @@ module ChefDK
78
84
  end
79
85
 
80
86
  def consume_lock_data(lock_data)
81
- policyfile_dependencies_data = lock_data["Policyfile"] || []
82
- policyfile_dependencies_data.each do |cookbook_name, constraint|
83
- add_policyfile_dep(cookbook_name, constraint)
84
- end
85
- cookbook_dependencies_data = lock_data["dependencies"] || {}
86
- cookbook_dependencies_data.each do |name_and_version, deps_list|
87
- cookbook = Cookbook.parse(name_and_version)
88
- add_cookbook_obj_dep(cookbook, deps_list)
87
+ unless lock_data.key?("Policyfile") and lock_data.key?("dependencies")
88
+ msg = %Q|lockfile solution_dependencies must be a Hash of the form `{"Policyfile": [], "dependencies": {} }' (got: #{lock_data.inspect})|
89
+ raise InvalidLockfile, msg
89
90
  end
91
+
92
+ set_policyfile_deps_from_lock_data(lock_data)
93
+ set_cookbook_deps_from_lock_data(lock_data)
90
94
  end
91
95
 
92
96
  def test_conflict!(cookbook_name, version)
@@ -198,6 +202,96 @@ module ChefDK
198
202
  @cookbook_dependencies[Cookbook.new(name, version)]
199
203
  end
200
204
 
205
+ def set_policyfile_deps_from_lock_data(lock_data)
206
+ policyfile_deps_data = lock_data["Policyfile"]
207
+
208
+ unless policyfile_deps_data.kind_of?(Array)
209
+ msg = "lockfile solution_dependencies Policyfile dependencies must be an array of cookbooks and constraints (got: #{policyfile_deps_data.inspect})"
210
+ raise InvalidLockfile, msg
211
+ end
212
+
213
+ policyfile_deps_data.each do |entry|
214
+ add_policyfile_dep_from_lock_data(entry)
215
+ end
216
+ end
217
+
218
+ def add_policyfile_dep_from_lock_data(entry)
219
+ unless entry.kind_of?(Array) and entry.size == 2
220
+ msg = %Q(lockfile solution_dependencies Policyfile dependencies entry must be like [ "$COOKBOOK_NAME", "$CONSTRAINT" ] (got: #{entry.inspect}))
221
+ raise InvalidLockfile, msg
222
+ end
223
+
224
+ cookbook_name, constraint = entry
225
+
226
+ unless cookbook_name.kind_of?(String) and !cookbook_name.empty?
227
+ msg = "lockfile solution_dependencies Policyfile dependencies entry. Cookbook name portion must be a string (got: #{entry.inspect})"
228
+ raise InvalidLockfile, msg
229
+ end
230
+
231
+ unless constraint.kind_of?(String) and !constraint.empty?
232
+ msg = "malformed lockfile solution_dependencies Policyfile dependencies entry. Version constraint portion must be a string (got: #{entry.inspect})"
233
+ raise InvalidLockfile, msg
234
+ end
235
+ add_policyfile_dep(cookbook_name, constraint)
236
+ rescue Semverse::InvalidConstraintFormat
237
+ msg = "malformed lockfile solution_dependencies Policyfile dependencies entry. Version constraint portion must be a valid version constraint (got: #{entry.inspect})"
238
+ raise InvalidLockfile, msg
239
+ end
240
+
241
+ def set_cookbook_deps_from_lock_data(lock_data)
242
+ cookbook_dependencies_data = lock_data["dependencies"]
243
+
244
+ unless cookbook_dependencies_data.kind_of?(Hash)
245
+ msg = "lockfile solution_dependencies dependencies entry must be a Hash (JSON object) of dependencies (got: #{cookbook_dependencies_data.inspect})"
246
+ raise InvalidLockfile, msg
247
+ end
248
+
249
+ cookbook_dependencies_data.each do |name_and_version, deps_list|
250
+ add_cookbook_dep_from_lock_data(name_and_version, deps_list)
251
+ end
252
+ end
253
+
254
+ def add_cookbook_dep_from_lock_data(name_and_version, deps_list)
255
+ unless name_and_version.kind_of?(String)
256
+ show = "#{name_and_version.inspect} => #{deps_list.inspect}"
257
+ msg = %Q(lockfile cookbook_dependencies entries must be of the form "$COOKBOOK_NAME ($VERSION)" => [ $dependency, ...] (got: #{show}) )
258
+ raise InvalidLockfile, msg
259
+ end
260
+
261
+ unless Cookbook.valid_str?(name_and_version)
262
+ msg = %Q(lockfile cookbook_dependencies entry keys must be of the form "$COOKBOOK_NAME ($VERSION)" (got: #{name_and_version.inspect}) )
263
+ raise InvalidLockfile, msg
264
+ end
265
+
266
+ unless deps_list.kind_of?(Array)
267
+ msg = %Q(lockfile cookbook_dependencies entry values must be an Array like [ [ "$COOKBOOK_NAME", "$CONSTRAINT" ], ... ] (got: #{deps_list.inspect}) )
268
+ raise InvalidLockfile, msg
269
+ end
270
+
271
+ deps_list.each do |entry|
272
+
273
+ unless entry.kind_of?(Array) and entry.size == 2
274
+ msg = %Q(lockfile solution_dependencies dependencies entry must be like [ "$COOKBOOK_NAME", "$CONSTRAINT" ] (got: #{entry.inspect}))
275
+ raise InvalidLockfile, msg
276
+ end
277
+
278
+ dep_name, constraint = entry
279
+
280
+ unless dep_name.kind_of?(String) and !dep_name.empty?
281
+ msg = "malformed lockfile solution_dependencies dependencies entry. Cookbook name portion must be a string (got: #{entry.inspect})"
282
+ raise InvalidLockfile, msg
283
+ end
284
+
285
+ unless constraint.kind_of?(String) and !constraint.empty?
286
+ msg = "malformed lockfile solution_dependencies dependencies entry. Version constraint portion must be a string (got: #{entry.inspect})"
287
+ raise InvalidLockfile, msg
288
+ end
289
+ end
290
+
291
+ cookbook = Cookbook.parse(name_and_version)
292
+ add_cookbook_obj_dep(cookbook, deps_list)
293
+ end
294
+
201
295
  end
202
296
 
203
297
  end