chef-apply 0.3.3 → 0.4.6

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/chef-apply.gemspec +2 -1
  4. data/lib/chef_apply/action/base.rb +1 -0
  5. data/lib/chef_apply/action/converge_target.rb +11 -11
  6. data/lib/chef_apply/action/converge_target/ccr_failure_mapper.rb +100 -0
  7. data/lib/chef_apply/action/generate_local_policy.rb +1 -1
  8. data/lib/chef_apply/action/generate_temp_cookbook.rb +53 -53
  9. data/lib/chef_apply/action/generate_temp_cookbook/recipe_lookup.rb +124 -0
  10. data/lib/chef_apply/action/generate_temp_cookbook/temp_cookbook.rb +175 -0
  11. data/lib/chef_apply/action/install_chef.rb +8 -8
  12. data/lib/chef_apply/action/install_chef/minimum_chef_version.rb +85 -0
  13. data/lib/chef_apply/cli.rb +10 -9
  14. data/lib/chef_apply/cli/options.rb +2 -2
  15. data/lib/chef_apply/cli/validation.rb +2 -1
  16. data/lib/chef_apply/file_fetcher.rb +1 -1
  17. data/lib/chef_apply/log.rb +4 -6
  18. data/lib/chef_apply/startup.rb +7 -4
  19. data/lib/chef_apply/target_host.rb +10 -5
  20. data/lib/chef_apply/target_host/linux.rb +1 -1
  21. data/lib/chef_apply/target_resolver.rb +3 -1
  22. data/lib/chef_apply/telemeter.rb +1 -1
  23. data/lib/chef_apply/text/error_translation.rb +1 -1
  24. data/lib/chef_apply/text/text_wrapper.rb +2 -1
  25. data/lib/chef_apply/ui/error_printer.rb +15 -13
  26. data/lib/chef_apply/ui/plain_text_element.rb +1 -2
  27. data/lib/chef_apply/ui/plain_text_header.rb +1 -1
  28. data/lib/chef_apply/ui/terminal.rb +4 -4
  29. data/lib/chef_apply/version.rb +1 -1
  30. data/spec/spec_helper.rb +43 -3
  31. data/spec/unit/action/base_spec.rb +2 -1
  32. data/spec/unit/{errors → action/converge_target}/ccr_failure_mapper_spec.rb +12 -9
  33. data/spec/unit/action/converge_target_spec.rb +21 -22
  34. data/spec/unit/action/generate_local_policy_spec.rb +5 -5
  35. data/spec/unit/{recipe_lookup_spec.rb → action/generate_temp_cookbook/recipe_lookup_spec.rb} +10 -10
  36. data/spec/unit/{temp_cookbook_spec.rb → action/generate_temp_cookbook/temp_cookbook_spec.rb} +23 -24
  37. data/spec/unit/action/generate_temp_cookbook_spec.rb +4 -6
  38. data/spec/unit/{minimum_chef_version_spec.rb → action/install_chef/minimum_chef_version_spec.rb} +13 -13
  39. data/spec/unit/action/install_chef_spec.rb +4 -4
  40. data/spec/unit/cli/options_spec.rb +17 -17
  41. data/spec/unit/cli/validation_spec.rb +6 -3
  42. data/spec/unit/cli_spec.rb +12 -9
  43. data/spec/unit/log_spec.rb +1 -1
  44. data/spec/unit/startup_spec.rb +11 -12
  45. data/spec/unit/target_host/windows_spec.rb +1 -1
  46. data/spec/unit/target_host_spec.rb +3 -2
  47. data/spec/unit/telemeter_spec.rb +5 -5
  48. data/spec/unit/text/error_translation_spec.rb +11 -7
  49. data/spec/unit/ui/error_printer_spec.rb +6 -7
  50. metadata +24 -11
  51. data/Gemfile.lock +0 -400
  52. data/lib/chef_apply/errors/ccr_failure_mapper.rb +0 -93
  53. data/lib/chef_apply/minimum_chef_version.rb +0 -79
  54. data/lib/chef_apply/recipe_lookup.rb +0 -117
  55. data/lib/chef_apply/temp_cookbook.rb +0 -170
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67da1832dc2cff37ca34e9e6cd98a740915779c88ff2367096676c6204d6f390
4
- data.tar.gz: 8febd165b01039b41427ba7406c209436ece3ac6dc8e93ab4fbfbd1a186e994e
3
+ metadata.gz: 79e301aab0d20ec072e87597869b55d24958945f0fb1cfa774841929cb5eaa80
4
+ data.tar.gz: e34804469b8ed16dfc9729f3229abc60d0714358146f04ca3a494a70dcef6a96
5
5
  SHA512:
