chef 12.5.1 → 12.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (221) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +6 -1
  3. data/README.md +6 -4
  4. data/Rakefile +1 -4
  5. data/chef-windows.gemspec +21 -0
  6. data/chef.gemspec +58 -0
  7. data/lib/chef/api_client/registration.rb +9 -4
  8. data/lib/chef/application.rb +3 -84
  9. data/lib/chef/application/apply.rb +9 -2
  10. data/lib/chef/application/client.rb +8 -3
  11. data/lib/chef/application/solo.rb +7 -1
  12. data/lib/chef/application/windows_service.rb +21 -6
  13. data/lib/chef/application/windows_service_manager.rb +2 -3
  14. data/lib/chef/audit/runner.rb +1 -0
  15. data/lib/chef/chef_class.rb +1 -11
  16. data/lib/chef/chef_fs/chef_fs_data_store.rb +181 -2
  17. data/lib/chef/chef_fs/file_system/cookbook_subdir.rb +5 -0
  18. data/lib/chef/chef_fs/file_system/file_system_entry.rb +11 -7
  19. data/lib/chef/client.rb +28 -1
  20. data/lib/chef/cookbook/cookbook_collection.rb +14 -1
  21. data/lib/chef/cookbook/cookbook_version_loader.rb +1 -1
  22. data/lib/chef/cookbook/metadata.rb +115 -9
  23. data/lib/chef/cookbook/remote_file_vendor.rb +1 -1
  24. data/lib/chef/cookbook_version.rb +6 -2
  25. data/lib/chef/data_bag.rb +1 -1
  26. data/lib/chef/data_bag_item.rb +1 -1
  27. data/lib/chef/digester.rb +5 -1
  28. data/lib/chef/dsl/chef_provisioning.rb +57 -0
  29. data/lib/chef/dsl/cheffish.rb +64 -0
  30. data/lib/chef/dsl/declare_resource.rb +108 -0
  31. data/lib/chef/dsl/platform_introspection.rb +3 -3
  32. data/lib/chef/dsl/recipe.rb +3 -73
  33. data/lib/chef/dsl/resources.rb +27 -1
  34. data/lib/chef/event_dispatch/base.rb +3 -0
  35. data/lib/chef/event_dispatch/dispatcher.rb +5 -0
  36. data/lib/chef/event_dispatch/events_output_stream.rb +8 -0
  37. data/lib/chef/exceptions.rb +21 -1
  38. data/lib/chef/file_access_control/unix.rb +12 -12
  39. data/lib/chef/file_content_management/deploy/cp.rb +2 -2
  40. data/lib/chef/file_content_management/deploy/mv_unix.rb +4 -4
  41. data/lib/chef/file_content_management/deploy/mv_windows.rb +1 -1
  42. data/lib/chef/formatters/base.rb +7 -0
  43. data/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +2 -2
  44. data/lib/chef/formatters/indentable_output_stream.rb +5 -0
  45. data/lib/chef/http.rb +19 -3
  46. data/lib/chef/http/decompressor.rb +2 -2
  47. data/lib/chef/json_compat.rb +1 -0
  48. data/lib/chef/knife.rb +16 -2
  49. data/lib/chef/knife/bootstrap.rb +55 -10
  50. data/lib/chef/knife/cookbook_site_install.rb +5 -1
  51. data/lib/chef/knife/core/bootstrap_context.rb +2 -1
  52. data/lib/chef/knife/core/node_presenter.rb +1 -1
  53. data/lib/chef/knife/ssh.rb +30 -16
  54. data/lib/chef/knife/ssl_check.rb +4 -2
  55. data/lib/chef/knife/ssl_fetch.rb +3 -2
  56. data/lib/chef/knife/status.rb +14 -1
  57. data/lib/chef/log.rb +14 -0
  58. data/lib/chef/mixin/get_source_from_package.rb +7 -2
  59. data/lib/chef/mixin/properties.rb +302 -0
  60. data/lib/chef/mixin/proxified_socket.rb +38 -0
  61. data/lib/chef/mixin/subclass_directive.rb +37 -0
  62. data/lib/chef/node.rb +13 -5
  63. data/lib/chef/platform/query_helpers.rb +14 -3
  64. data/lib/chef/platform/service_helpers.rb +20 -38
  65. data/lib/chef/policy_builder/expand_node_object.rb +3 -0
  66. data/lib/chef/policy_builder/policyfile.rb +1 -0
  67. data/lib/chef/property.rb +51 -12
  68. data/lib/chef/provider.rb +40 -35
  69. data/lib/chef/provider/deploy.rb +1 -1
  70. data/lib/chef/provider/dsc_resource.rb +54 -20
  71. data/lib/chef/provider/execute.rb +25 -4
  72. data/lib/chef/provider/group.rb +1 -1
  73. data/lib/chef/provider/lwrp_base.rb +1 -0
  74. data/lib/chef/provider/package.rb +76 -30
  75. data/lib/chef/provider/package/dpkg.rb +152 -69
  76. data/lib/chef/provider/package/openbsd.rb +6 -8
  77. data/lib/chef/provider/package/solaris.rb +2 -0
  78. data/lib/chef/provider/package/windows.rb +95 -14
  79. data/lib/chef/provider/package/windows/exe.rb +129 -0
  80. data/lib/chef/provider/package/windows/msi.rb +37 -13
  81. data/lib/chef/provider/package/windows/registry_uninstall_entry.rb +89 -0
  82. data/lib/chef/provider/package/yum.rb +13 -3
  83. data/lib/chef/provider/powershell_script.rb +3 -0
  84. data/lib/chef/provider/remote_file/cache_control_data.rb +37 -4
  85. data/lib/chef/provider/remote_file/http.rb +1 -1
  86. data/lib/chef/provider/script.rb +1 -0
  87. data/lib/chef/provider/service.rb +13 -10
  88. data/lib/chef/provider/service/solaris.rb +43 -17
  89. data/lib/chef/provider/service/upstart.rb +3 -3
  90. data/lib/chef/provider/user.rb +1 -1
  91. data/lib/chef/provider/user/dscl.rb +111 -100
  92. data/lib/chef/provider/user/windows.rb +5 -3
  93. data/lib/chef/recipe.rb +3 -5
  94. data/lib/chef/resource.rb +77 -320
  95. data/lib/chef/resource/action_class.rb +4 -0
  96. data/lib/chef/resource/dpkg_package.rb +4 -3
  97. data/lib/chef/resource/dsc_resource.rb +40 -2
  98. data/lib/chef/resource/execute.rb +9 -1
  99. data/lib/chef/resource/ksh.rb +32 -0
  100. data/lib/chef/resource/lwrp_base.rb +6 -10
  101. data/lib/chef/resource/package.rb +8 -9
  102. data/lib/chef/resource/registry_key.rb +1 -1
  103. data/lib/chef/resource/resource_notification.rb +14 -1
  104. data/lib/chef/resource/script.rb +1 -1
  105. data/lib/chef/resource/windows_package.rb +1 -1
  106. data/lib/chef/resource_builder.rb +14 -7
  107. data/lib/chef/resource_reporter.rb +6 -0
  108. data/lib/chef/resources.rb +1 -7
  109. data/lib/chef/rest.rb +1 -1
  110. data/lib/chef/run_context.rb +45 -2
  111. data/lib/chef/run_list/run_list_expansion.rb +47 -0
  112. data/lib/chef/runner.rb +25 -0
  113. data/lib/chef/search/query.rb +16 -2
  114. data/lib/chef/util/diff.rb +2 -2
  115. data/lib/chef/util/powershell/ps_credential.rb +2 -3
  116. data/lib/chef/version.rb +1 -1
  117. data/lib/chef/win32/api/file.rb +51 -1
  118. data/lib/chef/win32/file.rb +5 -0
  119. data/lib/chef/win32/file/version_info.rb +93 -0
  120. data/lib/chef/win32/mutex.rb +1 -1
  121. data/spec/data/apt/chef-integration-test2-1.0/debian/changelog +5 -0
  122. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.debhelper.log +45 -0
  123. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2.substvars +1 -0
  124. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/conffiles +1 -0
  125. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/control +10 -0
  126. data/spec/data/apt/chef-integration-test2-1.0/debian/chef-integration-test2/DEBIAN/md5sums +1 -0
  127. data/spec/data/apt/chef-integration-test2-1.0/debian/compat +1 -0
  128. data/spec/data/apt/chef-integration-test2-1.0/debian/conffiles +1 -0
  129. data/spec/data/apt/chef-integration-test2-1.0/debian/control +13 -0
  130. data/spec/data/apt/chef-integration-test2-1.0/debian/copyright +34 -0
  131. data/spec/data/apt/chef-integration-test2-1.0/debian/files +1 -0
  132. data/spec/data/apt/chef-integration-test2-1.0/debian/rules +13 -0
  133. data/spec/data/apt/chef-integration-test2-1.0/debian/source/format +1 -0
  134. data/spec/data/apt/chef-integration-test2_1.0-1.debian.tar.gz +0 -0
  135. data/spec/data/apt/chef-integration-test2_1.0-1.dsc +18 -0
  136. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.build +91 -0
  137. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.changes +31 -0
  138. data/spec/data/apt/chef-integration-test2_1.0-1_amd64.deb +0 -0
  139. data/spec/data/apt/chef-integration-test2_1.0.orig.tar.gz +0 -0
  140. data/spec/functional/application_spec.rb +1 -1
  141. data/spec/functional/audit/runner_spec.rb +4 -0
  142. data/spec/functional/knife/ssh_spec.rb +5 -5
  143. data/spec/functional/notifications_spec.rb +74 -4
  144. data/spec/functional/resource/aix_service_spec.rb +2 -2
  145. data/spec/functional/resource/dpkg_package_spec.rb +339 -0
  146. data/spec/functional/resource/ifconfig_spec.rb +3 -1
  147. data/spec/functional/resource/mount_spec.rb +5 -2
  148. data/spec/functional/resource/package_spec.rb +1 -1
  149. data/spec/functional/resource/user/windows_spec.rb +8 -0
  150. data/spec/functional/resource/windows_package_spec.rb +177 -0
  151. data/spec/functional/win32/version_info_spec.rb +50 -0
  152. data/spec/integration/client/client_spec.rb +80 -0
  153. data/spec/integration/knife/download_spec.rb +9 -0
  154. data/spec/integration/knife/upload_spec.rb +28 -1
  155. data/spec/integration/recipes/lwrp_inline_resources_spec.rb +93 -23
  156. data/spec/integration/recipes/resource_action_spec.rb +211 -116
  157. data/spec/integration/recipes/resource_converge_if_changed_spec.rb +72 -0
  158. data/spec/integration/solo/solo_spec.rb +34 -0
  159. data/spec/spec_helper.rb +11 -1
  160. data/spec/support/platform_helpers.rb +8 -0
  161. data/spec/support/shared/integration/integration_helper.rb +6 -0
  162. data/spec/support/shared/unit/execute_resource.rb +5 -0
  163. data/spec/support/shared/unit/platform_introspector.rb +7 -0
  164. data/spec/tiny_server.rb +6 -2
  165. data/spec/unit/api_client/registration_spec.rb +5 -4
  166. data/spec/unit/application_spec.rb +1 -181
  167. data/spec/unit/chef_fs/file_system/cookbook_subdir_spec.rb +34 -0
  168. data/spec/unit/cookbook/metadata_spec.rb +122 -2
  169. data/spec/unit/http_spec.rb +102 -0
  170. data/spec/unit/knife/bootstrap_spec.rb +55 -13
  171. data/spec/unit/knife/core/bootstrap_context_spec.rb +10 -3
  172. data/spec/unit/knife/ssl_check_spec.rb +7 -3
  173. data/spec/unit/knife/ssl_fetch_spec.rb +2 -2
  174. data/spec/unit/knife/status_spec.rb +13 -13
  175. data/spec/unit/knife_spec.rb +26 -2
  176. data/spec/unit/lwrp_spec.rb +1 -1
  177. data/spec/unit/mixin/properties_spec.rb +97 -0
  178. data/spec/unit/mixin/proxified_socket_spec.rb +94 -0
  179. data/spec/unit/mixin/subclass_directive_spec.rb +45 -0
  180. data/spec/unit/node_spec.rb +9 -1
  181. data/spec/unit/policy_builder/policyfile_spec.rb +2 -0
  182. data/spec/unit/property/validation_spec.rb +14 -12
  183. data/spec/unit/property_spec.rb +56 -0
  184. data/spec/unit/provider/deploy_spec.rb +1 -1
  185. data/spec/unit/provider/dsc_resource_spec.rb +63 -24
  186. data/spec/unit/provider/execute_spec.rb +95 -28
  187. data/spec/unit/provider/package/dpkg_spec.rb +185 -96
  188. data/spec/unit/provider/package/windows/exe_spec.rb +251 -0
  189. data/spec/unit/provider/package/windows/msi_spec.rb +94 -10
  190. data/spec/unit/provider/package/windows_spec.rb +227 -26
  191. data/spec/unit/provider/package/yum_spec.rb +6 -0
  192. data/spec/unit/provider/package_spec.rb +495 -366
  193. data/spec/unit/provider/remote_file/cache_control_data_spec.rb +62 -36
  194. data/spec/unit/provider/script_spec.rb +2 -2
  195. data/spec/unit/provider/service/solaris_smf_service_spec.rb +110 -39
  196. data/spec/unit/provider/service/upstart_service_spec.rb +19 -0
  197. data/spec/unit/provider/user/dscl_spec.rb +14 -0
  198. data/spec/unit/provider/user/windows_spec.rb +2 -2
  199. data/spec/unit/provider/user_spec.rb +9 -0
  200. data/spec/unit/provider_resolver_spec.rb +6 -30
  201. data/spec/unit/recipe_spec.rb +46 -20
  202. data/spec/unit/resource/chef_gem_spec.rb +1 -1
  203. data/spec/unit/resource/dsc_resource_spec.rb +14 -3
  204. data/spec/unit/resource/ksh_spec.rb +40 -0
  205. data/spec/unit/resource/registry_key_spec.rb +2 -2
  206. data/spec/unit/resource/resource_notification_spec.rb +44 -45
  207. data/spec/unit/resource_reporter_spec.rb +7 -0
  208. data/spec/unit/resource_spec.rb +268 -253
  209. data/spec/unit/rest_spec.rb +2 -2
  210. data/spec/unit/run_list/run_list_expansion_spec.rb +18 -3
  211. data/spec/unit/search/query_spec.rb +19 -1
  212. data/spec/unit/util/powershell/ps_credential_spec.rb +8 -1
  213. data/spec/unit/windows_service_spec.rb +83 -38
  214. data/tasks/external_tests.rb +19 -9
  215. data/tasks/rspec.rb +1 -1
  216. metadata +64 -15
  217. data/spec/support/pedant/Gemfile +0 -3
  218. data/spec/support/pedant/pedant_config.rb +0 -129
  219. data/spec/support/pedant/run_pedant.rb +0 -63
  220. data/spec/support/pedant/stickywicket.pem +0 -27
  221. data/spec/unit/provider/package_spec.rbe +0 -0
