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.
- checksums.yaml +4 -4
- data/Rakefile +1 -1
- data/chef-apply.gemspec +2 -1
- data/lib/chef_apply/action/base.rb +1 -0
- data/lib/chef_apply/action/converge_target.rb +11 -11
- data/lib/chef_apply/action/converge_target/ccr_failure_mapper.rb +100 -0
- data/lib/chef_apply/action/generate_local_policy.rb +1 -1
- data/lib/chef_apply/action/generate_temp_cookbook.rb +53 -53
- data/lib/chef_apply/action/generate_temp_cookbook/recipe_lookup.rb +124 -0
- data/lib/chef_apply/action/generate_temp_cookbook/temp_cookbook.rb +175 -0
- data/lib/chef_apply/action/install_chef.rb +8 -8
- data/lib/chef_apply/action/install_chef/minimum_chef_version.rb +85 -0
- data/lib/chef_apply/cli.rb +10 -9
- data/lib/chef_apply/cli/options.rb +2 -2
- data/lib/chef_apply/cli/validation.rb +2 -1
- data/lib/chef_apply/file_fetcher.rb +1 -1
- data/lib/chef_apply/log.rb +4 -6
- data/lib/chef_apply/startup.rb +7 -4
- data/lib/chef_apply/target_host.rb +10 -5
- data/lib/chef_apply/target_host/linux.rb +1 -1
- data/lib/chef_apply/target_resolver.rb +3 -1
- data/lib/chef_apply/telemeter.rb +1 -1
- data/lib/chef_apply/text/error_translation.rb +1 -1
- data/lib/chef_apply/text/text_wrapper.rb +2 -1
- data/lib/chef_apply/ui/error_printer.rb +15 -13
- data/lib/chef_apply/ui/plain_text_element.rb +1 -2
- data/lib/chef_apply/ui/plain_text_header.rb +1 -1
- data/lib/chef_apply/ui/terminal.rb +4 -4
- data/lib/chef_apply/version.rb +1 -1
- data/spec/spec_helper.rb +43 -3
- data/spec/unit/action/base_spec.rb +2 -1
- data/spec/unit/{errors → action/converge_target}/ccr_failure_mapper_spec.rb +12 -9
- data/spec/unit/action/converge_target_spec.rb +21 -22
- data/spec/unit/action/generate_local_policy_spec.rb +5 -5
- data/spec/unit/{recipe_lookup_spec.rb → action/generate_temp_cookbook/recipe_lookup_spec.rb} +10 -10
- data/spec/unit/{temp_cookbook_spec.rb → action/generate_temp_cookbook/temp_cookbook_spec.rb} +23 -24
- data/spec/unit/action/generate_temp_cookbook_spec.rb +4 -6
- data/spec/unit/{minimum_chef_version_spec.rb → action/install_chef/minimum_chef_version_spec.rb} +13 -13
- data/spec/unit/action/install_chef_spec.rb +4 -4
- data/spec/unit/cli/options_spec.rb +17 -17
- data/spec/unit/cli/validation_spec.rb +6 -3
- data/spec/unit/cli_spec.rb +12 -9
- data/spec/unit/log_spec.rb +1 -1
- data/spec/unit/startup_spec.rb +11 -12
- data/spec/unit/target_host/windows_spec.rb +1 -1
- data/spec/unit/target_host_spec.rb +3 -2
- data/spec/unit/telemeter_spec.rb +5 -5
- data/spec/unit/text/error_translation_spec.rb +11 -7
- data/spec/unit/ui/error_printer_spec.rb +6 -7
- metadata +24 -11
- data/Gemfile.lock +0 -400
- data/lib/chef_apply/errors/ccr_failure_mapper.rb +0 -93
- data/lib/chef_apply/minimum_chef_version.rb +0 -79
- data/lib/chef_apply/recipe_lookup.rb +0 -117
- data/lib/chef_apply/temp_cookbook.rb +0 -170
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 79e301aab0d20ec072e87597869b55d24958945f0fb1cfa774841929cb5eaa80
|
4
|
+
data.tar.gz: e34804469b8ed16dfc9729f3229abc60d0714358146f04ca3a494a70dcef6a96
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 251203d054f4ee1c8319b4b12e40a2778008f603e79530750875686facc0b2a8ec72e40425a1f643e91187882224b04af6a0c6d36698f7d341a35e104ed2c036
|
7
|
+
data.tar.gz: 107872f223933ca095df7e46afbf22f1260c05339531fb8c51c2e54007203e63f4e4ea4089f30abd75e2a20bd09bd1d4e56d44d21fb278e5e36e04d5db655ff7
|
data/Rakefile
CHANGED
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
|
@@ -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
|
-
|
42
|
-
|
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
|
-
|
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/
|
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 =
|
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
|
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
|
-
|
23
|
-
|
24
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
def perform_action
|
43
|
+
notify(:generating)
|
44
|
+
generate
|
45
|
+
notify(:success)
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
notify(:success)
|
48
|
+
def generate
|
49
|
+
raise NotImplemented
|
50
|
+
end
|
47
51
|
end
|
48
52
|
|
49
|
-
|
50
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
74
|
-
|
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
|