chef 0.8.16 → 0.9.0.a3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chef might be problematic. Click here for more details.

Files changed (185) hide show
  1. data/bin/shef +1 -0
  2. data/distro/common/man/man1/chef-server-webui.1 +106 -0
  3. data/distro/common/man/man1/chef-server.1 +0 -1
  4. data/distro/common/man/man1/chef-solr-indexer.1 +55 -0
  5. data/distro/common/man/man1/chef-solr.1 +55 -0
  6. data/distro/common/man/man8/chef-client.8 +4 -2
  7. data/distro/common/man/man8/chef-solo.8 +1 -2
  8. data/distro/common/man/man8/chef-solr-rebuild.8 +37 -0
  9. data/distro/common/man/man8/knife.8 +668 -266
  10. data/distro/common/man/man8/shef.8 +45 -0
  11. data/distro/common/markdown/README +3 -0
  12. data/distro/common/markdown/knife.mkd +520 -0
  13. data/distro/debian/etc/default/chef-client +4 -0
  14. data/distro/debian/etc/default/chef-server +6 -0
  15. data/distro/debian/etc/default/chef-server-webui +6 -0
  16. data/distro/debian/etc/default/chef-solr +4 -0
  17. data/distro/debian/etc/default/chef-solr-indexer +4 -0
  18. data/distro/debian/etc/init.d/chef-client +41 -41
  19. data/distro/debian/etc/init.d/chef-server +10 -10
  20. data/distro/debian/etc/init.d/chef-server-webui +121 -0
  21. data/distro/debian/etc/init.d/chef-solr +177 -0
  22. data/distro/debian/etc/init.d/chef-solr-indexer +176 -0
  23. data/distro/redhat/etc/init.d/chef-client +76 -48
  24. data/distro/redhat/etc/init.d/chef-server +85 -51
  25. data/distro/redhat/etc/init.d/chef-server-webui +85 -51
  26. data/distro/redhat/etc/init.d/chef-solr +77 -49
  27. data/distro/redhat/etc/init.d/chef-solr-indexer +77 -48
  28. data/distro/redhat/etc/logrotate.d/chef-client +8 -0
  29. data/distro/redhat/etc/logrotate.d/chef-server +8 -0
  30. data/distro/redhat/etc/logrotate.d/chef-server-webui +8 -0
  31. data/distro/redhat/etc/logrotate.d/chef-solr +8 -0
  32. data/distro/redhat/etc/logrotate.d/chef-solr-indexer +8 -0
  33. data/distro/redhat/etc/sysconfig/chef-client +9 -4
  34. data/distro/redhat/etc/sysconfig/chef-server +10 -6
  35. data/distro/redhat/etc/sysconfig/chef-server-webui +10 -6
  36. data/distro/redhat/etc/sysconfig/chef-solr +3 -4
  37. data/distro/redhat/etc/sysconfig/chef-solr-indexer +3 -3
  38. data/lib/chef.rb +16 -5
  39. data/lib/chef/application/knife.rb +2 -2
  40. data/lib/chef/application/solo.rb +1 -7
  41. data/lib/chef/cache/checksum.rb +12 -5
  42. data/lib/chef/cache/file_cache_by_checksum.rb +52 -0
  43. data/lib/chef/checksum.rb +115 -0
  44. data/lib/chef/client.rb +193 -185
  45. data/lib/chef/config.rb +9 -1
  46. data/lib/chef/cookbook/cookbook_collection.rb +43 -0
  47. data/lib/chef/cookbook/file_system_file_vendor.rb +53 -0
  48. data/lib/chef/cookbook/file_vendor.rb +47 -0
  49. data/lib/chef/cookbook/metadata.rb +34 -35
  50. data/lib/chef/cookbook/metadata/version.rb +1 -1
  51. data/lib/chef/cookbook_loader.rb +70 -45
  52. data/lib/chef/cookbook_version.rb +760 -0
  53. data/lib/chef/couchdb.rb +8 -5
  54. data/lib/chef/data_bag_item.rb +5 -5
  55. data/lib/chef/exceptions.rb +10 -0
  56. data/lib/chef/file_access_control.rb +134 -0
  57. data/lib/chef/handler.rb +62 -0
  58. data/lib/chef/handler/json_file.rb +47 -0
  59. data/lib/chef/knife.rb +14 -2
  60. data/lib/chef/knife/bootstrap.rb +126 -0
  61. data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
  62. data/lib/chef/knife/cookbook_delete.rb +4 -4
  63. data/lib/chef/knife/cookbook_download.rb +57 -26
  64. data/lib/chef/knife/cookbook_metadata.rb +2 -2
  65. data/lib/chef/knife/cookbook_show.rb +30 -11
  66. data/lib/chef/knife/cookbook_upload.rb +113 -86
  67. data/lib/chef/knife/ec2_server_create.rb +146 -0
  68. data/lib/chef/knife/ec2_server_delete.rb +84 -0
  69. data/lib/chef/knife/ec2_server_list.rb +82 -0
  70. data/lib/chef/knife/status.rb +51 -0
  71. data/lib/chef/mixin/language_include_attribute.rb +16 -11
  72. data/lib/chef/mixin/language_include_recipe.rb +15 -16
  73. data/lib/chef/mixin/recipe_definition_dsl_core.rb +17 -20
  74. data/lib/chef/mixin/shell_out.rb +38 -0
  75. data/lib/chef/mixins.rb +1 -1
  76. data/lib/chef/node.rb +190 -63
  77. data/lib/chef/node/attribute.rb +92 -78
  78. data/lib/chef/platform.rb +24 -4
  79. data/lib/chef/provider.rb +28 -10
  80. data/lib/chef/provider/breakpoint.rb +2 -2
  81. data/lib/chef/provider/cookbook_file.rb +96 -0
  82. data/lib/chef/provider/cron.rb +2 -2
  83. data/lib/chef/provider/deploy.rb +12 -10
  84. data/lib/chef/provider/env.rb +152 -0
  85. data/lib/chef/provider/env/windows.rb +75 -0
  86. data/lib/chef/provider/file.rb +10 -14
  87. data/lib/chef/provider/group.rb +15 -2
  88. data/lib/chef/provider/group/dscl.rb +17 -25
  89. data/lib/chef/provider/group/gpasswd.rb +6 -3
  90. data/lib/chef/provider/group/pw.rb +3 -7
  91. data/lib/chef/provider/group/windows.rb +79 -0
  92. data/lib/chef/provider/link.rb +4 -5
  93. data/lib/chef/provider/mdadm.rb +25 -18
  94. data/lib/chef/provider/mount/mount.rb +28 -27
  95. data/lib/chef/provider/package.rb +35 -35
  96. data/lib/chef/provider/package/dpkg.rb +13 -10
  97. data/lib/chef/provider/package/easy_install.rb +6 -6
  98. data/lib/chef/provider/package/freebsd.rb +17 -51
  99. data/lib/chef/provider/package/rpm.rb +1 -1
  100. data/lib/chef/provider/package/rubygems.rb +391 -74
  101. data/lib/chef/provider/package/yum.rb +2 -2
  102. data/lib/chef/provider/package/zypper.rb +2 -1
  103. data/lib/chef/provider/remote_directory.rb +60 -83
  104. data/lib/chef/provider/remote_file.rb +17 -66
  105. data/lib/chef/provider/script.rb +20 -9
  106. data/lib/chef/provider/service.rb +23 -30
  107. data/lib/chef/provider/service/arch.rb +3 -3
  108. data/lib/chef/provider/service/debian.rb +22 -17
  109. data/lib/chef/provider/service/freebsd.rb +4 -4
  110. data/lib/chef/provider/service/init.rb +2 -2
  111. data/lib/chef/provider/service/redhat.rb +14 -16
  112. data/lib/chef/provider/service/simple.rb +7 -3
  113. data/lib/chef/provider/service/solaris.rb +85 -0
  114. data/lib/chef/provider/service/upstart.rb +12 -7
  115. data/lib/chef/provider/service/windows.rb +2 -2
  116. data/lib/chef/provider/template.rb +133 -118
  117. data/lib/chef/provider/user.rb +34 -17
  118. data/lib/chef/provider/user/dscl.rb +117 -114
  119. data/lib/chef/provider/user/windows.rb +124 -0
  120. data/lib/chef/providers.rb +7 -0
  121. data/lib/chef/recipe.rb +39 -20
  122. data/lib/chef/resource.rb +47 -52
  123. data/lib/chef/resource/apt_package.rb +4 -4
  124. data/lib/chef/resource/bash.rb +4 -4
  125. data/lib/chef/resource/cookbook_file.rb +45 -0
  126. data/lib/chef/resource/cron.rb +3 -3
  127. data/lib/chef/resource/csh.rb +4 -4
  128. data/lib/chef/resource/deploy.rb +3 -3
  129. data/lib/chef/resource/directory.rb +4 -4
  130. data/lib/chef/resource/dpkg_package.rb +4 -4
  131. data/lib/chef/resource/easy_install_package.rb +3 -3
  132. data/lib/chef/resource/env.rb +58 -0
  133. data/lib/chef/resource/erl_call.rb +3 -3
  134. data/lib/chef/resource/execute.rb +3 -3
  135. data/lib/chef/resource/file.rb +3 -3
  136. data/lib/chef/resource/freebsd_package.rb +3 -3
  137. data/lib/chef/resource/gem_package.rb +17 -9
  138. data/lib/chef/resource/git.rb +3 -3
  139. data/lib/chef/resource/group.rb +3 -3
  140. data/lib/chef/resource/http_request.rb +4 -4
  141. data/lib/chef/resource/ifconfig.rb +3 -3
  142. data/lib/chef/resource/link.rb +3 -3
  143. data/lib/chef/resource/log.rb +2 -2
  144. data/lib/chef/resource/macports_package.rb +2 -2
  145. data/lib/chef/resource/mdadm.rb +3 -3
  146. data/lib/chef/resource/mount.rb +2 -2
  147. data/lib/chef/resource/package.rb +4 -4
  148. data/lib/chef/resource/pacman_package.rb +4 -4
  149. data/lib/chef/resource/perl.rb +4 -4
  150. data/lib/chef/resource/portage_package.rb +4 -4
  151. data/lib/chef/resource/python.rb +4 -4
  152. data/lib/chef/resource/remote_directory.rb +3 -3
  153. data/lib/chef/resource/remote_file.rb +26 -3
  154. data/lib/chef/resource/route.rb +3 -3
  155. data/lib/chef/resource/ruby.rb +3 -3
  156. data/lib/chef/resource/ruby_block.rb +3 -2
  157. data/lib/chef/resource/scm.rb +7 -5
  158. data/lib/chef/resource/script.rb +4 -4
  159. data/lib/chef/resource/service.rb +3 -3
  160. data/lib/chef/resource/subversion.rb +4 -2
  161. data/lib/chef/resource/template.rb +3 -3
  162. data/lib/chef/resource/user.rb +3 -3
  163. data/lib/chef/resource/yum_package.rb +3 -3
  164. data/lib/chef/resource_collection.rb +9 -5
  165. data/lib/chef/resources.rb +2 -0
  166. data/lib/chef/rest.rb +4 -0
  167. data/lib/chef/role.rb +2 -0
  168. data/lib/chef/run_context.rb +108 -0
  169. data/lib/chef/run_list.rb +75 -98
  170. data/lib/chef/run_list/run_list_expansion.rb +156 -0
  171. data/lib/chef/run_list/run_list_item.rb +71 -0
  172. data/lib/chef/runner.rb +58 -61
  173. data/lib/chef/sandbox.rb +147 -0
  174. data/lib/chef/shef.rb +4 -3
  175. data/lib/chef/shef/ext.rb +12 -4
  176. data/lib/chef/shef/shef_session.rb +27 -23
  177. data/lib/chef/shell_out.rb +375 -0
  178. data/lib/chef/util/windows.rb +56 -0
  179. data/lib/chef/util/windows/net_group.rb +101 -0
  180. data/lib/chef/util/windows/net_user.rb +198 -0
  181. data/lib/chef/version.rb +20 -0
  182. metadata +112 -22
  183. data/lib/chef/compile.rb +0 -158
  184. data/lib/chef/cookbook.rb +0 -201
  185. data/lib/chef/mixin/generate_url.rb +0 -58