@@ -17,124 +17,207 @@
17
17
  #
18
18
 
19
19
  require 'chef/provider/package'
20
- require 'chef/mixin/command'
21
20
  require 'chef/resource/package'
22
- require 'chef/mixin/get_source_from_package'
23
21
 
24
22
  class Chef
25
23
  class Provider
26
24
  class Package
27
25
  class Dpkg < Chef::Provider::Package
26
+ DPKG_REMOVED = /^Status: deinstall ok config-files/
28
27
  DPKG_INSTALLED = /^Status: install ok installed/
29
28
  DPKG_VERSION = /^Version: (.+)$/
30
29
 
31
30
  provides :dpkg_package, os: "linux"
32
31
 
33
- include Chef::Mixin::GetSourceFromPackage
32
+ use_multipackage_api
33
+ use_package_name_for_source
34
34
 
35
35
  def define_resource_requirements
36
36
  super
37
- requirements.assert(:install) do |a|
38
- a.assertion{ not @new_resource.source.nil? }
39
- a.failure_message Chef::Exceptions::Package, "Source for package #{@new_resource.name} required for action install"
37
+
38
+ requirements.assert(:install, :upgrade) do |a|
39
+ a.assertion { !resolved_source_array.compact.empty? }
40
+ a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade"
40
41
  end