6
- metadata.gz: 20a22779d89a97deed72a0e36fe93ebe489b2004368a5e87e4b5e69c668238e5747af817bf99d4d628c7ca583f91887c810441be9e5326068f6b89809f48a7b2
7
- data.tar.gz: d7d32da1d04bfb11b7fcd4e886bfe57fdb8517d0c6d3c21243a5a0279a969c324d2c4ac77536bf8c589ebb3c11aeabb0b3fe2ee2d308dff53add6fb6cbde8e32
6
+ metadata.gz: 251203d054f4ee1c8319b4b12e40a2778008f603e79530750875686facc0b2a8ec72e40425a1f643e91187882224b04af6a0c6d36698f7d341a35e104ed2c036
7
+ data.tar.gz: 107872f223933ca095df7e46afbf22f1260c05339531fb8c51c2e54007203e63f4e4ea4089f30abd75e2a20bd09bd1d4e56d44d21fb278e5e36e04d5db655ff7
data/Rakefile CHANGED
@@ -39,4 +39,4 @@ task :console do
39
39
  IRB.start
40
40
  end
41
41
 
42
- task default: [:style, :spec]
42
+ task default: %i{style spec}
data/chef-apply.gemspec CHANGED
@@ -48,7 +48,8 @@ Gem::Specification.new do |spec|
48
48
  # localization gem...
49
49
  spec.add_dependency "toml-rb" # This isn't ideal because mixlib-config uses 'tomlrb'
50
50
  # but that library does not support a dumper
51
- spec.add_dependency "train" # remote connection management over ssh, winrm
51
+ spec.add_dependency "train", "~> 3.0" # remote connection management over ssh, winrm
52
+ spec.add_dependency "train-winrm" # winrm transports were pulled out into this plugin
52
53
  spec.add_dependency "pastel" # A color library
53
54
  spec.add_dependency "tty-spinner" # Pretty output for status updates in the CLI
54
55
  spec.add_dependency "chef", ">= 15.0" # Needed to load cookbooks
@@ -65,6 +65,7 @@ module ChefApply
65
65
 
66
66
  def notify(action, *args)
67
67
  return if @notification_handler.nil?
68
+
68
69
  ChefApply::Log.debug("[#{self.class.name}] Action: #{action}, Action Data: #{args}")
69
70
  @notification_handler.call(action, args) if @notification_handler
70
71
  end
@@ -26,7 +26,7 @@ module ChefApply::Action
26
26
 
27
27
  def perform_action
28
28
  local_policy_path = config.delete :local_policy_path
29
- remote_tmp = target_host.temp_dir()
29
+ remote_tmp = target_host.temp_dir
30
30
  remote_dir_path = target_host.normalize_path(remote_tmp)
31
31
  # Ensure the directory is owned by the connecting user,
32
32
  # otherwise we won't be able to put things into it over scp as that user.
@@ -38,8 +38,8 @@ module ChefApply::Action
38
38
  notify(:running_chef)
39
39
  # TODO - just teach target_host how to run_chef?
40
40
  cmd_str = run_chef_cmd(remote_dir_path,
41
- File.basename(remote_config_path),
42
- File.basename(remote_policy_path))
41
+ File.basename(remote_config_path),
42
+ File.basename(remote_policy_path))
43
43
  c = target_host.run_command(cmd_str)
44
44
  target_host.del_dir(remote_dir_path)
45
45
  if c.exit_status == 0
@@ -52,7 +52,7 @@ module ChefApply::Action
52
52
  ChefApply::Log.error("Error running command [#{cmd_str}]")
53
53
  ChefApply::Log.error("stdout: #{c.stdout}")
54
54
  ChefApply::Log.error("stderr: #{c.stderr}")