@@ -127,7 +127,7 @@ module Shef
127
127
 
128
128
  def self.client_type
129
129
  type = Shef::StandAloneSession
130
- type = Shef::SoloSession if Chef::Config[:solo]
130
+ type = Shef::SoloSession if Chef::Config[:shef_solo]
131
131
  type = Shef::ClientSession if Chef::Config[:client]
132
132
  type
133
133
  end
@@ -161,11 +161,12 @@ module Shef
161
161
  :default => true,
162
162
  :boolean => true
163
163
 
164
- option :solo,
164
+ option :shef_solo,
165
165
  :short => "-s",
166
166
  :long => "--solo",
167
167
  :description => "chef-solo shef session",
168
- :boolean => true
168
+ :boolean => true,
169
+ :proc => proc {Chef::Config[:solo] = true}
169
170
 
170
171
  option :client,
171
172
  :short => "-z",
@@ -61,6 +61,7 @@ module Shef
61
61
  banner << "".ljust(80, "=")
62
62
  banner << "| " + "Command".ljust(25) + "| " + "Description"
63
63
  banner << "".ljust(80, "=")
64
+
64
65
  self.class.all_help_descriptions.each do |cmd, description|
65
66
  banner << "| " + cmd.ljust(25) + "| " + description
66
67
  end
