right_link 5.9.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.
- data/actors/agent_manager.rb +88 -0
- data/actors/instance_scheduler.rb +321 -0
- data/actors/instance_services.rb +64 -0
- data/actors/instance_setup.rb +567 -0
- data/bin/cloud +25 -0
- data/bin/cook_runner +44 -0
- data/bin/deploy +120 -0
- data/bin/enroll +385 -0
- data/bin/rad +32 -0
- data/bin/rchk +29 -0
- data/bin/rnac +39 -0
- data/bin/rs_connect +33 -0
- data/bin/rs_log_level +31 -0
- data/bin/rs_ohai +28 -0
- data/bin/rs_reenroll +31 -0
- data/bin/rs_run_recipe +34 -0
- data/bin/rs_run_right_script +34 -0
- data/bin/rs_shutdown +33 -0
- data/bin/rs_tag +33 -0
- data/bin/rs_thunk +33 -0
- data/bin/rstat +31 -0
- data/bin/system +16 -0
- data/ext/Rakefile +18 -0
- data/init/config.yml +5 -0
- data/init/init.rb +79 -0
- data/lib/chef/ohai_setup.rb +51 -0
- data/lib/chef/plugins/cloud.rb +91 -0
- data/lib/chef/plugins/cloudstack.rb +23 -0
- data/lib/chef/plugins/ec2.rb +23 -0
- data/lib/chef/plugins/linux/block_device2.rb +24 -0
- data/lib/chef/plugins/rackspace.rb +23 -0
- data/lib/chef/plugins/rightscale.rb +125 -0
- data/lib/chef/plugins/windows/network.rb +114 -0
- data/lib/chef/plugins.rb +74 -0
- data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
- data/lib/chef/providers/dns_resource.rb +100 -0
- data/lib/chef/providers/executable_schedule_provider.rb +70 -0
- data/lib/chef/providers/executable_schedule_resource.rb +144 -0
- data/lib/chef/providers/remote_recipe_provider.rb +86 -0
- data/lib/chef/providers/remote_recipe_resource.rb +101 -0
- data/lib/chef/providers/right_link_tag_provider.rb +73 -0
- data/lib/chef/providers/right_link_tag_resource.rb +59 -0
- data/lib/chef/providers/right_script_provider.rb +190 -0
- data/lib/chef/providers/right_script_resource.rb +113 -0
- data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
- data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
- data/lib/chef/providers/server_collection_provider.rb +66 -0
- data/lib/chef/providers/server_collection_resource.rb +93 -0
- data/lib/chef/providers/windows/powershell_provider.rb +151 -0
- data/lib/chef/providers/windows/powershell_resource.rb +111 -0
- data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
- data/lib/chef/right_providers.rb +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
- data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
- data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
- data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
- data/lib/chef/windows/chef_node_server.rb +463 -0
- data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
- data/lib/chef/windows/pipe_server.rb +283 -0
- data/lib/chef/windows/powershell_host.rb +285 -0
- data/lib/chef/windows/powershell_pipe_server.rb +136 -0
- data/lib/chef/windows/powershell_provider_base.rb +92 -0
- data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
- data/lib/clouds/cloud.rb +557 -0
- data/lib/clouds/cloud_factory.rb +250 -0
- data/lib/clouds/cloud_utilities.rb +244 -0
- data/lib/clouds/clouds/azure.rb +106 -0
- data/lib/clouds/clouds/cloudstack.rb +114 -0
- data/lib/clouds/clouds/ec2.rb +113 -0
- data/lib/clouds/clouds/eucalyptus.rb +46 -0
- data/lib/clouds/clouds/google.rb +102 -0
- data/lib/clouds/clouds/none.rb +76 -0
- data/lib/clouds/clouds/openstack.rb +30 -0
- data/lib/clouds/clouds/rackspace-ng.rb +54 -0
- data/lib/clouds/clouds/rackspace.rb +78 -0
- data/lib/clouds/clouds/softlayer.rb +91 -0
- data/lib/clouds/metadata_formatter.rb +108 -0
- data/lib/clouds/metadata_provider.rb +128 -0
- data/lib/clouds/metadata_source.rb +87 -0
- data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
- data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
- data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
- data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
- data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
- data/lib/clouds/metadata_tree_climber.rb +144 -0
- data/lib/clouds/metadata_writer.rb +155 -0
- data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
- data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
- data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
- data/lib/clouds/register_clouds.rb +34 -0
- data/lib/clouds.rb +32 -0
- data/lib/gem_dependencies.rb +83 -0
- data/lib/git_hooks/commit-msg.rb +7 -0
- data/lib/instance/agent_config.rb +168 -0
- data/lib/instance/agent_watcher.rb +233 -0
- data/lib/instance/audit_cook_stub.rb +104 -0
- data/lib/instance/audit_proxy.rb +247 -0
- data/lib/instance/bundle_queue.rb +104 -0
- data/lib/instance/cook/agent_connection.rb +109 -0
- data/lib/instance/cook/audit_logger.rb +165 -0
- data/lib/instance/cook/audit_stub.rb +142 -0
- data/lib/instance/cook/ca-bundle.crt +2794 -0
- data/lib/instance/cook/chef_state.rb +211 -0
- data/lib/instance/cook/cook.rb +306 -0
- data/lib/instance/cook/cook_state.rb +298 -0
- data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
- data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
- data/lib/instance/cook/executable_sequence.rb +765 -0
- data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
- data/lib/instance/cook/repose_downloader.rb +349 -0
- data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
- data/lib/instance/cook.rb +41 -0
- data/lib/instance/downloader.rb +208 -0
- data/lib/instance/duplicable.rb +67 -0
- data/lib/instance/exceptions.rb +49 -0
- data/lib/instance/executable_sequence_proxy.rb +278 -0
- data/lib/instance/instance_commands.rb +577 -0
- data/lib/instance/instance_state.rb +633 -0
- data/lib/instance/json_utilities.rb +102 -0
- data/lib/instance/login_manager.rb +533 -0
- data/lib/instance/login_user_manager.rb +522 -0
- data/lib/instance/message_encoder.rb +118 -0
- data/lib/instance/multi_thread_bundle_queue.rb +232 -0
- data/lib/instance/operation_context.rb +60 -0
- data/lib/instance/options_bag.rb +65 -0
- data/lib/instance/payload_formatter.rb +46 -0
- data/lib/instance/policy.rb +53 -0
- data/lib/instance/policy_audit.rb +100 -0
- data/lib/instance/policy_manager.rb +146 -0
- data/lib/instance/reenroll_manager.rb +104 -0
- data/lib/instance/right_scripts_cookbook.rb +181 -0
- data/lib/instance/shutdown_request.rb +221 -0
- data/lib/instance/single_thread_bundle_queue.rb +189 -0
- data/lib/instance/volume_management.rb +450 -0
- data/lib/instance.rb +50 -0
- data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
- data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
- data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
- data/lib/repo_conf_generators/rightscale_key.pub +17 -0
- data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
- data/lib/repo_conf_generators.rb +30 -0
- data/lib/run_shell.rb +28 -0
- data/scripts/agent_checker.rb +571 -0
- data/scripts/agent_controller.rb +247 -0
- data/scripts/agent_deployer.rb +148 -0
- data/scripts/bundle_runner.rb +336 -0
- data/scripts/cloud_controller.rb +176 -0
- data/scripts/log_level_manager.rb +142 -0
- data/scripts/ohai_runner.rb +33 -0
- data/scripts/reenroller.rb +193 -0
- data/scripts/server_importer.rb +293 -0
- data/scripts/shutdown_client.rb +183 -0
- data/scripts/system_configurator.rb +367 -0
- data/scripts/tagger.rb +381 -0
- data/scripts/thunker.rb +356 -0
- metadata +418 -0
@@ -0,0 +1,765 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2009-2012 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
|
23
|
+
require 'right_http_connection'
|
24
|
+
require 'right_popen'
|
25
|
+
require 'socket'
|
26
|
+
require 'tempfile'
|
27
|
+
require 'fileutils'
|
28
|
+
|
29
|
+
# The daemonize method of AR clashes with the daemonize Chef attribute, we don't need that method so undef it
|
30
|
+
undef :daemonize if methods.include?('daemonize')
|
31
|
+
|
32
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), '..', '..', 'chef', 'ohai_setup'))
|
33
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), 'cookbook_path_mapping'))
|
34
|
+
require File.normalize_path(File.join(File.dirname(__FILE__), 'cookbook_repo_retriever'))
|
35
|
+
|
36
|
+
class File
|
37
|
+
class << self
|
38
|
+
unless method_defined?(:world_writable?)
|
39
|
+
def world_writable?(filename)
|
40
|
+
(File.stat(filename).mode & 0002) != 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module RightScale
|
47
|
+
# Bundle sequence, includes installing dependent packages,
|
48
|
+
# downloading attachments and running scripts in given bundle.
|
49
|
+
# Also downloads cookbooks and run recipes in given bundle.
|
50
|
+
# Runs in separate (runner) process.
|
51
|
+
class ExecutableSequence
|
52
|
+
include EM::Deferrable
|
53
|
+
|
54
|
+
# Min number of seconds to wait before retrying Ohai to get the hostname
|
55
|
+
OHAI_RETRY_MIN_DELAY = 20
|
56
|
+
# Max number of seconds to wait before retrying Ohai to get the hostname
|
57
|
+
OHAI_RETRY_MAX_DELAY = 20 * 60
|
58
|
+
# Regexp to use when reporting extended information about Chef failures (line-number, etc)
|
59
|
+
BACKTRACE_LINE_REGEXP = /(.+):(\d+):in `(.+)'/
|
60
|
+
|
61
|
+
# Patch to be applied to inputs stored in core
|
62
|
+
attr_accessor :inputs_patch
|
63
|
+
|
64
|
+
# Failure title and message if any
|
65
|
+
attr_reader :failure_title, :failure_message
|
66
|
+
|
67
|
+
# Initialize sequence
|
68
|
+
#
|
69
|
+
# === Parameter
|
70
|
+
# bundle(RightScale::ExecutableBundle):: Bundle to be run
|
71
|
+
def initialize(bundle)
|
72
|
+
@description = bundle.to_s
|
73
|
+
@thread_name = get_thread_name_from_bundle(bundle)
|
74
|
+
@right_scripts_cookbook = RightScriptsCookbook.new(@thread_name)
|
75
|
+
@scripts = bundle.executables.select { |e| e.is_a?(RightScriptInstantiation) }
|
76
|
+
run_list_recipes = bundle.executables.map { |e| e.is_a?(RecipeInstantiation) ? e : @right_scripts_cookbook.recipe_from_right_script(e) }
|
77
|
+
@cookbooks = bundle.cookbooks
|
78
|
+
@downloader = ReposeDownloader.new(bundle.repose_servers)
|
79
|
+
@downloader.logger = Log
|
80
|
+
@download_path = File.join(AgentConfig.cookbook_download_dir, @thread_name)
|
81
|
+
@powershell_providers = nil
|
82
|
+
@ohai_retry_delay = OHAI_RETRY_MIN_DELAY
|
83
|
+
@audit = AuditStub.instance
|
84
|
+
@logger = Log
|
85
|
+
@cookbook_repo_retriever= CookbookRepoRetriever.new(@download_path, bundle.dev_cookbooks)
|
86
|
+
|
87
|
+
# Initialize run list for this sequence (partial converge support)
|
88
|
+
@run_list = []
|
89
|
+
@inputs = { }
|
90
|
+
breakpoint = CookState.breakpoint
|
91
|
+
run_list_recipes.each do |recipe|
|
92
|
+
if recipe.nickname == breakpoint
|
93
|
+
@audit.append_info("Breakpoint set to < #{breakpoint} >")
|
94
|
+
break
|
95
|
+
end
|
96
|
+
@run_list << recipe.nickname
|
97
|
+
::RightSupport::Data::HashTools.deep_merge!(@inputs, recipe.attributes)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Retrieve node attributes and deep merge in inputs
|
101
|
+
@attributes = ChefState.attributes
|
102
|
+
::RightSupport::Data::HashTools.deep_merge!(@attributes, @inputs)
|
103
|
+
end
|
104
|
+
|
105
|
+
# FIX: thread_name should never be nil from the core in future, but
|
106
|
+
# temporarily we must supply the default thread_name before if nil. in
|
107
|
+
# future we should fail execution when thread_name is reliably present and
|
108
|
+
# for any reason does not match ::RightScale::AgentConfig.valid_thread_name
|
109
|
+
# see also ExecutableSequenceProxy#initialize
|
110
|
+
#
|
111
|
+
# === Parameters
|
112
|
+
# bundle(ExecutableBundle):: An executable bundle
|
113
|
+
#
|
114
|
+
# === Return
|
115
|
+
# result(String):: Thread name of this bundle
|
116
|
+
def get_thread_name_from_bundle(bundle)
|
117
|
+
thread_name = nil
|
118
|
+
thread_name = bundle.runlist_policy.thread_name if bundle.respond_to?(:runlist_policy) && bundle.runlist_policy
|
119
|
+
Log.warn("Encountered a nil thread name unexpectedly, defaulting to '#{RightScale::AgentConfig.default_thread_name}'") unless thread_name
|
120
|
+
thread_name ||= RightScale::AgentConfig.default_thread_name
|
121
|
+
unless thread_name =~ RightScale::AgentConfig.valid_thread_name
|
122
|
+
raise ArgumentError, "Invalid thread name #{thread_name.inspect}"
|
123
|
+
end
|
124
|
+
thread_name
|
125
|
+
end
|
126
|
+
|
127
|
+
# FIX: This code can be removed once the core sends a runlist policy
|
128
|
+
#
|
129
|
+
# === Parameters
|
130
|
+
# bundle(ExecutableBundle):: An executable bundle
|
131
|
+
#
|
132
|
+
# === Return
|
133
|
+
# result(String):: Policy name of this bundle
|
134
|
+
def get_policy_name_from_bundle(bundle)
|
135
|
+
policy_name = nil
|
136
|
+
policy_name ||= bundle.runlist_policy.policy_name if bundle.respond_to?(:runlist_policy) && bundle.runlist_policy
|
137
|
+
policy_name
|
138
|
+
end
|
139
|
+
|
140
|
+
# Run given executable bundle
|
141
|
+
# Asynchronous, set deferrable object's disposition
|
142
|
+
#
|
143
|
+
# === Return
|
144
|
+
# true:: Always return true
|
145
|
+
def run
|
146
|
+
@ok = true
|
147
|
+
if @run_list.empty?
|
148
|
+
# Deliberately avoid auditing anything since we did not run any recipes
|
149
|
+
# Still download the cookbooks repos if in dev mode
|
150
|
+
checkout_cookbook_repos
|
151
|
+
download_cookbooks if CookState.cookbooks_path
|
152
|
+
report_success(nil)
|
153
|
+
else
|
154
|
+
configure_ohai
|
155
|
+
configure_logging
|
156
|
+
configure_chef
|
157
|
+
download_attachments if @ok
|
158
|
+
install_packages if @ok
|
159
|
+
checkout_cookbook_repos if @ok
|
160
|
+
download_cookbooks if @ok
|
161
|
+
update_cookbook_path if @ok
|
162
|
+
setup_powershell_providers if RightScale::Platform.windows?
|
163
|
+
check_ohai { |o| converge(o) } if @ok
|
164
|
+
end
|
165
|
+
true
|
166
|
+
rescue Exception => e
|
167
|
+
report_failure('Execution failed', "The following exception was caught while preparing for execution: (#{e.message}) from\n#{e.backtrace.join("\n")}")
|
168
|
+
end
|
169
|
+
|
170
|
+
protected
|
171
|
+
|
172
|
+
def configure_ohai
|
173
|
+
# Ohai plugins path and logging.
|
174
|
+
#
|
175
|
+
# note that this was moved to a separate .rb file to ensure that plugins
|
176
|
+
# path is not relative to this potentially relocatable source file.
|
177
|
+
RightScale::OhaiSetup.configure_ohai
|
178
|
+
end
|
179
|
+
|
180
|
+
# Initialize and configure the logger
|
181
|
+
def configure_logging
|
182
|
+
Chef::Log.logger = AuditLogger.new
|
183
|
+
Chef::Log.logger.level = Log.level_from_sym(Log.level)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Configure chef so it can find cookbooks and so its logs go to the audits
|
187
|
+
#
|
188
|
+
# === Return
|
189
|
+
# true:: Always return true
|
190
|
+
def configure_chef
|
191
|
+
# setup logger for mixlib-shellout gem to consume instead of the chef
|
192
|
+
# v0.10.10 behavior of not logging ShellOut calls by default. also setup
|
193
|
+
# command failure exception and callback for legacy reasons.
|
194
|
+
::Mixlib::ShellOut.default_logger = ::Chef::Log
|
195
|
+
::Mixlib::ShellOut.command_failure_callback = lambda do |params|
|
196
|
+
failure_reason = ::RightScale::SubprocessFormatting.reason(params[:status])
|
197
|
+
expected_error_codes = Array(params[:args][:returns]).join(' or ')
|
198
|
+
::RightScale::Exceptions::Exec.new("\"#{params[:args][:command]}\" #{failure_reason}, expected #{expected_error_codes}.",
|
199
|
+
params[:args][:cwd])
|
200
|
+
end
|
201
|
+
|
202
|
+
# Chef run mode is always solo for cook
|
203
|
+
Chef::Config[:solo] = true
|
204
|
+
|
205
|
+
# determine default cookbooks path. If debugging cookbooks, place the debug pat(s) first, otherwise
|
206
|
+
# clear out the list as it will be filled out with cookbooks needed for this converge as they are downloaded.
|
207
|
+
if CookState.use_cookbooks_path?
|
208
|
+
Chef::Config[:cookbook_path] = [CookState.cookbooks_path].flatten
|
209
|
+
@audit.append_info("Using development cookbooks repositories path:\n\t- #{Chef::Config[:cookbook_path].join("\n\t- ")}")
|
210
|
+
else
|
211
|
+
# reset the cookbook path. Will be filled out with cookbooks needed for this execution
|
212
|
+
Chef::Config[:cookbook_path] = []
|
213
|
+
end
|
214
|
+
# add the rightscript cookbook if there are rightscripts in this converge
|
215
|
+
Chef::Config[:cookbook_path] << @right_scripts_cookbook.repo_dir unless @right_scripts_cookbook.empty?
|
216
|
+
|
217
|
+
# must set file cache path and ensure it exists otherwise evented run_command will fail
|
218
|
+
file_cache_path = File.join(AgentConfig.cache_dir, 'chef')
|
219
|
+
Chef::Config[:file_cache_path] = file_cache_path
|
220
|
+
FileUtils.mkdir_p(Chef::Config[:file_cache_path])
|
221
|
+
|
222
|
+
Chef::Config[:cache_options][:path] = File.join(file_cache_path, 'checksums')
|
223
|
+
FileUtils.mkdir_p(Chef::Config[:cache_options][:path])
|
224
|
+
|
225
|
+
# Where backups of chef-managed files should go. Set to nil to backup to the same directory the file being backed up is in.
|
226
|
+
Chef::Config[:file_backup_path] = nil
|
227
|
+
|
228
|
+
true
|
229
|
+
end
|
230
|
+
|
231
|
+
# Download attachments, update @ok
|
232
|
+
#
|
233
|
+
# === Return
|
234
|
+
# true:: Always return true
|
235
|
+
def download_attachments
|
236
|
+
unless @scripts.all? { |s| s.attachments.empty? }
|
237
|
+
@audit.create_new_section('Downloading attachments')
|
238
|
+
audit_time do
|
239
|
+
@scripts.each do |script|
|
240
|
+
attach_dir = @right_scripts_cookbook.cache_dir(script)
|
241
|
+
script.attachments.each do |a|
|
242
|
+
script_file_path = File.join(attach_dir, a.file_name)
|
243
|
+
@audit.update_status("Downloading #{a.file_name} into #{script_file_path} through Repose")
|
244
|
+
begin
|
245
|
+
attachment_dir = File.dirname(script_file_path)
|
246
|
+
FileUtils.mkdir_p(attachment_dir)
|
247
|
+
tempfile = Tempfile.open('attachment', attachment_dir)
|
248
|
+
tempfile.binmode
|
249
|
+
@downloader.download(a.url) do |response|
|
250
|
+
tempfile << response
|
251
|
+
end
|
252
|
+
File.unlink(script_file_path) if File.exists?(script_file_path)
|
253
|
+
File.link(tempfile.path, script_file_path)
|
254
|
+
tempfile.close!
|
255
|
+
@audit.append_info(@downloader.details)
|
256
|
+
rescue Exception => e
|
257
|
+
tempfile.close! unless tempfile.nil?
|
258
|
+
@audit.append_info("Repose download failed: #{e.message}.")
|
259
|
+
if e.kind_of?(ReposeDownloader::DownloadException) && e.message.include?("Forbidden")
|
260
|
+
@audit.append_info("Often this means the download URL has expired while waiting for inputs to be satisfied.")
|
261
|
+
end
|
262
|
+
report_failure("Failed to download attachment '#{a.file_name}'", e.message)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
true
|
269
|
+
end
|
270
|
+
|
271
|
+
# Install required software packages, update @ok
|
272
|
+
# Always update the apt cache even if there is no package for recipes
|
273
|
+
#
|
274
|
+
# === Return
|
275
|
+
# true:: Always return true
|
276
|
+
def install_packages
|
277
|
+
packages = []
|
278
|
+
@scripts.each { |s| packages.push(s.packages) if s.packages && !s.packages.empty? }
|
279
|
+
return true if packages.empty?
|
280
|
+
|
281
|
+
success = false
|
282
|
+
installer = RightScale::Platform.installer
|
283
|
+
|
284
|
+
@audit.create_new_section("Installing packages: #{packages.uniq.join(' ')}")
|
285
|
+
audit_time do
|
286
|
+
success = retry_execution('Installation of packages failed, retrying...') do
|
287
|
+
begin
|
288
|
+
installer.install(packages)
|
289
|
+
rescue Exception => e
|
290
|
+
@audit.append_output(installer.output)
|
291
|
+
report_failure('Failed to install packages', e.message)
|
292
|
+
else
|
293
|
+
@audit.append_output(installer.output)
|
294
|
+
end
|
295
|
+
$?.success?
|
296
|
+
end
|
297
|
+
end
|
298
|
+
report_failure('Failed to install packages', 'Package install exited with bad status') unless success
|
299
|
+
true
|
300
|
+
end
|
301
|
+
|
302
|
+
# Update the Chef cookbook_path based on the cookbooks in the bundle.
|
303
|
+
#
|
304
|
+
# === Return
|
305
|
+
# true:: Always return true
|
306
|
+
def update_cookbook_path
|
307
|
+
# both cookbook sequences and paths are listed in same order as
|
308
|
+
# presented in repo UI. previous to RL v5.7 we received cookbook sequences
|
309
|
+
# in an arbitrary order, but this has been fixed as of the release of v5.8
|
310
|
+
# (we will not change the order for v5.7-).
|
311
|
+
# for chef to execute repos and paths in the order listed, both of these
|
312
|
+
# ordered lists need to be inserted in reverse order because the chef code
|
313
|
+
# replaces cookbook paths as it reads the array from beginning to end.
|
314
|
+
@cookbooks.reverse.each do |cookbook_sequence|
|
315
|
+
local_basedir = File.join(@download_path, cookbook_sequence.hash)
|
316
|
+
cookbook_sequence.paths.reverse.each do |path|
|
317
|
+
dir = File.expand_path(File.join(local_basedir, path))
|
318
|
+
unless Chef::Config[:cookbook_path].include?(dir)
|
319
|
+
if File.directory?(dir)
|
320
|
+
Chef::Config[:cookbook_path] << dir
|
321
|
+
else
|
322
|
+
RightScale::Log.info("Excluding #{path} from chef cookbooks_path because it was not downloaded")
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
RightScale::Log.info("Updated cookbook_path to: #{Chef::Config[:cookbook_path].join(", ")}")
|
328
|
+
true
|
329
|
+
end
|
330
|
+
|
331
|
+
AUDIT_BEGIN_OPERATIONS = Set.new([:scraping]).freeze unless defined?(AUDIT_BEGIN_OPERATIONS)
|
332
|
+
|
333
|
+
AUDIT_COMMIT_OPERATIONS = Set.new([:initialize,
|
334
|
+
:retrieving,
|
335
|
+
:reading_cookbook,
|
336
|
+
:scraping]).freeze unless defined?(AUDIT_COMMIT_OPERATIONS)
|
337
|
+
|
338
|
+
# Checkout repositories for selected cookbooks. Audit progress and errors, do not fail on checkout error.
|
339
|
+
#
|
340
|
+
# === Return
|
341
|
+
# true:: Always return true
|
342
|
+
def checkout_cookbook_repos
|
343
|
+
return true unless @cookbook_repo_retriever.has_cookbooks?
|
344
|
+
|
345
|
+
@audit.create_new_section('Checking out cookbooks for development')
|
346
|
+
@audit.append_info("Cookbook repositories will be checked out to #{@cookbook_repo_retriever.checkout_root}")
|
347
|
+
|
348
|
+
audit_time do
|
349
|
+
# only create a scraper if there are dev cookbooks
|
350
|
+
@cookbook_repo_retriever.checkout_cookbook_repos do |state, operation, explanation, exception|
|
351
|
+
# audit progress
|
352
|
+
case state
|
353
|
+
when :begin
|
354
|
+
@audit.append_info("start #{operation} #{explanation}") if AUDIT_BEGIN_OPERATIONS.include?(operation)
|
355
|
+
when :commit
|
356
|
+
@audit.append_info("finish #{operation} #{explanation}") if AUDIT_COMMIT_OPERATIONS.include?(operation)
|
357
|
+
when :abort
|
358
|
+
@audit.append_error("Failed #{operation} #{explanation}")
|
359
|
+
Log.error(Log.format("Failed #{operation} #{explanation}", exception, :trace))
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
# Download required cookbooks from Repose mirror; update @ok.
|
366
|
+
# Note: Starting with Chef 0.8, the cookbooks repositories list must be traversed in reverse
|
367
|
+
# order to preserve the semantic of the dashboard (first repo has priority)
|
368
|
+
#
|
369
|
+
# === Return
|
370
|
+
# true:: Always return true
|
371
|
+
def download_cookbooks
|
372
|
+
# first, if @download_path is world writable, stop that nonsense right this second.
|
373
|
+
unless RightScale::Platform.windows?
|
374
|
+
if File.exists?(@download_path) && File.world_writable?(@download_path)
|
375
|
+
Log.warn("Cookbooks download path world writable; fixing.")
|
376
|
+
File.chmod(0755, @download_path)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
unless CookState.download_once?
|
381
|
+
Log.info("Deleting existing cookbooks")
|
382
|
+
# second, wipe out any preexisting cookbooks in the download path
|
383
|
+
if File.directory?(@download_path)
|
384
|
+
Dir.foreach(@download_path) do |entry|
|
385
|
+
FileUtils.remove_entry_secure(File.join(@download_path, entry)) if entry =~ /\A[[:xdigit:]]+\Z/
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
unless @cookbooks.empty?
|
391
|
+
# only create audit output if we're actually going to download something!
|
392
|
+
@audit.create_new_section('Retrieving cookbooks')
|
393
|
+
audit_time do
|
394
|
+
@cookbooks.each do |cookbook_sequence|
|
395
|
+
cookbook_sequence.positions.each do |position|
|
396
|
+
if @cookbook_repo_retriever.should_be_linked?(cookbook_sequence.hash, position.position)
|
397
|
+
begin
|
398
|
+
@cookbook_repo_retriever.link(cookbook_sequence.hash, position.position)
|
399
|
+
rescue Exception => e
|
400
|
+
::RightScale::Log.error("Failed to link #{position.cookbook.name} for development", e)
|
401
|
+
end
|
402
|
+
else
|
403
|
+
# download with repose
|
404
|
+
cookbook_path = CookbookPathMapping.repose_path(@download_path, cookbook_sequence.hash, position.position)
|
405
|
+
if File.exists?(cookbook_path)
|
406
|
+
@audit.append_info("Skipping #{position.cookbook.name}, already there")
|
407
|
+
else
|
408
|
+
download_cookbook(cookbook_path, position.cookbook)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# record that cookbooks have been downloaded so we do not download them again in Dev mode
|
417
|
+
CookState.has_downloaded_cookbooks = true
|
418
|
+
|
419
|
+
true
|
420
|
+
rescue Exception => e
|
421
|
+
report_failure("Failed to download cookbook", "Cannot continue due to #{e.class.name}: #{e.message}.")
|
422
|
+
Log.debug(Log.format("Failed to download cookbook", e, :trace))
|
423
|
+
end
|
424
|
+
|
425
|
+
#
|
426
|
+
# Download a cookbook from Repose mirror and extract it to the filesystem.
|
427
|
+
#
|
428
|
+
# === Parameters
|
429
|
+
# root_dir(String):: subdir of basedir into which this cookbook goes
|
430
|
+
# cookbook(Cookbook):: cookbook
|
431
|
+
#
|
432
|
+
# === Raise
|
433
|
+
# Propagates exceptions raised by callees, namely DownloadFailure
|
434
|
+
# and ReposeServerFailure
|
435
|
+
#
|
436
|
+
# === Return
|
437
|
+
# true:: always returns true
|
438
|
+
def download_cookbook(root_dir, cookbook)
|
439
|
+
cache_dir = File.join(AgentConfig.cache_dir, "right_link", "cookbooks")
|
440
|
+
cookbook_tarball = File.join(cache_dir, "#{cookbook.hash.split('?').first}.tar")
|
441
|
+
begin
|
442
|
+
FileUtils.mkdir_p(cache_dir)
|
443
|
+
File.open(cookbook_tarball, "ab") do |tarball|
|
444
|
+
if tarball.stat.size == 0
|
445
|
+
#audit cookbook name & part of hash (as a disambiguator)
|
446
|
+
name = cookbook.name ; tag = cookbook.hash[0..4]
|
447
|
+
@audit.append_info("Downloading cookbook '#{name}' (#{tag})")
|
448
|
+
@downloader.download("/cookbooks/#{cookbook.hash}") do |response|
|
449
|
+
tarball << response
|
450
|
+
end
|
451
|
+
@audit.append_info(@downloader.details)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
rescue Exception => e
|
455
|
+
File.unlink(cookbook_tarball) if File.exists?(cookbook_tarball)
|
456
|
+
raise
|
457
|
+
end
|
458
|
+
|
459
|
+
@audit.append_info("Success; unarchiving cookbook")
|
460
|
+
|
461
|
+
# The local basedir is the faux "repository root" into which we extract all related
|
462
|
+
# cookbooks in that set, "related" meaning a set of cookbooks that originally came
|
463
|
+
# from the same Chef cookbooks repository as observed by the scraper.
|
464
|
+
#
|
465
|
+
# Even though we are pulling individually-packaged cookbooks and not the whole repository,
|
466
|
+
# we preserve the position of cookbooks in the directory hierarchy such that a given cookbook
|
467
|
+
# has the same path relative to the local basedir as the original cookbook had relative to the
|
468
|
+
# base directory of its repository.
|
469
|
+
#
|
470
|
+
# This ensures we will be able to deal with future changes to the Chef merge algorithm,
|
471
|
+
# as well as accommodate "naughty" cookbooks that side-load data from the filesystem
|
472
|
+
# using relative paths to other cookbooks.
|
473
|
+
FileUtils.mkdir_p(root_dir)
|
474
|
+
Dir.chdir(root_dir) do
|
475
|
+
# note that Windows uses a "tar.cmd" file which is found via the PATH
|
476
|
+
# used by the command interpreter.
|
477
|
+
cmd = "tar xf #{cookbook_tarball.inspect} 2>&1"
|
478
|
+
Log.debug(cmd)
|
479
|
+
output = `#{cmd}`
|
480
|
+
@audit.append_info(output)
|
481
|
+
unless $?.success?
|
482
|
+
report_failure("Unknown error", SubprocessFormatting.reason($?))
|
483
|
+
end
|
484
|
+
end
|
485
|
+
return true
|
486
|
+
end
|
487
|
+
|
488
|
+
# Create Powershell providers from cookbook repos
|
489
|
+
#
|
490
|
+
#
|
491
|
+
# === Return
|
492
|
+
# true:: Always return true
|
493
|
+
def setup_powershell_providers
|
494
|
+
dynamic_provider = DynamicPowershellProvider.new
|
495
|
+
dynamic_provider.generate_providers(Chef::Config[:cookbook_path])
|
496
|
+
@powershell_providers = dynamic_provider.providers
|
497
|
+
end
|
498
|
+
|
499
|
+
# Checks whether Ohai is ready and calls given block with it
|
500
|
+
# if that's the case otherwise schedules itself to try again
|
501
|
+
# indefinitely
|
502
|
+
#
|
503
|
+
# === Block
|
504
|
+
# Given block should take one argument which corresponds to
|
505
|
+
# ohai instance
|
506
|
+
#
|
507
|
+
# === Return
|
508
|
+
# true:: Always return true
|
509
|
+
def check_ohai(&block)
|
510
|
+
ohai = create_ohai
|
511
|
+
if ohai[:hostname]
|
512
|
+
block.call(ohai)
|
513
|
+
else
|
514
|
+
Log.warning("Could not determine node name from Ohai, will retry in #{@ohai_retry_delay}s...")
|
515
|
+
# Need to execute on defer thread consistent with where ExecutableSequence is running
|
516
|
+
# otherwise EM main thread command client activity will block
|
517
|
+
EM.add_timer(@ohai_retry_delay) { EM.defer { check_ohai(&block) } }
|
518
|
+
@ohai_retry_delay = [2 * @ohai_retry_delay, OHAI_RETRY_MAX_DELAY].min
|
519
|
+
end
|
520
|
+
true
|
521
|
+
end
|
522
|
+
|
523
|
+
# Creates a new ohai and configures it.
|
524
|
+
#
|
525
|
+
# === Return
|
526
|
+
# ohai(Ohai::System):: configured ohai
|
527
|
+
def create_ohai
|
528
|
+
ohai = Ohai::System.new
|
529
|
+
ohai.require_plugin('os')
|
530
|
+
ohai.require_plugin('hostname')
|
531
|
+
return ohai
|
532
|
+
end
|
533
|
+
|
534
|
+
# Chef converge
|
535
|
+
#
|
536
|
+
# === Parameters
|
537
|
+
# ohai(Ohai):: Ohai instance to be used by Chef
|
538
|
+
#
|
539
|
+
# === Return
|
540
|
+
# true:: Always return true
|
541
|
+
def converge(ohai)
|
542
|
+
begin
|
543
|
+
# suppress unnecessary error log output for cases of explictly exiting
|
544
|
+
# from converge (rs_shutdown, etc.).
|
545
|
+
::Chef::Client.clear_notifications
|
546
|
+
|
547
|
+
if @cookbooks.size > 0
|
548
|
+
@audit.create_new_section('Converging')
|
549
|
+
else
|
550
|
+
@audit.create_new_section('Preparing execution')
|
551
|
+
end
|
552
|
+
|
553
|
+
@audit.append_info("Run list for thread #{@thread_name.inspect} contains #{@run_list.size} items.")
|
554
|
+
@audit.append_info(@run_list.join(', '))
|
555
|
+
|
556
|
+
attribs = { 'run_list' => @run_list }
|
557
|
+
attribs.merge!(@attributes) if @attributes
|
558
|
+
c = Chef::Client.new(attribs)
|
559
|
+
c.ohai = ohai
|
560
|
+
audit_time do
|
561
|
+
# Ensure that Ruby subprocesses invoked by Chef do not inherit our
|
562
|
+
# RubyGems/Bundler environment.
|
563
|
+
without_bundler_env do
|
564
|
+
c.run
|
565
|
+
end
|
566
|
+
end
|
567
|
+
rescue SystemExit => e
|
568
|
+
# exit is expected in case where a script has invoked rs_shutdown
|
569
|
+
# (command line tool or Chef resource). exit is considered to be
|
570
|
+
# unexpected if rs_shutdown has not been called. note that it is
|
571
|
+
# possible, but not a 'best practice', for a recipe (but not a
|
572
|
+
# RightScript) to call rs_shutdown as an external command line utility
|
573
|
+
# without calling exit (i.e. request a deferred reboot) and continue
|
574
|
+
# running recipes until the list of recipes is complete. in this case,
|
575
|
+
# the shutdown occurs after subsequent recipes have finished. the best
|
576
|
+
# practice for a recipe is to use the rs_shutdown chef resource which
|
577
|
+
# calls exit when appropriate.
|
578
|
+
shutdown_request = RightScale::ShutdownRequestProxy.instance
|
579
|
+
if shutdown_request.continue?
|
580
|
+
report_failure('Execution failed due to rs_shutdown not being called before exit', chef_error(e))
|
581
|
+
Log.debug(Log.format("Execution failed", e, :trace))
|
582
|
+
else
|
583
|
+
Log.info("Shutdown requested by script: #{shutdown_request}")
|
584
|
+
end
|
585
|
+
rescue Exception => e
|
586
|
+
report_failure('Execution failed', chef_error(e))
|
587
|
+
Log.debug(Log.format("Execution failed", e, :trace))
|
588
|
+
ensure
|
589
|
+
# terminate the powershell providers
|
590
|
+
# terminate the providers before the node server as the provider term scripts may still use the node server
|
591
|
+
if @powershell_providers
|
592
|
+
@powershell_providers.each do |p|
|
593
|
+
begin
|
594
|
+
p.terminate
|
595
|
+
rescue Exception => e
|
596
|
+
Log.debug(Log.format("Error terminating #{p.inspect}", e, :trace))
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
# kill the chef node provider
|
602
|
+
RightScale::Windows::ChefNodeServer.instance.stop rescue nil if RightScale::Platform.windows?
|
603
|
+
end
|
604
|
+
report_success(c.node) if @ok
|
605
|
+
true
|
606
|
+
end
|
607
|
+
|
608
|
+
# Initialize inputs patch and report success
|
609
|
+
#
|
610
|
+
# === Parameters
|
611
|
+
# node(ChefNode):: Chef node used to converge, can be nil (patch is empty in this case)
|
612
|
+
#
|
613
|
+
# === Return
|
614
|
+
# true:: Always return true
|
615
|
+
def report_success(node)
|
616
|
+
ChefState.merge_attributes(node.normal_attrs) if node
|
617
|
+
patch = ::RightSupport::Data::HashTools.deep_create_patch(@inputs, ChefState.attributes)
|
618
|
+
# We don't want to send back new attributes (ohai etc.)
|
619
|
+
patch[:right_only] = { }
|
620
|
+
@inputs_patch = patch
|
621
|
+
EM.next_tick { succeed }
|
622
|
+
true
|
623
|
+
end
|
624
|
+
|
625
|
+
# Set status with failure message and audit it
|
626
|
+
#
|
627
|
+
# === Parameters
|
628
|
+
# title(String):: Title used to update audit status
|
629
|
+
# msg(String):: Failure message
|
630
|
+
#
|
631
|
+
# === Return
|
632
|
+
# true:: Always return true
|
633
|
+
def report_failure(title, msg)
|
634
|
+
@ok = false
|
635
|
+
@failure_title = title
|
636
|
+
@failure_message = msg
|
637
|
+
# note that the errback handler is expected to audit the message based on
|
638
|
+
# the preserved title and message and so we don't audit it here.
|
639
|
+
EM.next_tick { fail }
|
640
|
+
true
|
641
|
+
end
|
642
|
+
|
643
|
+
# Wrap chef exception with explanatory information and show
|
644
|
+
# context of failure
|
645
|
+
#
|
646
|
+
# === Parameters
|
647
|
+
# e(Exception):: Exception raised while executing Chef recipe
|
648
|
+
#
|
649
|
+
# === Return
|
650
|
+
# msg(String):: Human friendly error message
|
651
|
+
def chef_error(e)
|
652
|
+
if e.is_a?(::RightScale::Exceptions::Exec)
|
653
|
+
msg = "External command error: "
|
654
|
+
if match = /RightScale::Exceptions::Exec: (.*)/.match(e.message)
|
655
|
+
cmd_output = match[1]
|
656
|
+
else
|
657
|
+
cmd_output = e.message
|
658
|
+
end
|
659
|
+
msg += cmd_output
|
660
|
+
msg += "\nThe command was run from \"#{e.path}\"" if e.path
|
661
|
+
elsif e.is_a?(::Chef::Exceptions::ValidationFailed) && (e.message =~ /Option action must be equal to one of:/)
|
662
|
+
msg = "[chef] recipe references an action that does not exist. #{e.message}"
|
663
|
+
elsif e.is_a?(::NoMethodError) && (missing_action_match = /undefined method .action_(\S*)' for #<\S*:\S*>/.match(e.message)) && missing_action_match[1]
|
664
|
+
msg = "[chef] recipe references the action <#{missing_action_match[1]}> which is missing an implementation"
|
665
|
+
else
|
666
|
+
msg = "Execution error:\n"
|
667
|
+
msg += e.message
|
668
|
+
file, line, meth = e.backtrace[0].scan(BACKTRACE_LINE_REGEXP).flatten
|
669
|
+
line_number = line.to_i
|
670
|
+
if file && line && (line_number.to_s == line)
|
671
|
+
dir = AgentConfig.cookbook_download_dir
|
672
|
+
if file[0..dir.size - 1] == dir
|
673
|
+
path = "[COOKBOOKS]/" + file[dir.size..file.size]
|
674
|
+
else
|
675
|
+
path = file
|
676
|
+
end
|
677
|
+
msg += "\n\nThe error occurred line #{line} of #{path}"
|
678
|
+
msg += " in method '#{meth}'" if meth
|
679
|
+
context = ""
|
680
|
+
if File.readable?(file)
|
681
|
+
File.open(file, 'r') do |f|
|
682
|
+
lines = f.readlines
|
683
|
+
lines_count = lines.size
|
684
|
+
if lines_count >= line_number
|
685
|
+
upper = [lines_count, line_number + 2].max
|
686
|
+
padding = upper.to_s.size
|
687
|
+
context += context_line(lines, line_number - 2, padding)
|
688
|
+
context += context_line(lines, line_number - 1, padding)
|
689
|
+
context += context_line(lines, line_number, padding, '*')
|
690
|
+
context += context_line(lines, line_number + 1, padding)
|
691
|
+
context += context_line(lines, line_number + 2, padding)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
msg += " while executing:\n\n#{context}" unless context.empty?
|
696
|
+
end
|
697
|
+
end
|
698
|
+
msg
|
699
|
+
end
|
700
|
+
|
701
|
+
# Format a single line for the error context, return empty string
|
702
|
+
# if given index is negative or greater than the lines array size
|
703
|
+
#
|
704
|
+
# === Parameters
|
705
|
+
# lines(Array):: Lines of text
|
706
|
+
# index(Integer):: Index of line that should be formatted for context
|
707
|
+
# padding(Integer):: Number of character to pad line with (includes prefix)
|
708
|
+
# prefix(String):: Single character string used to prefix line
|
709
|
+
# use line number if not specified
|
710
|
+
def context_line(lines, index, padding, prefix=nil)
|
711
|
+
return '' if index < 1 || index > lines.size
|
712
|
+
margin = prefix ? prefix * index.to_s.size : index.to_s
|
713
|
+
"#{margin}#{' ' * ([padding - margin.size, 0].max)} #{lines[index - 1]}"
|
714
|
+
end
|
715
|
+
|
716
|
+
# Retry executing given block given number of times
|
717
|
+
# Block should return true when it succeeds
|
718
|
+
#
|
719
|
+
# === Parameters
|
720
|
+
# retry_message(String):: Message to audit before retrying
|
721
|
+
# times(Integer):: Number of times block should be retried before giving up
|
722
|
+
#
|
723
|
+
# === Block
|
724
|
+
# Block to be executed
|
725
|
+
#
|
726
|
+
# === Return
|
727
|
+
# success(Boolean):: true if execution was successful, false otherwise.
|
728
|
+
def retry_execution(retry_message, times = AgentConfig.max_packages_install_retries)
|
729
|
+
count = 0
|
730
|
+
success = false
|
731
|
+
begin
|
732
|
+
count += 1
|
733
|
+
success = yield
|
734
|
+
@audit.append_info("\n#{retry_message}\n") unless success || count > times
|
735
|
+
end while !success && count <= times
|
736
|
+
success
|
737
|
+
end
|
738
|
+
|
739
|
+
# Audit startup time and duration of given action
|
740
|
+
#
|
741
|
+
# === Block
|
742
|
+
# Block whose execution should be timed
|
743
|
+
#
|
744
|
+
# === Return
|
745
|
+
# res(Object):: Result returned by given block
|
746
|
+
def audit_time
|
747
|
+
start_time = Time.now
|
748
|
+
@audit.append_info("Starting at #{start_time}")
|
749
|
+
res = yield
|
750
|
+
@audit.append_info("Duration: #{'%.2f' % (Time.now - start_time)} seconds\n\n")
|
751
|
+
res
|
752
|
+
end
|
753
|
+
|
754
|
+
def without_bundler_env
|
755
|
+
original_env = ENV.to_hash
|
756
|
+
ENV.delete_if {|k,v| k =~ /^GEM_|^BUNDLE_/}
|
757
|
+
if ENV.key?('RUBYOPT')
|
758
|
+
ENV['RUBYOPT'] = ENV['RUBYOPT'].split(" ").select {|word| word !~ /bundler/}.join(" ")
|
759
|
+
end
|
760
|
+
yield
|
761
|
+
ensure
|
762
|
+
ENV.replace(original_env.to_hash)
|
763
|
+
end
|
764
|
+
end
|
765
|
+
end
|