chef-dk 0.2.1 → 0.3.0

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