55
- handle_ccr_error()
55
+ handle_ccr_error
56
56
  end
57
57
  end
58
58
 
@@ -63,7 +63,7 @@ module ChefApply::Action
63
63
  target_host.upload_file(local_policy_path, remote_policy_path)
64
64
  rescue RuntimeError => e
65
65
  ChefApply::Log.error(e)
66
- raise PolicyUploadFailed.new()
66
+ raise PolicyUploadFailed.new
67
67
  end
68
68
  remote_policy_path
69
69
  end
@@ -92,7 +92,7 @@ module ChefApply::Action
92
92
  # (we don't set a location because we want output to
93
93
  # go in stdout for reporting back to chef-apply)
94
94
  log_settings = ChefApply::Config.log
95
- if !log_settings.target_level.nil?
95
+ unless log_settings.target_level.nil?
96
96
  workstation_rb << <<~EOM
97
97
  log_level :#{log_settings.target_level}
98
98
  EOM
@@ -115,7 +115,7 @@ module ChefApply::Action
115
115
  config_file.close
116
116
  target_host.upload_file(config_file.path, remote_config_path)
117
117
  rescue RuntimeError
118
- raise ConfigUploadFailed.new()
118
+ raise ConfigUploadFailed.new
119
119
  ensure
120
120
  config_file.unlink
121
121
  end
@@ -126,14 +126,14 @@ module ChefApply::Action
126
126
  remote_handler_path = File.join(remote_dir, "reporter.rb")
127
127
  begin
128
128
  # TODO - why don't we upload the original remote_handler_path instead of making a temp copy?
129
- handler_file = Tempfile.new()
129
+ handler_file = Tempfile.new
130
130
  # TODO - ideally this is a resource in the gem, and not placed in with source files.
131
131
  handler_file.write(File.read(File.join(__dir__, "reporter.rb")))
132
132
  handler_file.close
133
133
  target_host.upload_file(handler_file.path, remote_handler_path)
134
134
  # TODO - should we be more specific in our error catch?
135
135
  rescue RuntimeError
136
- raise HandlerUploadFailed.new()
136
+ raise HandlerUploadFailed.new
137
137
  ensure
138
138
  handler_file.unlink
139
139
  end
@@ -159,7 +159,7 @@ module ChefApply::Action
159
159
  end
160
160
 
161
161
  def handle_ccr_error
162
- require "chef_apply/errors/ccr_failure_mapper"
162
+ require "chef_apply/action/converge_target/ccr_failure_mapper"
163
163
  mapper_opts = {}
164
164
  content = target_host.fetch_file_contents(chef_report_path)
165
165
  if content.nil?
@@ -175,7 +175,7 @@ module ChefApply::Action
175
175
  ChefApply::Log.error("Remote chef-client error follows:")
176
176
  ChefApply::Log.error(report["exception"])
177
177
  end
178
- mapper = ChefApply::Errors::CCRFailureMapper.new(report["exception"], mapper_opts)
178
+ mapper = ConvergeTarget::CCRFailureMapper.new(report["exception"], mapper_opts)
179
179
  mapper.raise_mapped_exception!
180
180
  end
181
181
 
