right_link 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (199) hide show
  1. data/actors/agent_manager.rb +88 -0
  2. data/actors/instance_scheduler.rb +321 -0
  3. data/actors/instance_services.rb +64 -0
  4. data/actors/instance_setup.rb +567 -0
  5. data/bin/cloud +25 -0
  6. data/bin/cook_runner +44 -0
  7. data/bin/deploy +120 -0
  8. data/bin/enroll +385 -0
  9. data/bin/rad +32 -0
  10. data/bin/rchk +29 -0
  11. data/bin/rnac +39 -0
  12. data/bin/rs_connect +33 -0
  13. data/bin/rs_log_level +31 -0
  14. data/bin/rs_ohai +28 -0
  15. data/bin/rs_reenroll +31 -0
  16. data/bin/rs_run_recipe +34 -0
  17. data/bin/rs_run_right_script +34 -0
  18. data/bin/rs_shutdown +33 -0
  19. data/bin/rs_tag +33 -0
  20. data/bin/rs_thunk +33 -0
  21. data/bin/rstat +31 -0
  22. data/bin/system +16 -0
  23. data/ext/Rakefile +18 -0
  24. data/init/config.yml +5 -0
  25. data/init/init.rb +79 -0
  26. data/lib/chef/ohai_setup.rb +51 -0
  27. data/lib/chef/plugins/cloud.rb +91 -0
  28. data/lib/chef/plugins/cloudstack.rb +23 -0
  29. data/lib/chef/plugins/ec2.rb +23 -0
  30. data/lib/chef/plugins/linux/block_device2.rb +24 -0
  31. data/lib/chef/plugins/rackspace.rb +23 -0
  32. data/lib/chef/plugins/rightscale.rb +125 -0
  33. data/lib/chef/plugins/windows/network.rb +114 -0
  34. data/lib/chef/plugins.rb +74 -0
  35. data/lib/chef/providers/dns_dnsmadeeasy_provider.rb +81 -0
  36. data/lib/chef/providers/dns_resource.rb +100 -0
  37. data/lib/chef/providers/executable_schedule_provider.rb +70 -0
  38. data/lib/chef/providers/executable_schedule_resource.rb +144 -0
  39. data/lib/chef/providers/remote_recipe_provider.rb +86 -0
  40. data/lib/chef/providers/remote_recipe_resource.rb +101 -0
  41. data/lib/chef/providers/right_link_tag_provider.rb +73 -0
  42. data/lib/chef/providers/right_link_tag_resource.rb +59 -0
  43. data/lib/chef/providers/right_script_provider.rb +190 -0
  44. data/lib/chef/providers/right_script_resource.rb +113 -0
  45. data/lib/chef/providers/rs_shutdown_provider.rb +75 -0
  46. data/lib/chef/providers/rs_shutdown_resource.rb +55 -0
  47. data/lib/chef/providers/server_collection_provider.rb +66 -0
  48. data/lib/chef/providers/server_collection_resource.rb +93 -0
  49. data/lib/chef/providers/windows/powershell_provider.rb +151 -0
  50. data/lib/chef/providers/windows/powershell_resource.rb +111 -0
  51. data/lib/chef/providers/windows/unsupported_provider.rb +51 -0
  52. data/lib/chef/right_providers.rb +55 -0
  53. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.csproj +104 -0
  54. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ChefNodeCmdlet.dll-Help.xml +141 -0
  55. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Exceptions.cs +182 -0
  56. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeCommand.cs +58 -0
  57. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeRequest.cs +46 -0
  58. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetChefNodeResponse.cs +45 -0
  59. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceCommand.cs +58 -0
  60. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceRequest.cs +46 -0
  61. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetCurrentResourceResponse.cs +45 -0
  62. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceCommand.cs +58 -0
  63. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceRequest.cs +46 -0
  64. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNewResourceResponse.cs +45 -0
  65. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionCommand.cs +178 -0
  66. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionRequest.cs +67 -0
  67. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNextActionResponse.cs +58 -0
  68. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueCommandBase.cs +142 -0
  69. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueRequestBase.cs +64 -0
  70. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/GetNodeValueResponseBase.cs +69 -0
  71. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/JsonTransport.cs +110 -0
  72. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeClient.cs +158 -0
  73. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/PipeServer.cs +142 -0
  74. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  75. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolConstants.cs +55 -0
  76. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ProtocolUtilities.cs +77 -0
  77. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/ReadMe.txt +53 -0
  78. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeCommand.cs +59 -0
  79. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeRequest.cs +46 -0
  80. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetChefNodeResponse.cs +58 -0
  81. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceCommand.cs +59 -0
  82. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceRequest.cs +46 -0
  83. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetCurrentResourceResponse.cs +40 -0
  84. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceCommand.cs +59 -0
  85. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceRequest.cs +46 -0
  86. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNewResourceResponse.cs +40 -0
  87. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueCommandBase.cs +293 -0
  88. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueRequestBase.cs +75 -0
  89. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/SetNodeValueResponseBase.cs +45 -0
  90. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet/Transport.cs +91 -0
  91. data/lib/chef/windows/ChefNodeCmdlet/ChefNodeCmdlet.sln +35 -0
  92. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Program.cs +374 -0
  93. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/Properties/AssemblyInfo.cs +16 -0
  94. data/lib/chef/windows/ChefNodeCmdlet/TestChefNodeCmdlet/TestChefNodeCmdlet.csproj +65 -0
  95. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Program.cs +136 -0
  96. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/Properties/AssemblyInfo.cs +36 -0
  97. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/ReadMe.txt +46 -0
  98. data/lib/chef/windows/ChefNodeCmdlet/TestNextActionCmdlet/TestNextActionCmdlet.csproj +68 -0
  99. data/lib/chef/windows/bin/Newtonsoft.Json.dll +0 -0
  100. data/lib/chef/windows/chef_node_server.rb +463 -0
  101. data/lib/chef/windows/dynamic_powershell_provider.rb +296 -0
  102. data/lib/chef/windows/pipe_server.rb +283 -0
  103. data/lib/chef/windows/powershell_host.rb +285 -0
  104. data/lib/chef/windows/powershell_pipe_server.rb +136 -0
  105. data/lib/chef/windows/powershell_provider_base.rb +92 -0
  106. data/lib/chef/windows/scripts/run_loop.ps1 +105 -0
  107. data/lib/clouds/cloud.rb +557 -0
  108. data/lib/clouds/cloud_factory.rb +250 -0
  109. data/lib/clouds/cloud_utilities.rb +244 -0
  110. data/lib/clouds/clouds/azure.rb +106 -0
  111. data/lib/clouds/clouds/cloudstack.rb +114 -0
  112. data/lib/clouds/clouds/ec2.rb +113 -0
  113. data/lib/clouds/clouds/eucalyptus.rb +46 -0
  114. data/lib/clouds/clouds/google.rb +102 -0
  115. data/lib/clouds/clouds/none.rb +76 -0
  116. data/lib/clouds/clouds/openstack.rb +30 -0
  117. data/lib/clouds/clouds/rackspace-ng.rb +54 -0
  118. data/lib/clouds/clouds/rackspace.rb +78 -0
  119. data/lib/clouds/clouds/softlayer.rb +91 -0
  120. data/lib/clouds/metadata_formatter.rb +108 -0
  121. data/lib/clouds/metadata_provider.rb +128 -0
  122. data/lib/clouds/metadata_source.rb +87 -0
  123. data/lib/clouds/metadata_sources/certificate_metadata_source.rb +207 -0
  124. data/lib/clouds/metadata_sources/config_drive_metadata_source.rb +129 -0
  125. data/lib/clouds/metadata_sources/file_metadata_source.rb +74 -0
  126. data/lib/clouds/metadata_sources/http_metadata_source.rb +277 -0
  127. data/lib/clouds/metadata_sources/selective_metadata_source.rb +122 -0
  128. data/lib/clouds/metadata_tree_climber.rb +144 -0
  129. data/lib/clouds/metadata_writer.rb +155 -0
  130. data/lib/clouds/metadata_writers/dictionary_metadata_writer.rb +72 -0
  131. data/lib/clouds/metadata_writers/ruby_metadata_writer.rb +76 -0
  132. data/lib/clouds/metadata_writers/shell_metadata_writer.rb +121 -0
  133. data/lib/clouds/register_clouds.rb +34 -0
  134. data/lib/clouds.rb +32 -0
  135. data/lib/gem_dependencies.rb +83 -0
  136. data/lib/git_hooks/commit-msg.rb +7 -0
  137. data/lib/instance/agent_config.rb +168 -0
  138. data/lib/instance/agent_watcher.rb +233 -0
  139. data/lib/instance/audit_cook_stub.rb +104 -0
  140. data/lib/instance/audit_proxy.rb +247 -0
  141. data/lib/instance/bundle_queue.rb +104 -0
  142. data/lib/instance/cook/agent_connection.rb +109 -0
  143. data/lib/instance/cook/audit_logger.rb +165 -0
  144. data/lib/instance/cook/audit_stub.rb +142 -0
  145. data/lib/instance/cook/ca-bundle.crt +2794 -0
  146. data/lib/instance/cook/chef_state.rb +211 -0
  147. data/lib/instance/cook/cook.rb +306 -0
  148. data/lib/instance/cook/cook_state.rb +298 -0
  149. data/lib/instance/cook/cookbook_path_mapping.rb +66 -0
  150. data/lib/instance/cook/cookbook_repo_retriever.rb +190 -0
  151. data/lib/instance/cook/executable_sequence.rb +765 -0
  152. data/lib/instance/cook/external_parameter_gatherer.rb +190 -0
  153. data/lib/instance/cook/repose_downloader.rb +349 -0
  154. data/lib/instance/cook/shutdown_request_proxy.rb +121 -0
  155. data/lib/instance/cook.rb +41 -0
  156. data/lib/instance/downloader.rb +208 -0
  157. data/lib/instance/duplicable.rb +67 -0
  158. data/lib/instance/exceptions.rb +49 -0
  159. data/lib/instance/executable_sequence_proxy.rb +278 -0
  160. data/lib/instance/instance_commands.rb +577 -0
  161. data/lib/instance/instance_state.rb +633 -0
  162. data/lib/instance/json_utilities.rb +102 -0
  163. data/lib/instance/login_manager.rb +533 -0
  164. data/lib/instance/login_user_manager.rb +522 -0
  165. data/lib/instance/message_encoder.rb +118 -0
  166. data/lib/instance/multi_thread_bundle_queue.rb +232 -0
  167. data/lib/instance/operation_context.rb +60 -0
  168. data/lib/instance/options_bag.rb +65 -0
  169. data/lib/instance/payload_formatter.rb +46 -0
  170. data/lib/instance/policy.rb +53 -0
  171. data/lib/instance/policy_audit.rb +100 -0
  172. data/lib/instance/policy_manager.rb +146 -0
  173. data/lib/instance/reenroll_manager.rb +104 -0
  174. data/lib/instance/right_scripts_cookbook.rb +181 -0
  175. data/lib/instance/shutdown_request.rb +221 -0
  176. data/lib/instance/single_thread_bundle_queue.rb +189 -0
  177. data/lib/instance/volume_management.rb +450 -0
  178. data/lib/instance.rb +50 -0
  179. data/lib/repo_conf_generators/apt_conf_generators.rb +106 -0
  180. data/lib/repo_conf_generators/gem_conf_generators.rb +80 -0
  181. data/lib/repo_conf_generators/rightscale_conf_generators.rb +254 -0
  182. data/lib/repo_conf_generators/rightscale_key.pub +17 -0
  183. data/lib/repo_conf_generators/yum_conf_generators.rb +225 -0
  184. data/lib/repo_conf_generators.rb +30 -0
  185. data/lib/run_shell.rb +28 -0
  186. data/scripts/agent_checker.rb +571 -0
  187. data/scripts/agent_controller.rb +247 -0
  188. data/scripts/agent_deployer.rb +148 -0
  189. data/scripts/bundle_runner.rb +336 -0
  190. data/scripts/cloud_controller.rb +176 -0
  191. data/scripts/log_level_manager.rb +142 -0
  192. data/scripts/ohai_runner.rb +33 -0
  193. data/scripts/reenroller.rb +193 -0
  194. data/scripts/server_importer.rb +293 -0
  195. data/scripts/shutdown_client.rb +183 -0
  196. data/scripts/system_configurator.rb +367 -0
  197. data/scripts/tagger.rb +381 -0
  198. data/scripts/thunker.rb +356 -0
  199. 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