41
42
 
42
- # TODO this was originally written for any action in which .source is provided
43
- # but would it make more sense to only look at source if the action is :install?
44
- requirements.assert(:all_actions) do |a|
45
- a.assertion { @source_exists }
46
- a.failure_message Chef::Exceptions::Package, "Package #{@new_resource.name} not found: #{@new_resource.source}"
47
- a.whyrun "Assuming it would have been previously downloaded."
43
+ requirements.assert(:install, :upgrade) do |a|
44
+ a.assertion { source_files_exist? }
45
+ a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}"
46
+ a.whyrun "Assuming they would have been previously created."
48
47
  end
49
48
  end
50
49
 
51
50
  def load_current_resource
52
- @source_exists = true
53
- @current_resource = Chef::Resource::Package.new(@new_resource.name)
54
- @current_resource.package_name(@new_resource.package_name)
55
-
56
- if @new_resource.source
57
- @source_exists = ::File.exists?(@new_resource.source)
58
- if @source_exists
59
- # Get information from the package if supplied
60
- Chef::Log.debug("#{@new_resource} checking dpkg status")
61
- status = shell_out_with_timeout("dpkg-deb -W #{@new_resource.source}")
62
- pkginfo = status.stdout.split("\t")
63
- unless pkginfo.empty?
64
- @current_resource.package_name(pkginfo[0])
65
- @candidate_version = pkginfo[1].strip
66
- end
67
- else
68
- # Source provided but not valid means we can't safely do further processing
69
- return
70
- end
51
+ @current_resource = Chef::Resource::Package.new(new_resource.name)
52
+ current_resource.package_name(new_resource.package_name)
53
+
54
+ if source_files_exist?
55
+ @candidate_version = get_candidate_version
56
+ current_resource.package_name(get_package_name)
57
+ # if the source file exists then our package_name is right
58
+ current_resource.version(get_current_version_from(current_package_name_array))
59
+ elsif !installing?
60
+ # we can't do this if we're installing with no source, because our package_name
61
+ # is probably not right.
62
+ #
63
+ # if we're removing or purging we don't use source, and our package_name must
64
+ # be right so we can do this.
65
+ #
66
+ # we don't error here on the dpkg command since we'll handle the exception or
67
+ # the why-run message in define_resource_requirements.
68
+ current_resource.version(get_current_version_from(current_package_name_array))
71
69
  end