@@ -0,0 +1,100 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 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_apply/error"
19
+
20
+ module ChefApply
21
+ module Action
22
+ class ConvergeTarget
23
+ # This converts chef client run failures
24
+ # to human-friendly exceptions with detail
25
+ # and remediation steps based on the failure type.
26
+ class CCRFailureMapper
27
+ attr_reader :params
28
+
29
+ def initialize(exception, params)
30
+ @params = params
31
+ @cause_line = exception
32
+ end
33
+
34
+ def raise_mapped_exception!
35
+ if @cause_line.nil?
36
+ raise RemoteChefRunFailedToResolveError.new(params[:failed_report_path])
37
+ else
38
+ errid, *args = exception_args_from_cause
39
+ if errid.nil?
40
+ raise RemoteChefClientRunFailedUnknownReason.new
41
+ else
42
+ raise RemoteChefClientRunFailed.new(errid, *args)
43
+ end
44
+
45
+ end
46
+ end
47
+
48
+ # Ideally we will write a custom handler to package up data we care
49
+ # about and present it more directly https://docs.chef.io/handlers.html
50
+ # For now, we'll just match the most common failures based on their
51
+ # messages.
52
+ def exception_args_from_cause
53
+ # Ordering is important below. Some earlier tests are more detailed
54
+ # cases of things that will match more general tests further down.
55
+ case @cause_line
56
+ when /.*had an error:(.*:)\s+(.*$)/
57
+ # Some invalid property value cases, among others.
58
+ ["CHEFCCR002", $2]
59
+ when /.*Chef::Exceptions::ValidationFailed:\s+Option action must be equal to one of:\s+(.*)!\s+You passed :(.*)\./
60
+ # Invalid action - specialization of invalid property value, below
61
+ ["CHEFCCR003", $2, $1]
62
+ when /.*Chef::Exceptions::ValidationFailed:\s+(.*)/
63
+ # Invalid resource property value
64
+ ["CHEFCCR004", $1]
65
+ when /.*NameError: undefined local variable or method `(.+)' for cookbook.+/
66
+ # Invalid resource type in most cases
67
+ ["CHEFCCR005", $1]
68
+ when /.*NoMethodError: undefined method `(.+)' for cookbook.+/
69
+ # Invalid resource type in most cases
70
+ ["CHEFCCR005", $1]
71
+ when /.*undefined method `(.*)' for (.+)/
72
+ # Unknown resource property
73
+ ["CHEFCCR006", $1, $2]
74
+
75
+ # Below would catch the general form of most errors, but the
76
+ # message itself in those lines is not generally aligned
77
+ # with the UX we want to provide.
78
+ # when /.*Exception|Error.*:\s+(.*)/
79
+ else
80
+ nil
81
+ end
82
+ end
83
+
84
+ class RemoteChefClientRunFailed < ChefApply::ErrorNoLogs
85
+ def initialize(id, *args); super(id, *args); end
86
+ end
87
+
88
+ class RemoteChefClientRunFailedUnknownReason < ChefApply::ErrorNoStack
89
+ def initialize(); super("CHEFCCR099"); end
90
+ end
91
+
92
+ class RemoteChefRunFailedToResolveError < ChefApply::ErrorNoStack
93
+ def initialize(path); super("CHEFCCR001", path); end
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -49,7 +49,7 @@ module ChefApply::Action
49
49
  require "chef-cli/policyfile_services/install"
50
50
  require "chef-cli/ui"
51
51
  @installer ||=
52
- ChefCLI::PolicyfileServices::Install.new(ui: ChefCLI::UI.null(), root_dir: @cookbook.path)
52
+ ChefCLI::PolicyfileServices::Install.new(ui: ChefCLI::UI.null, root_dir: @cookbook.path)
53
53
  end
54
54
 
55
55
  end
@@ -14,73 +14,73 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
  #
17
-
18
17
  require "chef_apply/action/base"
19
- require "chef_apply/temp_cookbook"
20
18
  require "chef_apply/error"
19
+ module ChefApply
20
+ module Action
21
+ class GenerateTempCookbook < Base
22
+ attr_reader :generated_cookbook
21
23
 
22
- module ChefApply::Action
23
- class GenerateTempCookbook < Base
24
- attr_reader :generated_cookbook
24
+ def self.from_options(opts)
25
+ if opts.key?(:recipe_spec)
26
+ GenerateCookbookFromRecipe.new(opts)
27
+ elsif opts.key?(:resource_name) &&
28
+ opts.key?(:resource_type) &&
29
+ opts.key?(:resource_properties)
30
+ GenerateCookbookFromResource.new(opts)
31
+ else
32
+ raise MissingOptions.new(opts)
33
+ end
34
+ end
25
35
 
26
- def self.from_options(opts)
27
- if opts.key?(:recipe_spec)
28
- GenerateCookbookFromRecipe.new(opts)
29
- elsif opts.key?(:resource_name) &&
30
- opts.key?(:resource_type) &&
31
- opts.key?(:resource_properties)
32
- GenerateCookbookFromResource.new(opts)
33
- else
34
- raise MissingOptions.new(opts)
36
+ def initialize(options)
37
+ super(options)
38
+ require "chef_apply/action/generate_temp_cookbook/temp_cookbook"
39
+ @generated_cookbook ||= TempCookbook.new
35
40
  end
36
- end
37
41
 
38
- def initialize(options)
39
- super(options)
40
- @generated_cookbook ||= ChefApply::TempCookbook.new
41
- end
42
+ def perform_action
43
+ notify(:generating)
44
+ generate
45
+ notify(:success)
46
+ end
42
47
 
43
- def perform_action
44
- notify(:generating)
45
- generate
46
- notify(:success)
48
+ def generate
49
+ raise NotImplemented
50
+ end
47
51
  end
48
52
 
49
- def generate
50
- raise NotImplemented
53
+ class GenerateCookbookFromRecipe < GenerateTempCookbook
54
+ def generate
55
+ recipe_specifier = config.delete :recipe_spec
56
+ repo_paths = config.delete :cookbook_repo_paths
57
+ ChefApply::Log.debug("Beginning to look for recipe specified as #{recipe_specifier}")
58
+ if File.file?(recipe_specifier)
59
+ ChefApply::Log.debug("#{recipe_specifier} is a valid path to a recipe")
60
+ recipe_path = recipe_specifier
61
+ else
62
+ require "chef_apply/action/generate_temp_cookbook/recipe_lookup"
63
+ rl = RecipeLookup.new(repo_paths)
64
+ cookbook_path_or_name, optional_recipe_name = rl.split(recipe_specifier)
65
+ cookbook = rl.load_cookbook(cookbook_path_or_name)
66
+ recipe_path = rl.find_recipe(cookbook, optional_recipe_name)
67
+ end
68
+ generated_cookbook.from_existing_recipe(recipe_path)
69
+ end
51
70
  end
52
- end
53
71
 
54
- class GenerateCookbookFromRecipe < GenerateTempCookbook
55
- def generate
56
- recipe_specifier = config.delete :recipe_spec
57
- repo_paths = config.delete :cookbook_repo_paths
58
- ChefApply::Log.debug("Beginning to look for recipe specified as #{recipe_specifier}")
59
- if File.file?(recipe_specifier)
60
- ChefApply::Log.debug("#{recipe_specifier} is a valid path to a recipe")
61
- recipe_path = recipe_specifier
62
- else
63
- require "chef_apply/recipe_lookup"
64
- rl = ChefApply::RecipeLookup.new(repo_paths)
65
- cookbook_path_or_name, optional_recipe_name = rl.split(recipe_specifier)
66
- cookbook = rl.load_cookbook(cookbook_path_or_name)
67
- recipe_path = rl.find_recipe(cookbook, optional_recipe_name)
72
+ class GenerateCookbookFromResource < GenerateTempCookbook
73
+ def generate
74
+ type = config.delete :resource_type
75
+ name = config.delete :resource_name
76
+ props = config.delete :resource_properties
77
+ ChefApply::Log.debug("Generating cookbook for ad-hoc resource #{type}[#{name}]")
78
+ generated_cookbook.from_resource(type, name, props)
68
79
  end
69
- generated_cookbook.from_existing_recipe(recipe_path)
70
80
  end
71
- end
72
81
 
73
- class GenerateCookbookFromResource < GenerateTempCookbook
74
- def generate
75
- type = config.delete :resource_type
76
- name = config.delete :resource_name
77
- props = config.delete :resource_properties
78
- ChefApply::Log.debug("Generating cookbook for ad-hoc resource #{type}[#{name}]")
79
- generated_cookbook.from_resource(type, name, props)
82
+ class MissingOptions < ChefApply::APIError
83
+ def initialize(*args); super("CHEFAPI001", *args); end
80
84
  end
81
85
  end
82
-
83
- class MissingOptions < ChefApply::APIError
84
- def initialize(*args); super("CHEFAPI001", *args); end
85
- end
86
86
  end
@@ -0,0 +1,124 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 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-config/config"
19
+ require "chef_apply/config"
20
+ require "chef_apply/error"
21
+ require "chef_apply/log"
22
+ require "chef_apply/action/base"
23
+
24
+ module ChefApply
25
+ module Action
26
+ class GenerateTempCookbook
27
+ # When users are trying to converge a local recipe on a remote target, there
28
+ # is a very specific (but expansive) set of things they can specify. This
29
+ # class encapsulates that logic for testing purposes. We either return
30
+ # a path to a recipe or we raise an error.
31
+ class RecipeLookup
32
+
33
+ attr_reader :cookbook_repo_paths
34
+ def initialize(cookbook_repo_paths)
35
+ @cookbook_repo_paths = cookbook_repo_paths
36
+ end
37
+
38
+ # The recipe specifier is provided by the customer as either a path OR
39
+ # a cookbook and optional recipe name.
40
+ def split(recipe_specifier)
41
+ recipe_specifier.split("::")
42
+ end
43
+
44
+ # Given a cookbook path or name, try to load that cookbook. Either return
45
+ # a cookbook object or raise an error.
46
+ def load_cookbook(path_or_name)
47
+ require "chef/exceptions"
48
+ if File.directory?(path_or_name)
49
+ cookbook_path = path_or_name
50
+ # First, is there a cookbook in the specified dir that matches?
51
+ require "chef/cookbook/cookbook_version_loader"
52
+ begin
53
+ v = Chef::Cookbook::CookbookVersionLoader.new(cookbook_path)
54
+ v.load!
55
+ cookbook = v.cookbook_version
56
+ rescue Chef::Exceptions::CookbookNotFoundInRepo
57
+ raise InvalidCookbook.new(cookbook_path)
58
+ end
59
+ else
60
+ cookbook_name = path_or_name
61
+ # Second, is there a cookbook in their local repository that matches?
62
+ require "chef/cookbook_loader"
63
+ cb_loader = Chef::CookbookLoader.new(cookbook_repo_paths)
64
+ cb_loader.load_cookbooks
65
+
66
+ begin
67
+ cookbook = cb_loader[cookbook_name]
68
+ rescue Chef::Exceptions::CookbookNotFoundInRepo
69
+ cookbook_repo_paths.each do |repo_path|
70
+ cookbook_path = File.join(repo_path, cookbook_name)
71
+ if File.directory?(cookbook_path)
72
+ raise InvalidCookbook.new(cookbook_path)
73
+ end
74
+ end
75
+ raise CookbookNotFound.new(cookbook_name, cookbook_repo_paths)
76
+ end
77
+ end
78
+ cookbook
79
+ end
80
+
81
+ # Find the specified recipe or default recipe if none is specified.
82
+ # Raise an error if recipe cannot be found.
83
+ def find_recipe(cookbook, recipe_name = nil)
84
+ recipes = cookbook.recipe_filenames_by_name
85
+ if recipe_name.nil?
86
+ default_recipe = recipes["default"]
87
+ raise NoDefaultRecipe.new(cookbook.root_dir, cookbook.name) if default_recipe.nil?
88
+
89
+ default_recipe
90
+ else
91
+ recipe = recipes[recipe_name]
92
+ raise RecipeNotFound.new(cookbook.root_dir, recipe_name, recipes.keys, cookbook.name) if recipe.nil?
93
+
94
+ recipe
95
+ end
96
+ end
97
+
98
+ class InvalidCookbook < ChefApply::Error
99
+ def initialize(cookbook_path); super("CHEFVAL005", cookbook_path); end
100
+ end
101
+
102
+ class CookbookNotFound < ChefApply::Error
103
+ def initialize(cookbook_name, repo_paths)
104
+ repo_paths = repo_paths.join("\n")
105
+ super("CHEFVAL006", cookbook_name, repo_paths)
106
+ end
107
+ end
108
+
109
+ class NoDefaultRecipe < ChefApply::Error
110
+ def initialize(cookbook_path, cookbook_name); super("CHEFVAL007", cookbook_path, cookbook_name); end
111
+ end
112
+
113
+ class RecipeNotFound < ChefApply::Error
114
+ def initialize(cookbook_path, recipe_name, available_recipes, cookbook_name)
115
+ available_recipes.map! { |r| "'#{r}'" }
116
+ available_recipes = available_recipes.join(", ")
117
+ super("CHEFVAL008", cookbook_path, recipe_name, available_recipes, cookbook_name)
118
+ end
119
+ end
120
+
121
+ end
122
+ end
123
+ end
124
+ end