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
@@ -48,14 +48,14 @@ class Chef
48
48
  @current_resource.version(nil)
49
49
 
50
50
  # get the currently installed version if installed
51
+ package_version = nil
51
52
  if install_check(@new_resource.package_name)
52
53
  command = "python -c \"import #{@new_resource.package_name}; print #{@new_resource.package_name}.__path__\""
53
- pid, stdin, stdout, stderr = popen4(command)
54
- install_location = stdout.readline
55
- install_location[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
56
- package_version = $3
57
- else
58
- package_version = nil
54
+ status = popen4(command) do |pid, stdin, stdout, stderr|
55
+ install_location = stdout.readline
56
+ install_location[/\S\S(.*)\/(.*)-(.*)-py(.*).egg\S/]
57
+ package_version = $3
58
+ end
59
59
  end
60
60
 
61
61
  if package_version == @new_resource.version
@@ -18,13 +18,14 @@
18
18
  #
19
19
 
20
20
  require 'chef/provider/package'
21
- require 'chef/mixin/command'
21
+ require 'chef/mixin/shell_out'
22
22
  require 'chef/resource/package'
23
23
 
24
24
  class Chef
25
25
  class Provider
26
26
  class Package
27
27
  class Freebsd < Chef::Provider::Package
28
+ include Chef::Mixin::ShellOut
28
29
 
29
30
  def initialize(*args)
30
31
  super
@@ -32,19 +33,8 @@ class Chef
32
33
  end
33
34
 
34
35
  def current_installed_version
35
- command = "pkg_info -E \"#{package_name}*\""
36
- status = popen4(command) do |pid, stdin, stdout, stderr|
37
- stdout.each do |line|
38
- case line
39
- when /^#{package_name}-(.+)/
40
- return $1
41
- end
42
- end
43
- end
44
- unless status.exitstatus == 0 || status.exitstatus == 1
45
- raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
46
- end
47
- nil
36
+ pkg_info = shell_out!("pkg_info -E \"#{package_name}*\"", :env => nil, :returns => [0,1])
37
+ pkg_info.stdout[/^#{package_name}-(.+)/, 1]
48
38
  end
49
39
 
50
40
  def port_path
@@ -58,27 +48,17 @@ class Chef
58
48
  "/usr/ports/#{@new_resource.package_name}"
59
49
  # Otherwise look up the path to the ports directory using 'whereis'
60
50
  else
61
- popen4("whereis -s #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
62
- stdout.each do |line|
63
- case line
64
- when /^#{@new_resource.package_name}:\s+(.+)$/
65
- return $1
66
- end
67
- end
51
+ whereis = shell_out!("whereis -s #{@new_resource.package_name}", :env => nil)
52
+ unless path = whereis.stdout[/^#{@new_resource.package_name}:\s+(.+)$/, 1]
53
+ raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}"
68
54
  end
69
- raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}"
55
+ path
70
56
  end
71
57
  end
72
58
 
73
59
  def ports_makefile_variable_value(variable)
74
- command = "cd #{port_path}; make -V #{variable}"
75
- status = popen4(command) do |pid, stdin, stdout, stderr|
76
- return stdout.readline.strip
77
- end
78
- unless status.exitstatus == 0 || status.exitstatus == 1
79
- raise Chef::Exceptions::Package, "#{command} failed - #{status.inspect}!"
80
- end
81
- nil
60
+ make_v = shell_out!("make -V #{variable}", :cwd => port_path, :env => nil, :returns => [0,1])
61
+ make_v.stdout.strip.split($\).first # $\ is the line separator, i.e., newline
82
62
  end
83
63
 
84
64
  def ports_candidate_version
@@ -114,26 +94,15 @@ class Chef
114
94
  unless @current_resource.version
115
95
  case @new_resource.source
116
96
  when /^ports$/
117
- run_command_with_systems_locale(
118
- :command => "make -DBATCH install",
119
- :cwd => "#{port_path}"
120
- )
97
+ shell_out!("make -DBATCH install", :cwd => port_path, :env => nil).status
121
98
  when /^http/, /^ftp/
122
- run_command_with_systems_locale(
123
- :command => "pkg_add -r #{package_name}",
124
- :environment => { "PACKAGESITE" => @new_resource.source }
125
- )
99
+ shell_out!("pkg_add -r #{package_name}", :env => { "PACKAGESITE" => @new_resource.source, 'LC_ALL' => nil }).status
126
100
  Chef::Log.info("Installed package #{package_name} from: #{@new_resource.source}")
127
101
  when /^\//
128
- run_command_with_systems_locale(
129
- :command => "pkg_add #{@new_resource.name}",
130
- :environment => { "PKG_PATH" => @new_resource.source }
131
- )
102
+ shell_out!("pkg_add #{@new_resource.name}", :env => { "PKG_PATH" => @new_resource.source , 'LC_ALL'=>nil}).status
132
103
  Chef::Log.info("Installed package #{@new_resource.name} from: #{@new_resource.source}")
133
104
  else
134
- run_command_with_systems_locale(
135
- :command => "pkg_add -r #{latest_link_name}"
136
- )
105
+ shell_out!("pkg_add -r #{latest_link_name}", :env => nil).status
137
106
  Chef::Log.info("Installed package #{package_name}")
138
107
  end
139
108
  end
@@ -142,15 +111,12 @@ class Chef
142
111
  def remove_package(name, version)
143
112
  # a version is mandatory
144
113
  if version
145
- run_command_with_systems_locale(
146
- :command => "pkg_delete #{package_name}-#{version}"
147
- )
114
+ shell_out!("pkg_delete #{package_name}-#{version}", :env => nil).status
148
115
  else
149
- run_command_with_systems_locale(
150
- :command => "pkg_delete #{package_name}-#{@current_resource.version}"
151
- )
116
+ shell_out!("pkg_delete #{package_name}-#{@current_resource.version}", :env => nil).status
152
117
  end
153
118
  end
119
+
154
120
  end
155
121
  end
156
122
  end
@@ -45,7 +45,7 @@ class Chef
45
45
  end
46
46
  end
47
47
  else
48
- if @new_resource.action.include?(:install)
48
+ if Array(@new_resource.action).include?(:install)
49
49
  raise Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
50
50
  end
51
51
  end
@@ -1,14 +1,15 @@
1
1
  #
2
2
  # Author:: Adam Jacob (<adam@opscode.com>)
3
- # Copyright:: Copyright (c) 2008 Opscode, Inc.
3
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
4
+ # Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
4
5
  # License:: Apache License, Version 2.0
5
6
  #
6
7
  # Licensed under the Apache License, Version 2.0 (the "License");
7
8
  # you may not use this file except in compliance with the License.
8
9
  # You may obtain a copy of the License at
9
- #
10
+ #
10
11
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
+ #
12
13
  # Unless required by applicable law or agreed to in writing, software
13
14
  # distributed under the License is distributed on an "AS IS" BASIS,
14
15
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,113 +21,429 @@ require 'chef/provider/package'
20
21
  require 'chef/mixin/command'
21
22
  require 'chef/resource/package'
22
23
 
24
+ # Class methods on Gem are defined in rubygems
25
+ require 'rubygems'
26
+ # Ruby 1.9's gem_prelude can interact poorly with loading the full rubygems
27
+ # explicitly like this. Make sure rubygems/specification is always last in this
28
+ # list
29
+ require 'rubygems/version'
30
+ require 'rubygems/dependency'
31
+ require 'rubygems/spec_fetcher'
32
+ require 'rubygems/platform'
33
+ require 'rubygems/format'
34
+ require 'rubygems/dependency_installer'
35
+ require 'rubygems/uninstaller'
36
+ require 'rubygems/specification'
37
+
23
38
  class Chef
24
39
  class Provider
25
40
  class Package
26
- class Rubygems < Chef::Provider::Package
27
-
28
- def gem_list_parse(line)
29
- installed_versions = Array.new
30
- if md = line.match(/^#{@new_resource.package_name} \((.+?)(?: [^\)\.]+)?\)$/)
31
- md.captures.first.split(/, /)
32
- else
33
- nil
41
+ class Rubygems < Chef::Provider::Package
42
+ class GemEnvironment
43
+ # HACK: trigger gem config load early. Otherwise it can get lazy
44
+ # loaded during operations where we've set Gem.sources to an
45
+ # alternate value and overwrite it with the defaults.
46
+ Gem.configuration
47
+
48
+ DEFAULT_UNINSTALLER_OPTS = {:ignore => true, :executables => true}
49
+
50
+ ##
51
+ # The paths where rubygems should search for installed gems.
52
+ # Implemented by subclasses.
53
+ def gem_paths
54
+ raise NotImplementedError
55
+ end
56
+
57
+ ##
58
+ # A rubygems source index containing the list of gemspecs for all
59
+ # available gems in the gem installation.
60
+ # Implemented by subclasses
61
+ # === Returns
62
+ # Gem::SourceIndex
63
+ def gem_source_index
64
+ raise NotImplementedError
65
+ end
66
+
67
+ ##
68
+ # Lists the installed versions of +gem_name+, constrained by the
69
+ # version spec in +gem_dep+
70
+ # === Arguments
71
+ # Gem::Dependency +gem_dep+ is a Gem::Dependency object, its version
72
+ # specification constrains which gems are returned.
73
+ # === Returns
74
+ # [Gem::Specification] an array of Gem::Specification objects
75
+ def installed_versions(gem_dep)
76
+ gem_source_index.search(gem_dep)
77
+ end
78
+
79
+ ##
80
+ # Yields to the provided block with rubygems' source list set to the
81
+ # list provided. Always resets the list when the block returns or
82
+ # raises an exception.
83
+ def with_gem_sources(*sources)
84
+ sources.compact!
85
+ original_sources = Gem.sources
86
+ Gem.sources = sources unless sources.empty?
87
+ yield
88
+ ensure
89
+ Gem.sources = original_sources
90
+ end
91
+
92
+ ##
93
+ # Determines the candidate version for a gem from a .gem file on disk
94
+ # and checks if it matches the version contraints in +gem_dependency+
95
+ # === Returns
96
+ # Gem::Version a singular gem version object is returned if the gem
97
+ # is available
98
+ # nil returns nil if the gem on disk doesn't match the
99
+ # version constraints for +gem_dependency+
100
+ def candidate_version_from_file(gem_dependency, source)
101
+ spec = Gem::Format.from_file_by_path(source).spec
102
+ if spec.satisfies_requirement?(gem_dependency)
103
+ logger.debug {"found candidate gem version #{spec.version} from local gem package #{source}"}
104
+ spec.version
105
+ else
106
+ # This is probably going to end badly...
107
+ logger.warn { "The gem package #{source} does not satisfy the requirements #{gem_dependency.to_s}" }
108
+ nil
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Finds the newest version that satisfies the constraints of
114
+ # +gem_dependency+. The version is determined from the cache or a
115
+ # round-trip to the server as needed. The architecture and gem
116
+ # sources will be set before making the query.
117
+ # === Returns
118
+ # Gem::Version a singular gem version object is returned if the gem
119
+ # is available
120
+ # nil returns nil if the gem could not be found
121
+ def candidate_version_from_remote(gem_dependency, *sources)
122
+ raise NotImplementedError
123
+ end
124
+
125
+ ##
126
+ # Find the newest gem version available from Gem.sources that satisfies
127
+ # the constraints of +gem_dependency+
128
+ def find_newest_remote_version(gem_dependency, *sources)
129
+ # DependencyInstaller sorts the results such that the last one is
130
+ # always the one it considers best.
131
+ spec_with_source = dependency_installer.find_gems_with_sources(gem_dependency).last
132
+
133
+ spec = spec_with_source && spec_with_source[0]
134
+ version = spec && spec_with_source[0].version
135
+ if version
136
+ logger.debug { "Found gem #{spec.name} version #{version} for platform #{spec.platform} from #{spec_with_source[1]}" }
137
+ version
138
+ else
139
+ source_list = sources.compact.empty? ? "[#{Gem.sources.join(', ')}]" : "[#{sources.join(', ')}]"
140
+ logger.warn { "Failed to find gem #{gem_dependency} from #{source_list}" }
141
+ nil
142
+ end
143
+ end
144
+
145
+ ##
146
+ # Installs a gem via the rubygems ruby API.
147
+ # === Options
148
+ # :sources rubygems servers to use
149
+ # Other options are passed to Gem::DependencyInstaller.new
150
+ def install(gem_dependency, options={})
151
+ with_gem_sources(*options.delete(:sources)) do
152
+ dependency_installer(options).install(gem_dependency)
153
+ end
154
+ end
155
+
156
+ ##
157
+ # Uninstall the gem +gem_name+ via the rubygems ruby API. If
158
+ # +gem_version+ is provided, only that version will be uninstalled.
159
+ # Otherwise, all versions are uninstalled.
160
+ # === Options
161
+ # Options are passed to Gem::Uninstaller.new
162
+ def uninstall(gem_name, gem_version=nil, opts={})
163
+ gem_version ? opts[:version] = gem_version : opts[:all] = true
164
+ uninstaller(gem_name, opts).uninstall
165
+ end
166
+
167
+ def dependency_installer(opts={})
168
+ Gem::DependencyInstaller.new(opts)
169
+ end
170
+
171
+ def uninstaller(gem_name, opts={})
172
+ Gem::Uninstaller.new(gem_name, DEFAULT_UNINSTALLER_OPTS.merge(opts))
34
173
  end
174
+
175
+ private
176
+
177
+ def logger
178
+ Chef::Log.logger
179
+ end
180
+
35
181
  end
36
182
 
37
- def gem_binary_path
38
- path = @new_resource.gem_binary
39
- path ? path : 'gem'
183
+ class CurrentGemEnvironment < GemEnvironment
184
+
185
+ def gem_paths
186
+ Gem.path
187
+ end
188
+
189
+ def gem_source_index
190
+ Gem.source_index
191
+ end
192
+
193
+ def candidate_version_from_remote(gem_dependency, *sources)
194
+ with_gem_sources(*sources) do
195
+ find_newest_remote_version(gem_dependency, *sources)
196
+ end
197
+ end
198
+
40
199
  end
41
-
42
- def load_current_resource
43
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
44
- @current_resource.package_name(@new_resource.package_name)
45
- @current_resource.version(nil)
46
-
47
- # First, we need to look up whether we have the local gem installed or not
48
- status = popen4("#{gem_binary_path} list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
49
- stdout.each_line do |line|
50
- next unless installed_versions = gem_list_parse(line)
51
- # If the version we are asking for is installed, make that our current
52
- # version. Otherwise, go ahead and use the highest one, which
53
- # happens to come first in the array.
54
- if installed_versions.detect { |v| v == @new_resource.version }
55
- Chef::Log.debug("#{@new_resource.package_name} at version #{@new_resource.version}")
56
- @current_resource.version(@new_resource.version)
200
+
201
+ class AlternateGemEnvironment < GemEnvironment
202
+ JRUBY_PLATFORM = /(:?universal|x86_64|x86)\-java\-[0-9\.]+/
203
+
204
+ def self.gempath_cache
205
+ @gempath_cache ||= {}
206
+ end
207
+
208
+ def self.platform_cache
209
+ @platform_cache ||= {}
210
+ end
211
+
212
+ include Chef::Mixin::ShellOut
213
+
214
+ attr_reader :gem_binary_location
215
+
216
+ def initialize(gem_binary_location)
217
+ @gem_binary_location = gem_binary_location
218
+ end
219
+
220
+ def gem_paths
221
+ if self.class.gempath_cache.key?(@gem_binary_location)
222
+ self.class.gempath_cache[@gem_binary_location]
223
+ else
224
+ # shellout! is a fork/exec which won't work on windows
225
+ shell_style_paths = shell_out!("#{@gem_binary_location} env gempath").stdout
226
+ # on windows, the path separator is (usually? always?) semicolon
227
+ paths = shell_style_paths.split(::File::PATH_SEPARATOR).map { |path| path.strip }
228
+ self.class.gempath_cache[@gem_binary_location] = paths
229
+ end
230
+ end
231
+
232
+ def gem_source_index
233
+ @source_index ||= Gem::SourceIndex.from_gems_in(*gem_paths.map { |p| p + '/specifications' })
234
+ end
235
+
236
+ ##
237
+ # Attempt to detect the correct platform settings for the target gem
238
+ # environment.
239
+ #
240
+ # In practice, this only makes a difference if different versions are
241
+ # available depending on platform, and only if the target gem
242
+ # environment has a radically different platform (i.e., jruby), so we
243
+ # just try to detect jruby and fall back to the current platforms
244
+ # (Gem.platforms) if we don't detect it.
245
+ #
246
+ # === Returns
247
+ # [String|Gem::Platform] returns an array of Gem::Platform-compatible
248
+ # objects, i.e., Strings that are valid for Gem::Platform or actual
249
+ # Gem::Platform objects.
250
+ def gem_platforms
251
+ if self.class.platform_cache.key?(@gem_binary_location)
252
+ self.class.platform_cache[@gem_binary_location]
253
+ else
254
+ gem_environment = shell_out!("#{@gem_binary_location} env").stdout
255
+ if jruby = gem_environment[JRUBY_PLATFORM]
256
+ self.class.platform_cache[@gem_binary_location] = ['ruby', Gem::Platform.new(jruby)]
57
257
  else
58
- iv = installed_versions.first
59
- Chef::Log.debug("#{@new_resource.package_name} at version #{iv}")
60
- @current_resource.version(iv)
258
+ self.class.platform_cache[@gem_binary_location] = Gem.platforms
61
259
  end
62
260
  end
63
261
  end
64
-
65
- unless status.exitstatus == 0
66
- raise Chef::Exceptions::Package, "#{gem_binary_path} list --local failed - #{status.inspect}!"
262
+
263
+ def with_gem_platforms(*alt_gem_platforms)
264
+ alt_gem_platforms.flatten!
265
+ original_gem_platforms = Gem.platforms
266
+ Gem.platforms = alt_gem_platforms
267
+ yield
268
+ ensure
269
+ Gem.platforms = original_gem_platforms
67
270
  end
68
-
69
- @current_resource
70
- end
71
271
 
72
- def candidate_version
73
- return @candidate_version if @candidate_version
74
-
75
- status = popen4("#{gem_binary_path} list --remote #{@new_resource.package_name}#{' --source=' + @new_resource.source if @new_resource.source}") do |pid, stdin, stdout, stderr|
76
- stdout.each_line do |line|
77
- next unless installed_versions = gem_list_parse(line)
78
- Chef::Log.debug("candidate_version: remote rubygem(s) available: #{installed_versions.inspect}")
79
-
80
- unless installed_versions.empty?
81
- Chef::Log.debug("candidate_version: setting install candidate version to #{installed_versions.first}")
82
- @candidate_version = installed_versions.first
272
+ def candidate_version_from_remote(gem_dependency, *sources)
273
+ with_gem_sources(*sources) do
274
+ with_gem_platforms(*gem_platforms) do
275
+ find_newest_remote_version(gem_dependency, *sources)
83
276
  end
84
277
  end
278
+ end
279
+
280
+ end
281
+
282
+ include Chef::Mixin::ShellOut
283
+
284
+ attr_reader :gem_env
285
+
286
+ def logger
287
+ Chef::Log.logger
288
+ end
289
+
290
+ def initialize(new_resource, run_context=nil)
291
+ super
292
+ if new_resource.gem_binary
293
+ if new_resource.options && new_resource.options.kind_of?(Hash)
294
+ msg = "options cannot be given as a hash when using an explicit gem_binary\n"
295
+ msg << "in #{new_resource} from #{new_resource.source_line}"
296
+ raise ArgumentError, msg
297
+ end
298
+ @gem_env = AlternateGemEnvironment.new(new_resource.gem_binary)
299
+ else
300
+ @gem_env = CurrentGemEnvironment.new
301
+ end
302
+ end
303
+
304
+ def gem_dependency
305
+ Gem::Dependency.new(@new_resource.package_name, @new_resource.version)
306
+ end
307
+
308
+ def source_is_remote?
309
+ return true if @new_resource.source.nil?
310
+ URI.parse(@new_resource.source).absolute?
311
+ end
312
+
313
+ def current_version
314
+ #raise 'todo'
315
+ # If one or more matching versions are installed, the newest of them
316
+ # is the current version
317
+ if !matching_installed_versions.empty?
318
+ gemspec = matching_installed_versions.last
319
+ logger.debug { "Found installed gem #{gemspec.name} version #{gemspec.version} matching #{gem_dependency}"}
320
+ gemspec
321
+ # If no version matching the requirements exists, the latest installed
322
+ # version is the current version.
323
+ elsif !all_installed_versions.empty?
324
+ gemspec = all_installed_versions.last
325
+ logger.debug { "Newest installed version of gem #{gemspec.name} is #{gemspec.version}" }
326
+ gemspec
327
+ else
328
+ logger.debug { "No installed version found for #{gem_dependency.to_s}"}
329
+ nil
330
+ end
331
+ end
332
+
333
+ def matching_installed_versions
334
+ @matching_installed_versions ||= @gem_env.installed_versions(gem_dependency)
335
+ end
336
+
337
+ def all_installed_versions
338
+ @all_installed_versions ||= begin
339
+ @gem_env.installed_versions(Gem::Dependency.new(gem_dependency.name))
340
+ end
341
+ end
85
342
 
343
+ def gem_sources
344
+ @new_resource.source ? [@new_resource.source, 'http://rubygems.org'] : nil
345
+ end
346
+
347
+ def load_current_resource
348
+ @current_resource = Chef::Resource::Package::GemPackage.new(@new_resource.name)
349
+ @current_resource.package_name(@new_resource.package_name)
350
+ if current_spec = current_version
351
+ @current_resource.version(current_spec.version.to_s)
86
352
  end
353
+ @current_resource
354
+ end
87
355
 
88
- unless status.exitstatus == 0
89
- raise Chef::Exceptions::Package, "#{gem_binary_path} list --remote failed - #{status.inspect}!"
356
+ def candidate_version
357
+ @candidate_version ||= begin
358
+ if target_version_already_installed?
359
+ nil
360
+ elsif source_is_remote?
361
+ @gem_env.candidate_version_from_remote(gem_dependency, *gem_sources).to_s
362
+ else
363
+ @gem_env.candidate_version_from_file(gem_dependency, @new_resource.source).to_s
364
+ end
90
365
  end
91
- @candidate_version
92
366
  end
93
-
367
+
368
+ def target_version_already_installed?
369
+ return false unless @current_resource && @current_resource.version
370
+ return false if @current_resource.version.nil?
371
+ # in the future we could support squiggly requirements like "~> 1.2.0"
372
+ # for now, the behavior when using anything other than exact
373
+ # requirements is undefined.
374
+ Gem::Requirement.new(@new_resource.version).satisfied_by?(Gem::Version.new(@current_resource.version))
375
+ end
376
+
377
+ ##
378
+ # Installs the gem, using either the gems API or shelling out to `gem`
379
+ # according to the following criteria:
380
+ # 1. Use gems API (Gem::DependencyInstaller) by default
381
+ # 2. shell out to `gem install` when a String of options is given
382
+ # 3. use gems API with options if a hash of options is given
94
383
  def install_package(name, version)
95
- src = nil
96
- if @new_resource.source
97
- src = " --source=#{@new_resource.source} --source=http://rubygems.org"
98
- end
99
- run_command_with_systems_locale(
100
- :command => "#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}"
101
- )
102
- end
103
-
384
+ if source_is_remote? && @new_resource.gem_binary.nil?
385
+ if @new_resource.options.nil?
386
+ @gem_env.install(gem_dependency, :sources => gem_sources)
387
+ elsif @new_resource.options.kind_of?(Hash)
388
+ options = @new_resource.options
389
+ options[:sources] = gem_sources
390
+ @gem_env.install(gem_dependency, options)
391
+ else
392
+ install_via_gem_command(name, version)
393
+ end
394
+ elsif @new_resource.gem_binary.nil?
395
+ @gem_env.install(@new_resource.source)
396
+ else
397
+ install_via_gem_command(name,version)
398
+ end
399
+ true
400
+ end
401
+
402
+ def gem_binary_path
403
+ @new_resource.gem_binary || 'gem'
404
+ end
405
+
406
+ def install_via_gem_command(name, version)
407
+ src = @new_resource.source && " --source=#{@new_resource.source} --source=http://rubygems.org"
408
+ shell_out!("#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}", :env=>nil)
409
+ end
410
+
104
411
  def upgrade_package(name, version)
105
412
  install_package(name, version)
106
413
  end
107
-
414
+
108
415
  def remove_package(name, version)
416
+ if @new_resource.gem_binary.nil?
417
+ if @new_resource.options.nil?
418
+ @gem_env.uninstall(name, version)
419
+ elsif @new_resource.options.kind_of?(Hash)
420
+ @gem_env.uninstall(name, version, @new_resource.options)
421
+ else
422
+ uninstall_via_gem_command(name, version)
423
+ end
424
+ else
425
+ uninstall_via_gem_command(name, version)
426
+ end
427
+ end
428
+
429
+ def uninstall_via_gem_command(name, version)
109
430
  if version
110
- run_command_with_systems_locale(
111
- :command => "#{gem_binary_path} uninstall #{name} -q -v \"#{version}\""
112
- )
431
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -v \"#{version}\"#{opts}", :env=>nil)
113
432
  else
114
- run_command_with_systems_locale(
115
- :command => "#{gem_binary_path} uninstall #{name} -q -a"
116
- )
433
+ shell_out!("#{gem_binary_path} uninstall #{name} -q -x -I -a#{opts}", :env=>nil)
117
434
  end
118
435
  end
119
-
436
+
120
437
  def purge_package(name, version)
121
438
  remove_package(name, version)
122
439
  end
123
-
440
+
124
441
  private
125
-
442
+
126
443
  def opts
127
444
  expand_options(@new_resource.options)
128
445
  end
129
-
446
+
130
447
  end
131
448
  end
132
449
  end