@@ -86,7 +87,7 @@ module Shef
86
87
  end
87
88
 
88
89
  def all_help_descriptions
89
- if sc = superclass
90
+ if (sc = superclass) && superclass.respond_to?(:help_descriptions)
90
91
  help_descriptions + sc.help_descriptions
91
92
  else
92
93
  help_descriptions
@@ -225,8 +226,7 @@ class Object
225
226
  def run_chef
226
227
  Chef::Log.level = :debug
227
228
  session = Shef.session
228
- session.rebuild_collection
229
- runrun = Chef::Runner.new(node, session.collection, session.definitions, session.cookbook_loader).converge
229
+ runrun = Chef::Runner.new(session.run_context).converge
230
230
  Chef::Log.level = :info
231
231
  runrun
232
232
  end
@@ -275,6 +275,14 @@ class Object
275
275
 
276
276
  end
277
277
 
278
+ class String
279
+ undef_method :version
280
+ end
281
+
282
+ class NilClass
283
+ undef_method :version
284
+ end
285
+
278
286
  class Chef
279
287
  class Recipe
280
288
 
@@ -287,7 +295,7 @@ class Chef
287
295
  desc "list all the resources on the current recipe"
288
296
  def resources(*args)
289
297
  if args.empty?
290
- pp collection.instance_variable_get(:@resources_by_name).keys
298
+ pp run_context.resource_collection.instance_variable_get(:@resources_by_name).keys
291
299
  else