72
70
 
73
- # Check to see if it is installed
74
- package_installed = nil
75
- Chef::Log.debug("#{@new_resource} checking install state")
76
- status = shell_out_with_timeout("dpkg -s #{@current_resource.package_name}")
71
+ current_resource
72
+ end
73
+
74
+ def install_package(name, version)
75
+ sources = name.map { |n| name_sources[n] }
76
+ Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}")
77
+ run_noninteractive("dpkg -i", new_resource.options, *sources)
78
+ end
79
+
80
+ def remove_package(name, version)
81
+ Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}")
82
+ run_noninteractive("dpkg -r", new_resource.options, *name)
83
+ end
84
+
85
+ def purge_package(name, version)
86
+ Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}")
87
+ run_noninteractive("dpkg -P", new_resource.options, *name)
88
+ end
89
+
90
+ def upgrade_package(name, version)
91
+ install_package(name, version)
92
+ end
93
+
94
+ def preseed_package(preseed_file)
95
+ Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
96
+ run_noninteractive("debconf-set-selections", *preseed_file)
97
+ end
98
+
99
+ def reconfig_package(name, version)
100
+ Chef::Log.info("#{new_resource} reconfiguring")
101
+ run_noninteractive("dpkg-reconfigure", *name)
102
+ end
103
+
104
+ # Override the superclass check. Multiple sources are required here.
105
+ def check_resource_semantics!
106
+ end
107
+
108
+ private
109
+
110
+ def read_current_version_of_package(package_name)
111
+ Chef::Log.debug("#{new_resource} checking install state of #{package_name}")
112
+ status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1])
113
+ package_installed = false
77
114
  status.stdout.each_line do |line|
