chef 12.5.1-universal-mingw32 → 12.6.0-universal-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +70 -21
  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