292
300
  pp resources = original_resources(*args)
293
301
  resources
@@ -19,9 +19,8 @@ module Shef
19
19
  class ShefSession
20
20
  include Singleton
21
21
 
22
- attr_accessor :node, :compile, :recipe
22
+ attr_accessor :node, :compile, :recipe, :run_context
23
23
  attr_reader :node_attributes, :client
24
-
25
24
  def initialize
26
25
  @node_built = false
27
26
  end
@@ -34,9 +33,10 @@ module Shef
34
33
  loading do
35
34
  rebuild_node
36
35
  @node = client.node
36
+ rebuild_context
37
37
  node.consume_attributes(node_attributes) if node_attributes
38
38
  shorten_node_inspect
39
- @recipe = Chef::Recipe.new(nil, nil, @node)
39
+ @recipe = Chef::Recipe.new(nil, nil, run_context)
40
40
  @node_built = true
41
41
  end
42
42
  end
@@ -46,8 +46,12 @@ module Shef
46
46
  @node.consume_attributes(@node_attributes)
47
47
  end
48
48
 
49
- def collection
50
- @collection || rebuild_collection
49
+ def resource_collection
50
+ run_context.resource_collection
51
+ end
52
+
53
+ def run_context
54
+ @run_context || rebuild_context
51
55
  end
