summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider/package/portupgrade.rb
blob: 531cab2197f1ccbe67be75b320d3ee3b39920afb (plain)
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: %s" % data
			end
		}

		# return the packages array of hashes
		return packages

	end

	######## Installation sub command

	def install
		Puppet.debug "portupgrade.install() - Installation call on %s" % @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 %s" % @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 %s" % @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 (%s)" % 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 (%s)" % newversion
					return newversion
				else
					Puppet.debug "portupgrade.latest() - Unable to determine new version from (%s)" % otherdata
					return installedversion
				end
			when "?", "!", "#"
				Puppet.debug "portupgrade.latest() - Comparison Error reported from portversion (%s)" % output
				return installedversion
			else
				Puppet.debug "portupgrade.latest() - Unknown code from portversion output (%s)" % 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 %s" % @resource[:name]
			else
				# Any other error (dump output to log)
				raise Puppet::ExecutionFailure, "Unexpected output from portversion: %s" % 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: %s" % 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 %s" % @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 (%s) not installed" % @resource[:name]
			return nil
		end

	end # def query

	####### Uninstall command 

	def uninstall
		Puppet.debug "portupgrade.uninstall() - called on %s" % @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 (%s)" % @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