summaryrefslogtreecommitdiffstats
path: root/lib/puppet/provider/package/portupgrade.rb
blob: c3aea98bf3ca39c0160927e86be61666358bc74f (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: #{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 # def query

        ####### 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