52
56
 
53
57
  def definitions
@@ -62,7 +66,7 @@ module Shef
62
66
  raise "Not Supported! #{self.class.name} doesn't support #save_node, maybe you need to run shef in client mode?"
63
67
  end
64
68
 
65
- def rebuild_collection
69
+ def rebuild_context
66
70
  raise "Not Implemented! :rebuild_collection should be implemented by subclasses"
67
71
  end
68
72
 
@@ -112,16 +116,18 @@ module Shef
112
116
 
113
117
  class StandAloneSession < ShefSession
114
118
 
115
- def rebuild_collection
116
- @collection = @recipe.collection
119
+ def rebuild_context
120
+ @run_context = Chef::RunContext.new(@node, {}) # no recipes
117
121
  end
118
122
 
119
123
  private
120
124
 
121
125
  def rebuild_node
126
+ Chef::Config[:solo] = true
122
127
  @client = Chef::Client.new
128
+ @client.run_ohai
123
129
  @client.determine_node_name
124
- @client.build_node(@client.node_name, true)
130
+ @client.build_node #(@client.node_name, true)
125
131
  end
126
132
 
127
133
  end
@@ -129,27 +135,22 @@ module Shef
129
135
  class SoloSession < ShefSession
130
136
 
131
137
  def definitions
132
- @compile.definitions
138
+ @run_context.definitions
133
139
  end
134
140
 
135
- def cookbook_loader
136
- @compile.cookbook_loader
137
- end
138
-
139
- def rebuild_collection
140
- @compile = Chef::Compile.new(@client.node)
141
-
142
- @collection = @compile.collection
143
- @collection << @recipe.collection.all_resources
144
- @collection
141
+ def rebuild_context
142
+ @run_context = Chef::RunContext.new(@node, Chef::CookbookCollection.new(Chef::CookbookLoader.new))
145
143
  end
146
144
 
147
145
  private
148
146
 
149
147
  def rebuild_node
148
+ # Tell the client we're chef solo so it won't try to contact the server
149
+ Chef::Config[:solo] = true
150
150
  @client = Chef::Client.new
151
+ @client.run_ohai
151
152
  @client.determine_node_name
152
- @client.build_node(@client.node_name, true)
153
+ @client.build_node #(@client.node_name, true)
153
154
  end
154
155
 
155
156
  end
@@ -163,13 +164,16 @@ module Shef
163
164
  private
164
165
 
165
166
  def rebuild_node
167
+ # Make sure the client knows this is not chef solo
168
+ Chef::Config[:solo] = false
166
169
  @client = Chef::Client.new
170
+ @client.run_ohai
167
171
  @client.determine_node_name
168
172
  @client.register
169
- @client.build_node(@client.node_name, false)
173
+ @client.build_node #(@client.node_name, false)
170
174
 
171
175
  @client.sync_cookbooks
172
176
  end
173
177
 
174
178
  end