78
115
  case line
116
+ when DPKG_REMOVED
117
+ # if we are 'purging' then we consider 'removed' to be 'installed'
118
+ package_installed = true if action == :purge
79
119
  when DPKG_INSTALLED
80
120
  package_installed = true
81
121
  when DPKG_VERSION
82
122
  if package_installed
83
- Chef::Log.debug("#{@new_resource} current version is #{$1}")
84
- @current_resource.version($1)
123
+ Chef::Log.debug("#{new_resource} current version is #{$1}")
124
+ return $1
85
125
  end
86
126
  end
87
127
  end
128
+ return nil
129
+ end
88
130
 
89
- unless status.exitstatus == 0 || status.exitstatus == 1
90
- raise Chef::Exceptions::Package, "dpkg failed - #{status.inspect}!"
131
+ def get_current_version_from(array)
132
+ array.map do |name|
133
+ read_current_version_of_package(name)
91
134
  end
135
+ end
92
136
 
93
- @current_resource
137
+ # Runs command via shell_out_with_timeout with magic environment to disable
138
+ # interactive prompts.
139
+ def run_noninteractive(*command)
140
+ shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
94
141
  end
95
142
 
96
- def install_package(name, version)
97
- Chef::Log.info("#{@new_resource} installing #{@new_resource.source}")
98
- run_noninteractive(
99
- "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}"
100
- )
143
+ # Returns true if all sources exist. Returns false if any do not, or if no
144
+ # sources were specified.
145
+ #
146
+ # @return [Boolean] True if all sources exist
147
+ def source_files_exist?
148
+ resolved_source_array.all? {|s| s && ::File.exist?(s) }
101
149
  end
102
150
 
