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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
# Whole new package, so include pack stuff
require 'puppet/provider/package'
Puppet::Type.type(:package).provide :portupgrade, :parent => Puppet::Provider::Package do
include Puppet::Util::Execution
desc "Support for FreeBSD's ports using the portupgrade ports management software.
Use the port's full origin as the resource name. eg (ports-mgmt/portupgrade)
for the portupgrade port."
## has_features is usually autodetected based on defs below.
# has_features :installable, :uninstallable, :upgradeable
commands :portupgrade => "/usr/local/sbin/portupgrade",
:portinstall => "/usr/local/sbin/portinstall",
:portversion => "/usr/local/sbin/portversion",
:portuninstall => "/usr/local/sbin/pkg_deinstall",
:portinfo => "/usr/sbin/pkg_info"
## Activate this only once approved by someone important.
# defaultfor :operatingsystem => :freebsd
# Remove unwanted environment variables.
%w{INTERACTIVE UNAME}.each do |var|
if ENV.include?(var)
ENV.delete(var)
end
end
######## instances sub command (builds the installed packages list)
def self.instances
Puppet.debug "portupgrade.rb Building packages list from installed ports"
# regex to match output from pkg_info
regex = %r{^(\S+)-([^-\s]+):(\S+)$}
# Corresponding field names
fields = [:portname, :ensure, :portorigin]
# define Temporary hash used, packages array of hashes
hash = Hash.new
packages = []
# exec command
cmdline = ["-aoQ"]
begin
output = portinfo(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
return nil
end
# split output and match it and populate temp hash
output.split("\n").each { |data|
# reset hash to nil for each line
hash.clear
if match = regex.match(data)
# Output matched regex
fields.zip(match.captures) { |field, value|
hash[field] = value
}
# populate the actual :name field from the :portorigin
# Set :provider to this object name
hash[:name] = hash[:portorigin]
hash[:provider] = self.name
# Add to the full packages listing
packages << new(hash)
else
# unrecognised output from pkg_info
Puppet.debug "portupgrade.Instances() - unable to match output: #{data}"
end
}
# return the packages array of hashes
return packages
end
######## Installation sub command
def install
Puppet.debug "portupgrade.install() - Installation call on #{@resource[:name]}"
# -M: yes, we're a batch, so don't ask any questions
cmdline = ["-M BATCH=yes", @resource[:name]]
# FIXME: it's possible that portinstall prompts for data so locks up.
begin
output = portinstall(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
if output =~ /\*\* No such /
raise Puppet::ExecutionFailure, "Could not find package #{@resource[:name]}"
end
# No return code required, so do nil to be clean
return nil
end
######## Latest subcommand (returns the latest version available, or current version if installed is latest)
def latest
Puppet.debug "portupgrade.latest() - Latest check called on #{@resource[:name]}"
# search for latest version available, or return current version.
# cmdline = "portversion -v <portorigin>", returns "<portname> <code> <stuff>"
# or "** No matching package found: <portname>"
cmdline = ["-v", @resource[:name]]
begin
output = portversion(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
# Check: output format.
if output =~ /^\S+-([^-\s]+)\s+(\S)\s+(.*)/
# $1 = installed version, $2 = comparison, $3 other data
# latest installed
installedversion = $1
comparison = $2
otherdata = $3
# Only return a new version number when it's clear that there is a new version
# all others return the current version so no unexpected 'upgrades' occur.
case comparison
when "=", ">"
Puppet.debug "portupgrade.latest() - Installed package is latest (#{installedversion})"
return installedversion
when "<"
# "portpkg-1.7_5 < needs updating (port has 1.14)"
# "portpkg-1.7_5 < needs updating (port has 1.14) (=> 'newport/pkg')
if otherdata =~ /\(port has (\S+)\)/
newversion = $1
Puppet.debug "portupgrade.latest() - Installed version needs updating to (#{newversion})"
return newversion
else
Puppet.debug "portupgrade.latest() - Unable to determine new version from (#{otherdata})"
return installedversion
end
when "?", "!", "#"
Puppet.debug "portupgrade.latest() - Comparison Error reported from portversion (#{output})"
return installedversion
else
Puppet.debug "portupgrade.latest() - Unknown code from portversion output (#{output})"
return installedversion
end
else
# error: output not parsed correctly, error out with nil.
# Seriously - this section should never be called in a perfect world.
# as verification that the port is installed has already happened in query.
if output =~ /^\*\* No matching package /
raise Puppet::ExecutionFailure, "Could not find package #{@resource[:name]}"
else
# Any other error (dump output to log)
raise Puppet::ExecutionFailure, "Unexpected output from portversion: #{output}"
end
# Just in case we still are running, return nil
return nil
end
# At this point normal operation has finished and we shouldn't have been called.
# Error out and let the admin deal with it.
raise Puppet::Error, "portversion.latest() - fatal error with portversion: #{output}"
return nil
end
###### Query subcommand - return a hash of details if exists, or nil if it doesn't.
# Used to make sure the package is installed
def query
Puppet.debug "portupgrade.query() - Called on #{@resource[:name]}"
cmdline = ["-qO", @resource[:name]]
begin
output = portinfo(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
# Check: if output isn't in the right format, return nil
if output =~ /^(\S+)-([^-\s]+)/
# Fill in the details
hash = Hash.new
hash[:portorigin] = self.name
hash[:portname] = $1
hash[:ensure] = $2
# If more details are required, then we can do another pkg_info query here
# and parse out that output and add to the hash
# return the hash to the caller
return hash
else
Puppet.debug "portupgrade.query() - package (#{@resource[:name]}) not installed"
return nil
end
end
####### Uninstall command
def uninstall
Puppet.debug "portupgrade.uninstall() - called on #{@resource[:name]}"
# Get full package name from port origin to uninstall with
cmdline = ["-qO", @resource[:name]]
begin
output = portinfo(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
if output =~ /^(\S+)/
# output matches, so uninstall it
portuninstall $1
end
end
######## Update/upgrade command
def update
Puppet.debug "portupgrade.update() - called on (#{@resource[:name]})"
cmdline = ["-qO", @resource[:name]]
begin
output = portinfo(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
if output =~ /^(\S+)/
# output matches, so upgrade the software
cmdline = ["-M BATCH=yes", $1]
begin
output = portupgrade(*cmdline)
rescue Puppet::ExecutionFailure
raise Puppet::Error.new(output)
end
end
end
## EOF
end
|