chef-apply 0.3.3 → 0.4.6

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