103
- def remove_package(name, version)
104
- Chef::Log.info("#{@new_resource} removing #{@new_resource.package_name}")
105
- run_noninteractive(
106
- "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
107
- )
151
+ # Helper to return all the nanes of the missing sources for error messages.
152
+ #
153
+ # @return [Array<String>] Array of missing sources
154
+ def missing_sources
155
+ resolved_source_array.select {|s| s.nil? || !::File.exist?(s) }
108
156
  end
109
157
 
110
- def purge_package(name, version)
111
- Chef::Log.info("#{@new_resource} purging #{@new_resource.package_name}")
112
- run_noninteractive(
113
- "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}"
114
- )
158
+ def current_package_name_array
159
+ [ current_resource.package_name ].flatten
115
160
  end
116
161
 
117
- def upgrade_package(name, version)
118
- install_package(name, version)
162
+ # Helper to construct Hash of names-to-sources.
163
+ #
164
+ # @return [Hash] Mapping of package names to sources
165
+ def name_sources
166
+ @name_sources =
167
+ begin
168
+ Hash[*package_name_array.zip(resolved_source_array).flatten]
169
+ end
119
170
  end
120
171
 
121
- def preseed_package(preseed_file)
122
- Chef::Log.info("#{@new_resource} pre-seeding package installation instructions")
123
- run_noninteractive("debconf-set-selections #{preseed_file}")
172
+ # Helper to construct Hash of names-to-package-information.
173
+ #
174
+ # @return [Hash] Mapping of package names to package information
175
+ def name_pkginfo
176
+ @name_pkginfo ||=
177
+ begin
178
+ pkginfos = resolved_source_array.map do |src|
179
+ Chef::Log.debug("#{new_resource} checking #{src} dpkg status")
180
+ status = shell_out_with_timeout!("dpkg-deb -W #{src}")
181
+ status.stdout
182
+ end
183
+ Hash[*package_name_array.zip(pkginfos).flatten]
184
+ end
124
185
  end
125
186
 
126
- def reconfig_package(name, version)
127
- Chef::Log.info("#{@new_resource} reconfiguring")
128
- run_noninteractive("dpkg-reconfigure #{name}")
187
+ def name_candidate_version
188
+ @name_candidate_version ||=
189
+ begin
190
+ Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[1].strip : nil] }]
191
+ end
129
192
  end
130
193
 
131
- # Runs command via shell_out_with_timeout with magic environment to disable
132
- # interactive prompts. Command is run with default localization rather
133
- # than forcing locale to "C", so command output may not be stable.
194
+ def name_package_name
195
+ @name_package_name ||=
196
+ begin
197
+ Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[0] : nil] }]
198
+ end
199
+ end
200
+
201
+ # Return candidate version array from pkg-deb -W against the source file(s).
202
+ #
203
+ # @return [Array] Array of candidate versions read from the source files
204
+ def get_candidate_version
205
+ package_name_array.map { |name| name_candidate_version[name] }
206
+ end
207
+
208
+ # Return package names from the candidate source file(s).
209
+ #
210
+ # @return [Array] Array of actual package names read from the source files
211
+ def get_package_name
212
+ package_name_array.map { |name| name_package_name[name] }
213
+ end
214
+
215
+ # Since upgrade just calls install, this is a helper to determine
216
+ # if our action means that we'll be calling install_package.
134
217
  #
135
- # FIXME: This should be "LC_ALL" => "en_US.UTF-8" in order to stabilize the output and get UTF-8
136
- def run_noninteractive(command)
137
- shell_out_with_timeout!(command, :env => { "DEBIAN_FRONTEND" => "noninteractive", "LC_ALL" => nil })
218
+ # @return [Boolean] true if we're doing :install or :upgrade
219
+ def installing?
220
+ [:install, :upgrade].include?(action)
138
221
  end
139
222
 
140
223
  end
@@ -72,18 +72,16 @@ class Chef
72
72
  if parts = name.match(/^(.+?)--(.+)/) # use double-dash for stems with flavors, see man page for pkg_add
73
73
  name = parts[1]
74
74
  end
75
- shell_out_with_timeout!("pkg_add -r #{name}#{version_string}", :env => {"PKG_PATH" => pkg_path}).status
75
+ shell_out_with_timeout!("pkg_add -r #{name}#{version_string(version)}", :env => {"PKG_PATH" => pkg_path}).status
76
76
  Chef::Log.debug("#{new_resource.package_name} installed")
77
77
  end
78
78
  end
79
79
 
80
80
  def remove_package(name, version)
81
- version_string = ''
82
- version_string += "-#{version}" if version
83
81
  if parts = name.match(/^(.+?)--(.+)/)
84
82
  name = parts[1]
85
83
  end