175
- end
179
+ end
@@ -0,0 +1,375 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'etc'
20
+ require 'tmpdir'
21
+ require 'chef/log'
22
+
23
+ class Chef
24
+
25
+ # Provides a simplified interface to shelling out yet still collecting both
26
+ # standard out and standard error and providing full control over environment,
27
+ # working directory, uid, gid, etc.
28
+ #
29
+ # No means for passing input to the subprocess is provided, nor is there any
30
+ # way to inspect the output of the command as it is being read. If you need
31
+ # to do that, you have to use Chef::Mixin::Command.popen4
32
+ #
33
+ # == Platform Support
34
+ # Chef::ShellOut uses Kernel.fork() and is therefore unsuitable for Windows
35
+ # or jruby.
36
+ class ShellOut
37
+ READ_WAIT_TIME = 0.01
38
+ DEFAULT_READ_TIMEOUT = 60
39
+ DEFAULT_ENVIRONMENT = {'LC_ALL' => 'C'}
40
+
41
+ attr_accessor :user, :group, :cwd, :valid_exit_codes
42
+ attr_reader :command, :umask, :environment
43
+ attr_writer :timeout
44
+
45
+ attr_reader :stdout, :stderr, :status
46
+
47
+ attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe
48
+
49
+ # === Arguments:
50
+ # Takes a single command, or a list of command fragments. These are used
51
+ # as arguments to Kernel.exec. See the Kernel.exec documentation for more
52
+ # explanation of how arguments are evaluated. The last argument can be an
53
+ # options Hash.
54
+ # === Options:
55
+ # If the last argument is a Hash, it is removed from the list of args passed
56
+ # to exec and used as an options hash. The following options are available:
57
+ # * user: the user the commmand should run as. if an integer is given, it is
58
+ # used as a uid. A string is treated as a username and resolved to a uid
59
+ # with Etc.getpwnam
60
+ # * group: the group the command should run as. works similarly to +user+
61
+ # * cwd: the directory to chdir to before running the command
62
+ # * umask: a umask to set before running the command. If given as an Integer,
63
+ # be sure to use two leading zeros so it's parsed as Octal. A string will
64
+ # be treated as an octal integer
65
+ # * returns: one or more Integer values to use as valid exit codes for the
66
+ # subprocess. This only has an effect if you call +error!+ after
67
+ # +run_command+.
68
+ # * environment: a Hash of environment variables to set before the command
69
+ # is run. By default, the environment will *always* be set to 'LC_ALL' => 'C'
70
+ # to prevent issues with multibyte characters in Ruby 1.8. To avoid this,
71
+ # use :environment => nil for *no* extra environment settings, or
72
+ # :environment => {'LC_ALL'=>nil, ...} to set other environment settings
73
+ # without changing the locale.
74
+ def initialize(*command_args)
75
+ @stdout, @stderr = '', ''
76
+ @environment = DEFAULT_ENVIRONMENT
77
+ @cwd = Dir.tmpdir
78
+ @valid_exit_codes = [0]
79
+
80
+ if command_args.last.is_a?(Hash)
81
+ parse_options(command_args.pop)
82
+ end
83
+
84
+ @command = command_args.size == 1 ? command_args.first : command_args
85
+ end
86
+
87
+ def umask=(new_umask)
88
+ @umask = (new_umask.respond_to?(:oct) ? new_umask.oct : new_umask.to_i) & 007777
89
+ end
90
+
91
+ def uid
92
+ return nil unless user
93
+ user.kind_of?(Integer) ? user : Etc.getpwnam(user.to_s).uid
94
+ end
95
+
96
+ def gid
97
+ return nil unless group
98
+ group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid
99
+ end
100
+
101
+ def timeout
102
+ @timeout || DEFAULT_READ_TIMEOUT
103
+ end
104
+
105
+ def format_for_exception
106
+ msg = ""
107
+ msg << "---- Begin output of #{command} ----\n"
108
+ msg << "STDOUT: #{stdout.strip}\n"
109
+ msg << "STDERR: #{stderr.strip}\n"
110
+ msg << "---- End output of #{command} ----\n"
111
+ msg << "Ran #{command} returned #{status.exitstatus}" if status
112
+ msg
113
+ end
114
+
115
+ def exitstatus
116
+ @status && @status.exitstatus
117
+ end
118
+
119
+ # Run the command, writing the command's standard out and standard error
120
+ # to +stdout+ and +stderr+, and saving its exit status object to +status+
121
+ # === Returns
122
+ # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
123
+ # populated with results of the command
124
+ # === Raises
125
+ # * Errno::EACCES when you are not privileged to execute the command
126
+ # * Errno::ENOENT when the command is not available on the system (or not
127
+ # in the current $PATH)
128
+ # * Chef::Exceptions::CommandTimeout when the command does not complete
129
+ # within +timeout+ seconds (default: 60s)
130
+ def run_command
131
+ Chef::Log.debug("sh(#{@command})")
132
+
133
+ @child_pid = fork_subprocess
134
+
135
+ configure_parent_process_file_descriptors
136
+ propagate_pre_exec_failure
137
+
138
+ @result = nil
139
+ read_time = 0
140
+
141
+ until @status
142
+ ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME)
143
+ unless ready
144
+ read_time += READ_WAIT_TIME
145
+ if read_time >= timeout && !@result
146
+ raise Chef::Exceptions::CommandTimeout, "command timed out:\n#{format_for_exception}"
147
+ end
148
+ end
149
+
150
+ if ready && ready.first.include?(child_stdout)
151
+ read_stdout_to_buffer
152
+ end
153
+ if ready && ready.first.include?(child_stderr)
154
+ read_stderr_to_buffer
155
+ end
156
+
157
+ unless @status
158
+ # make one more pass to get the last of the output after the
159
+ # child process dies
160
+ if results = Process.waitpid2(@child_pid, Process::WNOHANG)
161
+ @status = results.last
162
+ redo
163
+ end
164
+ end
165
+ end
166
+ self
167
+ rescue Exception
168
+ # do our best to kill zombies
169
+ Process.waitpid2(@child_pid, Process::WNOHANG) rescue nil
170
+ raise
171
+ ensure
172
+ close_all_pipes
173
+ end
174
+
175
+ def error!
176
+ unless Array(valid_exit_codes).include?(exitstatus)
177
+ invalid!("Expected process to exit 0, but it exited with #{exitstatus}")
178
+ end
179
+ end
180
+
181
+ # Raises a Chef::Exceptions::ShellCommandFailed exception, appending the
182
+ # command's stdout, stderr, and exitstatus to the exception message.
183
+ # === Arguments
184
+ # +msg+ A String to use as the basis of the exception message. The
185
+ # default explanation is very generic, providing a more
186
+ # informative message is highly encouraged.
187
+ # === Raises
188
+ # Chef::Exceptions::ShellCommandFailed always
189
+ def invalid!(msg=nil)
190
+ msg ||= "Command produced unexpected results"
191
+ raise Chef::Exceptions::ShellCommandFailed, msg + "\n" + format_for_exception
192
+ end
193
+
194
+ def inspect
195
+ "<#{self.class.name}##{object_id}: command: '#@command' process_status: #{@status.inspect} " +
196
+ "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
197
+ "environment: #{@environment.inspect} timeout: #{timeout} user: #@user group: #@group working_dir: #@cwd >"
198
+ end
199
+
200
+ private
201
+
202
+ def parse_options(opts)
203
+ opts.each do |option, setting|
204
+ case option.to_s
205
+ when 'cwd'
206
+ self.cwd = setting
207
+ when 'user'
208
+ self.user = setting
209
+ when 'group'
210
+ self.group = setting
211
+ when 'umask'
212
+ self.umask = setting
213
+ when 'timeout'
214
+ self.timeout = setting
215
+ when 'returns'
216
+ self.valid_exit_codes = Array(setting)
217
+ when 'environment', 'env'
218
+ # passing :environment => nil means don't set any new ENV vars
219
+ setting.nil? ? @environment = {} : @environment.merge!(setting)
220
+ else
221
+ raise Chef::Exceptions::InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
222
+ end
223
+ end
224
+ end
225
+
226
+ def set_user
227
+ if user
228
+ Process.euid = uid
229
+ Process.uid = uid
230
+ end
231
+ end
232
+
233
+ def set_group
234
+ if group
235
+ Process.egid = gid
236
+ Process.gid = gid
237
+ end
238
+ end
239
+
240
+ def set_environment
241
+ environment.each do |env_var,value|
242
+ ENV[env_var] = value
243
+ end
244
+ end
245
+
246
+ def set_umask
247
+ File.umask(umask) if umask
248
+ end
249
+
250
+ def initialize_ipc
251
+ @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe
252
+ @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
253
+ end
254
+
255
+ def child_stdout
256
+ @stdout_pipe[0]
257
+ end
258
+
259
+ def child_stderr
260
+ @stderr_pipe[0]
261
+ end
262
+
263
+ def child_process_status
264
+ @process_status_pipe[0]
265
+ end
266
+
267
+ def close_all_pipes
268
+ child_stdout.close unless child_stdout.closed?
269
+ child_stderr.close unless child_stderr.closed?
270
+ child_process_status.close unless child_process_status.closed?
271
+ #rescue NoMethodError # we blew up before IPC was setup
272
+ end
273
+
274
+ # replace stdout, and stderr with pipes to the parent, and close the
275
+ # reader side of the error marshaling side channel. Close STDIN so when we
276
+ # exec, the new program will no it's never getting input ever.
277
+ def configure_subprocess_file_descriptors
278
+ process_status_pipe.first.close
279
+
280
+ # HACK: for some reason, just STDIN.close isn't good enough when running
281
+ # under ruby 1.9.2, so make it good enough:
282
+ stdin_reader, stdin_writer = IO.pipe
283
+ stdin_writer.close
284
+ STDIN.reopen stdin_reader
285
+ stdin_reader.close
286
+
287
+ stdout_pipe.first.close
288
+ STDOUT.reopen stdout_pipe.last
289
+ stdout_pipe.last.close
290
+
291
+ stderr_pipe.first.close
292
+ STDERR.reopen stderr_pipe.last
293
+ stderr_pipe.last.close
294
+
295
+ STDOUT.sync = STDERR.sync = true
296
+ end
297
+
298
+ def configure_parent_process_file_descriptors
299
+ # Close the sides of the pipes we don't care about
300
+ stdout_pipe.last.close
301
+ stderr_pipe.last.close
302
+ process_status_pipe.last.close
303
+ # Get output as it happens rather than buffered
304
+ child_stdout.sync = true
305
+ child_stderr.sync = true
306
+ # Set file descriptors to non-blocking IO. man(2) fcntl
307
+ child_stdout.fcntl(Fcntl::F_SETFL, child_stdout.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
308
+ child_stderr.fcntl(Fcntl::F_SETFL, child_stderr.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
309
+ true
310
+ end
311
+
312
+ # Some patch levels of ruby in wide use (in particular the ruby 1.8.6 on OSX)
313
+ # segfault when you IO.select a pipe that's reached eof. Weak sauce.
314
+ def open_pipes
315
+ @open_pipes ||= [child_stdout, child_stderr]
316
+ end
317
+
318
+ def read_stdout_to_buffer
319
+ while chunk = child_stdout.read(16 * 1024)
320
+ @stdout << chunk
321
+ end
322
+ rescue Errno::EAGAIN
323
+ rescue EOFError
324
+ open_pipes.delete_at(0)
325
+ end
326
+
327
+ def read_stderr_to_buffer
328
+ while chunk = child_stderr.read(16 * 1024)
329
+ @stderr << chunk
330
+ end
331
+ rescue Errno::EAGAIN
332
+ rescue EOFError
333
+ open_pipes.delete_at(1)
334
+ end
335
+
336
+ def fork_subprocess
337
+ initialize_ipc
338
+
339
+ fork do
340
+ configure_subprocess_file_descriptors
341
+
342
+ set_user
343
+ set_group
344
+ set_environment
345
+ set_umask
346
+
347
+ begin
348
+ command.kind_of?(Array) ? exec(*command) : exec(command)
349
+
350
+ raise 'forty-two' # Should never get here
351
+ rescue Exception => e
352
+ Marshal.dump(e, process_status_pipe.last)
353
+ process_status_pipe.last.flush
354
+ end
355
+ process_status_pipe.last.close unless (process_status_pipe.last.closed?)
356
+ exit!
357
+ end
358
+ end
359
+
360
+ # Attempt to get a Marshaled error from the side-channel.
361
+ # If it's there, un-marshal it and raise. If it's not there,
362
+ # assume everything went well.
363
+ def propagate_pre_exec_failure
364
+ begin
365
+ e = Marshal.load child_process_status
366
+ raise(Exception === e ? e : "unknown failure: #{e.inspect}")
367
+ rescue EOFError # If we get an EOF error, then the exec was successful
368
+ true
369
+ ensure
370
+ child_process_status.close
371
+ end
372
+ end
373
+
374
+ end
375
+ end