1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
|
#
# Author:: Bryan McLellan (btm@loftninjas.org)
# Copyright:: Copyright 2009-2016, Bryan McLellan
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require "chef/provider/package"
require "chef/resource/package"
class Chef
class Provider
class Package
class Dpkg < Chef::Provider::Package
DPKG_REMOVED = /^Status: deinstall ok config-files/
DPKG_INSTALLED = /^Status: install ok installed/
DPKG_VERSION = /^Version: (.+)$/
provides :dpkg_package, os: "linux"
use_multipackage_api
use_package_name_for_source
def define_resource_requirements
super
requirements.assert(:install, :upgrade) do |a|
a.assertion { !resolved_source_array.compact.empty? }
a.failure_message Chef::Exceptions::Package, "#{new_resource} the source property is required for action :install or :upgrade"
end
requirements.assert(:install, :upgrade) do |a|
a.assertion { source_files_exist? }
a.failure_message Chef::Exceptions::Package, "#{new_resource} source file(s) do not exist: #{missing_sources}"
a.whyrun "Assuming they would have been previously created."
end
end
def load_current_resource
@current_resource = Chef::Resource::Package.new(new_resource.name)
current_resource.package_name(new_resource.package_name)
if source_files_exist?
@candidate_version = get_candidate_version
current_resource.package_name(get_package_name)
# if the source file exists then our package_name is right
current_resource.version(get_current_version_from(current_package_name_array))
elsif !installing?
# we can't do this if we're installing with no source, because our package_name
# is probably not right.
#
# if we're removing or purging we don't use source, and our package_name must
# be right so we can do this.
#
# we don't error here on the dpkg command since we'll handle the exception or
# the why-run message in define_resource_requirements.
current_resource.version(get_current_version_from(current_package_name_array))
end
current_resource
end
def install_package(name, version)
sources = name.map { |n| name_sources[n] }
Chef::Log.info("#{new_resource} installing package(s): #{name.join(' ')}")
run_noninteractive("dpkg -i", new_resource.options, *sources)
end
def remove_package(name, version)
Chef::Log.info("#{new_resource} removing package(s): #{name.join(' ')}")
run_noninteractive("dpkg -r", new_resource.options, *name)
end
def purge_package(name, version)
Chef::Log.info("#{new_resource} purging packages(s): #{name.join(' ')}")
run_noninteractive("dpkg -P", new_resource.options, *name)
end
def upgrade_package(name, version)
install_package(name, version)
end
def preseed_package(preseed_file)
Chef::Log.info("#{new_resource} pre-seeding package installation instructions")
run_noninteractive("debconf-set-selections", *preseed_file)
end
def reconfig_package(name, version)
Chef::Log.info("#{new_resource} reconfiguring")
run_noninteractive("dpkg-reconfigure", *name)
end
# Override the superclass check. Multiple sources are required here.
def check_resource_semantics!
end
private
def read_current_version_of_package(package_name)
Chef::Log.debug("#{new_resource} checking install state of #{package_name}")
status = shell_out_with_timeout!("dpkg -s #{package_name}", returns: [0, 1])
package_installed = false
status.stdout.each_line do |line|
case line
when DPKG_REMOVED
# if we are 'purging' then we consider 'removed' to be 'installed'
package_installed = true if action == :purge
when DPKG_INSTALLED
package_installed = true
when DPKG_VERSION
if package_installed
Chef::Log.debug("#{new_resource} current version is #{$1}")
return $1
end
end
end
return nil
end
def get_current_version_from(array)
array.map do |name|
read_current_version_of_package(name)
end
end
# Runs command via shell_out_with_timeout with magic environment to disable
# interactive prompts.
def run_noninteractive(*command)
shell_out_with_timeout!(a_to_s(*command), :env => { "DEBIAN_FRONTEND" => "noninteractive" })
end
# Returns true if all sources exist. Returns false if any do not, or if no
# sources were specified.
#
# @return [Boolean] True if all sources exist
def source_files_exist?
resolved_source_array.all? {|s| s && ::File.exist?(s) }
end
# Helper to return all the nanes of the missing sources for error messages.
#
# @return [Array<String>] Array of missing sources
def missing_sources
resolved_source_array.select {|s| s.nil? || !::File.exist?(s) }
end
def current_package_name_array
[ current_resource.package_name ].flatten
end
# Helper to construct Hash of names-to-sources.
#
# @return [Hash] Mapping of package names to sources
def name_sources
@name_sources =
begin
Hash[*package_name_array.zip(resolved_source_array).flatten]
end
end
# Helper to construct Hash of names-to-package-information.
#
# @return [Hash] Mapping of package names to package information
def name_pkginfo
@name_pkginfo ||=
begin
pkginfos = resolved_source_array.map do |src|
Chef::Log.debug("#{new_resource} checking #{src} dpkg status")
status = shell_out_with_timeout!("dpkg-deb -W #{src}")
status.stdout
end
Hash[*package_name_array.zip(pkginfos).flatten]
end
end
def name_candidate_version
@name_candidate_version ||=
begin
Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[1].strip : nil] }]
end
end
def name_package_name
@name_package_name ||=
begin
Hash[name_pkginfo.map {|k, v| [k, v ? v.split("\t")[0] : nil] }]
end
end
# Return candidate version array from pkg-deb -W against the source file(s).
#
# @return [Array] Array of candidate versions read from the source files
def get_candidate_version
package_name_array.map { |name| name_candidate_version[name] }
end
# Return package names from the candidate source file(s).
#
# @return [Array] Array of actual package names read from the source files
def get_package_name
package_name_array.map { |name| name_package_name[name] }
end
# Since upgrade just calls install, this is a helper to determine
# if our action means that we'll be calling install_package.
#
# @return [Boolean] true if we're doing :install or :upgrade
def installing?
[:install, :upgrade].include?(action)
end
end
end
end
end
|