86
- shell_out_with_timeout!("pkg_delete #{name}#{version_string}", :env => nil).status
84
+ shell_out_with_timeout!("pkg_delete #{name}#{version_string(version)}", :env => nil).status
87
85
  end
88
86
 
89
87
  private
@@ -103,7 +101,7 @@ class Chef
103
101
  def candidate_version
104
102
  @candidate_version ||= begin
105
103
  results = []
106
- shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
104
+ shell_out_with_timeout!("pkg_info -I \"#{new_resource.package_name}#{version_string(new_resource.version)}\"", :env => nil, :returns => [0,1]).stdout.each_line do |line|
107
105
  if parts = new_resource.package_name.match(/^(.+?)--(.+)/)
108
106
  results << line[/^#{Regexp.escape(parts[1])}-(.+?)\s/, 1]
109
107
  else
@@ -111,7 +109,7 @@ class Chef
111
109
  end
112
110
  end
113
111
  results = results.reject(&:nil?)
114
- Chef::Log.debug("candidate versions of '#{new_resource.package_name}' are '#{results}'")
112
+ Chef::Log.debug("Candidate versions of '#{new_resource.package_name}' are '#{results}'")
115
113
  case results.length
116
114
  when 0
117
115
  []
@@ -123,9 +121,9 @@ class Chef
123
121
  end
124
122
  end
125
123
 
126
- def version_string
124
+ def version_string(version)
127
125
  ver = ''
128
- ver += "-#{new_resource.version}" if new_resource.version
126
+ ver += "-#{version}" if version
129
127
  end
130
128
 
131
129
  def pkg_path
@@ -125,6 +125,8 @@ class Chef
125
125
  end
126
126
  end
127
127
 
128
+ alias_method :upgrade_package, :install_package
129
+
128
130
  def remove_package(name, version)
129
131
  if @new_resource.options.nil?
130
132
  shell_out_with_timeout!( "pkgrm -n #{name}" )
@@ -32,11 +32,7 @@ class Chef
32
32
  provides :package, os: "windows"
33
33
  provides :windows_package, os: "windows"
34
34
 
35
- # Depending on the installer, we may need to examine installer_type or
36
- # source attributes, or search for text strings in the installer file
37
- # binary to determine the installer type for the user. Since the file
38
- # must be on disk to do so, we have to make this choice in the provider.
39
- require 'chef/provider/package/windows/msi.rb'
35
+ require 'chef/provider/package/windows/registry_uninstall_entry.rb'
40
36
 
41
37
  # load_current_resource is run in Chef::Provider#run_action when not in whyrun_mode?
42
38
  def load_current_resource
@@ -56,24 +52,64 @@ class Chef
56
52
  @package_provider ||= begin
57
53
  case installer_type
58
54
  when :msi
59
- Chef::Provider::Package::Windows::MSI.new(resource_for_provider)
55
+ Chef::Log.debug("#{@new_resource} is MSI")
56
+ require 'chef/provider/package/windows/msi'
57
+ Chef::Provider::Package::Windows::MSI.new(resource_for_provider, uninstall_registry_entries)
60
58
  else
61
- raise "Unable to find a Chef::Provider::Package::Windows provider for installer_type '#{installer_type}'"
59
+ Chef::Log.debug("#{@new_resource} is EXE with type '#{installer_type}'")
60
+ require 'chef/provider/package/windows/exe'
61
+ Chef::Provider::Package::Windows::Exe.new(resource_for_provider, installer_type, uninstall_registry_entries)
62
62
  end
63
63
  end
64
64
  end
65
65
 
66
66
  def installer_type
67
+ # Depending on the installer, we may need to examine installer_type or
68
+ # source attributes, or search for text strings in the installer file
69
+ # binary to determine the installer type for the user. Since the file
70
+ # must be on disk to do so, we have to make this choice in the provider.
67
71
  @installer_type ||= begin
68
72
  if @new_resource.installer_type
69
73
  @new_resource.installer_type
70
- else
71
- file_extension = ::File.basename(@new_resource.source).split(".").last.downcase
74
+ elsif source_location.nil?
75
+ inferred_registry_type
76
+ else
77
+ basename = ::File.basename(source_location)
78
+ file_extension = basename.split(".").last.downcase
72
79
 
73
80
  if file_extension == "msi"
74
81
  :msi
75
82
  else
76
- raise ArgumentError, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
83
+ # search the binary file for installer type
84
+ ::Kernel.open(::File.expand_path(source_location), 'rb') do |io|
85
+ filesize = io.size
86
+ bufsize = 4096 # read 4K buffers
87
+ overlap = 16 # bytes to overlap between buffer reads
88
+
89
+ until io.eof
90
+ contents = io.read(bufsize)
91
+
92
+ case contents
93
+ when /inno/i # Inno Setup
94
+ return :inno
95
+ when /wise/i # Wise InstallMaster
96
+ return :wise
97
+ when /nullsoft/i # Nullsoft Scriptable Install System
98
+ return :nsis
99
+ end
100
+
101
+ if (io.tell() < filesize)
102
+ io.seek(io.tell() - overlap)
103
+ end
104
+ end
105
+ end
106
+
107
+ # if file is named 'setup.exe' assume installshield
108
+ if basename == 'setup.exe'
109
+ :installshield
110
+ else
111
+ fail Chef::Exceptions::CannotDetermineWindowsInstallerType, "Installer type for Windows Package '#{@new_resource.name}' not specified and cannot be determined from file extension '#{file_extension}'"
112
+ end
77
113
  end
78
114
  end
79
115
  end
@@ -93,11 +129,11 @@ class Chef
93
129
  # Chef::Provider::Package action_install + action_remove call install_package + remove_package
94
130
  # Pass those calls to the correct sub-provider
95
131
  def install_package(name, version)
96
- package_provider.install_package(name, version)
132
+ package_provider.install_package
97
133
  end
98
134
 
99
135
  def remove_package(name, version)
100
- package_provider.remove_package(name, version)
136
+ package_provider.remove_package
101
137
  end
102
138
 
103
139
  # @return [Array] new_version(s) as an array
@@ -106,15 +142,59 @@ class Chef
106
142
  [new_resource.version]
107
143
  end
108
144
 
145
+ # @return [String] candidate_version
146
+ def candidate_version
147
+ @candidate_version ||= (@new_resource.version || 'latest')
148
+ end
149
+
150
+ # @return [Array] current_version(s) as an array
151
+ # this package provider does not support package arrays
152
+ # However, There may be multiple versions for a single
153
+ # package so the first element may be a nested array
154
+ def current_version_array
155
+ [ current_resource.version ]
156
+ end
157
+
158
+ # @param current_version<String> one or more versions currently installed
159
+ # @param new_version<String> version of the new resource
160
+ #
161
+ # @return [Boolean] true if new_version is equal to or included in current_version
162
+ def target_version_already_installed?(current_version, new_version)
163
+ Chef::Log.debug("Checking if #{@new_resource} version '#{new_version}' is already installed. #{current_version} is currently installed")
164
+ if current_version.is_a?(Array)
165
+ current_version.include?(new_version)
166
+ else
167
+ new_version == current_version
168
+ end
169
+ end
170
+
171
+ def have_any_matching_version?
172
+ target_version_already_installed?(current_resource.version, new_resource.version)
173
+ end
174
+
109
175
  private
110
176
 
177
+ def uninstall_registry_entries
178
+ @uninstall_registry_entries ||= Chef::Provider::Package::Windows::RegistryUninstallEntry.find_entries(new_resource.name)
179
+ end
180
+
181
+ def inferred_registry_type
182
+ uninstall_registry_entries.each do |entry|
183
+ return :inno if entry.key.end_with?("_is1")
184
+ return :msi if entry.uninstall_string.downcase.start_with?("msiexec.exe ")
185
+ return :nsis if entry.uninstall_string.downcase.end_with?("uninst.exe\"")
186
+ end
187
+ nil
188
+ end
189
+
111
190
  def downloadable_file_missing?
112
191
  uri_scheme?(new_resource.source) && !::File.exists?(source_location)
113
192
  end
114
193
 
115
194
  def resource_for_provider
116
195
  @resource_for_provider = Chef::Resource::WindowsPackage.new(new_resource.name).tap do |r|
117
- r.source(Chef::Util::PathHelper.validate_path(source_location))
196
+ r.source(Chef::Util::PathHelper.validate_path(source_location)) unless source_location.nil?
197
+ r.version(new_resource.version)
118
198
  r.timeout(new_resource.timeout)
119
199
  r.returns(new_resource.returns)
120
200
  r.options(new_resource.options)
@@ -151,7 +231,8 @@ class Chef
151
231
  if uri_scheme?(new_resource.source)
152
232
  source_resource.path
153
233
  else
154
- Chef::Util::PathHelper.cleanpath(new_resource.source)
234
+ new_source = Chef::Util::PathHelper.cleanpath(new_resource.source)
235
+ ::File.exist?(new_source) ? new_source : nil
155
236
  end
156
237
  end
157
238