File: portupgrade.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (240 lines) | stat: -rw-r--r-- 7,892 bytes parent folder | download
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
# Whole new package, so include pack stuff
require_relative '../../../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, $!)
    end

    # split output and match it and populate temp hash
    output.split("\n").each { |data|
      # reset hash to nil for each line
      hash.clear
      match = regex.match(data)
      if match
        # 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 %{name}") % { name: @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+(.*)/
      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 %{name}") % { name: @resource[:name] }
      else
        # Any other error (dump output to log)
        raise Puppet::ExecutionFailure, _("Unexpected output from portversion: %{output}") % { output: 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}") % { output: output }
  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