File: package.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 (713 lines) | stat: -rw-r--r-- 25,854 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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# coding: utf-8
# Define the different packaging systems.  Each package system is implemented
# in a module, which then gets used to individually extend each package object.
# This allows packages to exist on the same machine using different packaging
# systems.

require_relative '../../puppet/parameter/package_options'
require_relative '../../puppet/parameter/boolean'

module Puppet
  Type.newtype(:package) do
    @doc = "Manage packages.  There is a basic dichotomy in package
      support right now:  Some package types (such as yum and apt) can
      retrieve their own package files, while others (such as rpm and sun)
      cannot.  For those package formats that cannot retrieve their own files,
      you can use the `source` parameter to point to the correct file.

      Puppet will automatically guess the packaging format that you are
      using based on the platform you are on, but you can override it
      using the `provider` parameter; each provider defines what it
      requires in order to function, and you must meet those requirements
      to use a given provider.

      You can declare multiple package resources with the same `name` as long
      as they have unique titles, and specify different providers and commands.

      Note that you must use the _title_ to make a reference to a package
      resource; `Package[<NAME>]` is not a synonym for `Package[<TITLE>]` like
      it is for many other resource types.

      **Autorequires:** If Puppet is managing the files specified as a
      package's `adminfile`, `responsefile`, or `source`, the package
      resource will autorequire those files."

    feature :reinstallable, "The provider can reinstall packages.",
      :methods => [:reinstall]
    feature :installable, "The provider can install packages.",
      :methods => [:install]
    feature :uninstallable, "The provider can uninstall packages.",
      :methods => [:uninstall]
    feature :upgradeable, "The provider can upgrade to the latest version of a
        package.  This feature is used by specifying `latest` as the
        desired value for the package.",
      :methods => [:update, :latest]
    feature :purgeable, "The provider can purge packages.  This generally means
        that all traces of the package are removed, including
        existing configuration files.  This feature is thus destructive
        and should be used with the utmost care.",
      :methods => [:purge]
    feature :versionable, "The provider is capable of interrogating the
        package database for installed version(s), and can select
        which out of a set of available versions of a package to
        install if asked."
    feature :version_ranges, "The provider can ensure version ranges."
    feature :holdable, "The provider is capable of placing packages on hold
        such that they are not automatically upgraded as a result of
        other package dependencies unless explicit action is taken by
        a user or another package.",
      :methods => [:hold, :unhold]
    feature :install_only, "The provider accepts options to only install packages never update (kernels, etc.)"
    feature :install_options, "The provider accepts options to be
      passed to the installer command."
    feature :uninstall_options, "The provider accepts options to be
      passed to the uninstaller command."
    feature :disableable, "The provider can disable packages. This feature is used by specifying `disabled` as the
      desired value for the package.",
      :methods => [:disable]
    feature :supports_flavors, "The provider accepts flavors, which are specific variants of packages."
    feature :package_settings, "The provider accepts package_settings to be
      ensured for the given package. The meaning and format of these settings is
      provider-specific.",
      :methods => [:package_settings_insync?, :package_settings, :package_settings=]
    feature :virtual_packages, "The provider accepts virtual package names for install and uninstall."

    feature :targetable, "The provider accepts a targeted package management command."

    ensurable do
      desc <<-EOT
        What state the package should be in. On packaging systems that can
        retrieve new packages on their own, you can choose which package to
        retrieve by specifying a version number or `latest` as the ensure
        value. On packaging systems that manage configuration files separately
        from "normal" system files, you can uninstall config files by
        specifying `purged` as the ensure value. This defaults to `installed`.

        Version numbers must match the full version to install, including
        release if the provider uses a release moniker. For
        example, to install the bash package from the rpm
        `bash-4.1.2-29.el6.x86_64.rpm`, use the string `'4.1.2-29.el6'`.

        On supported providers, version ranges can also be ensured. For example,
        inequalities: `<2.0.0`, or intersections: `>1.0.0 <2.0.0`.
      EOT

      attr_accessor :latest

      newvalue(:present, :event => :package_installed) do
        provider.install
      end

      newvalue(:absent, :event => :package_removed) do
        provider.uninstall
      end

      newvalue(:purged, :event => :package_purged, :required_features => :purgeable) do
        provider.purge
      end

      newvalue(:disabled, :required_features => :disableable) do
        provider.disable
      end

      # Alias the 'present' value.
      aliasvalue(:installed, :present)

      newvalue(:latest, :required_features => :upgradeable) do
        # Because yum always exits with a 0 exit code, there's a retrieve
        # in the "install" method.  So, check the current state now,
        # to compare against later.
        current = self.retrieve
        begin
          provider.update
        rescue => detail
          self.fail Puppet::Error, _("Could not update: %{detail}") % { detail: detail }, detail
        end

        if current == :absent
          :package_installed
        else
          :package_changed
        end
      end

      newvalue(/./, :required_features => :versionable) do
        begin
          provider.install
        rescue => detail
          self.fail Puppet::Error, _("Could not update: %{detail}") % { detail: detail }, detail
        end

        if self.retrieve == :absent
          :package_installed
        else
          :package_changed
        end
      end

      defaultto :installed

      # Override the parent method, because we've got all kinds of
      # funky definitions of 'in sync'.
      def insync?(is)
        @lateststamp ||= (Time.now.to_i - 1000)
        # Iterate across all of the should values, and see how they
        # turn out.

        @should.each { |should|
          case should
          when :present
            return true unless [:absent, :purged, :disabled].include?(is)
          when :latest
            # Short-circuit packages that are not present
            return false if is == :absent || is == :purged

            # Don't run 'latest' more than about every 5 minutes
            if @latest and ((Time.now.to_i - @lateststamp) / 60) < 5
              #self.debug "Skipping latest check"
            else
              begin
                @latest = provider.latest
                @lateststamp = Time.now.to_i
              rescue => detail
                error = Puppet::Error.new(_("Could not get latest version: %{detail}") % { detail: detail })
                error.set_backtrace(detail.backtrace)
                raise error
              end
            end

            case
              when is.is_a?(Array) && is.include?(@latest)
                return true
              when is == @latest
                return true
              when is == :present
                # This will only happen on packaging systems
                # that can't query versions.
                return true
              else
                self.debug "#{@resource.name} #{is.inspect} is installed, latest is #{@latest.inspect}"
            end


          when :absent
            return true if is == :absent || is == :purged
          when :purged
            return true if is == :purged
          # this handles version number matches and
          # supports providers that can have multiple versions installed
          when *Array(is)
            return true
          else
            # We have version numbers, and no match. If the provider has
            # additional logic, run it here.
            return provider.insync?(is) if provider.respond_to?(:insync?)
          end
        }

        false
      end

      # This retrieves the current state. LAK: I think this method is unused.
      def retrieve
        provider.properties[:ensure]
      end

      # Provide a bit more information when logging upgrades.
      def should_to_s(newvalue = @should)
        if @latest
          super(@latest)
        else
          super(newvalue)
        end
      end

      def change_to_s(currentvalue, newvalue)
        # Handle transitioning from any previous state to 'purged'
        return 'purged' if newvalue == :purged

        # Check for transitions from nil/purged/absent to 'created' (any state that is not absent and not purged)
        return 'created' if (currentvalue.nil? || currentvalue == :absent || currentvalue == :purged) && (newvalue != :absent && newvalue != :purged)

        # The base should handle the normal property transitions
        super(currentvalue, newvalue)
      end
    end

    newparam(:name) do
      desc "The package name.  This is the name that the packaging
      system uses internally, which is sometimes (especially on Solaris)
      a name that is basically useless to humans.  If a package goes by
      several names, you can use a single title and then set the name
      conditionally:

          # In the 'openssl' class
          $ssl = $operatingsystem ? {
            solaris => SMCossl,
            default => openssl
          }

          package { 'openssl':
            ensure => installed,
            name   => $ssl,
          }

          ...

          $ssh = $operatingsystem ? {
            solaris => SMCossh,
            default => openssh
          }

          package { 'openssh':
            ensure  => installed,
            name    => $ssh,
            require => Package['openssl'],
          }

      "
      isnamevar

      validate do |value|
        if !value.is_a?(String)
          raise ArgumentError, _("Name must be a String not %{klass}") % { klass: value.class }
        end
      end
    end

    # We call providify here so that we can set provider as a namevar.
    # Normally this method is called after newtype finishes constructing this
    # Type class.
    providify
    paramclass(:provider).isnamevar

    def self.parameters_to_include
      [:provider]
    end

    # Specify a targeted package management command.
    newparam(:command, :required_features => :targetable) do
      desc <<-EOT
        The targeted command to use when managing a package:

          package { 'mysql':
            provider => gem,
          }

          package { 'mysql-opt':
            name     => 'mysql',
            provider => gem,
            command  => '/opt/ruby/bin/gem',
          }

        Each provider defines a package management command; and uses the first
        instance of the command found in the PATH.

        Providers supporting the targetable feature allow you to specify the
        absolute path of the package management command; useful when multiple
        instances of the command are installed, or the command is not in the PATH.
      EOT

      isnamevar
      defaultto :default
    end

    # We have more than one namevar, so we need title_patterns.
    # However, we cheat and set the patterns to map to name only
    # and completely ignore provider (and command, for targetable providers).
    # So far, the logic that determines uniqueness appears to just
    # "Do The Right Thing™" when provider (and command) are explicitly set.
    #
    # The following resources will be seen as unique by puppet:
    #
    #     # Uniqueness Key: ['mysql', nil]
    #     package {'mysql': }
    #
    #     # Uniqueness Key: ['mysql', 'gem', nil]
    #     package {'gem-mysql':
    #       name     => 'mysql,
    #       provider => gem,
    #     }
    #
    #     # Uniqueness Key: ['mysql', 'gem', '/opt/ruby/bin/gem']
    #     package {'gem-mysql-opt':
    #       name     => 'mysql,
    #       provider => gem
    #       command  => '/opt/ruby/bin/gem',
    #     }
    #
    # This does not handle the case where providers like 'yum' and 'rpm' should
    # clash. Also, declarations that implicitly use the default provider will
    # clash with those that explicitly use the default.
    def self.title_patterns
      # This is the default title pattern for all types, except hard-wired to
      # set only name.
      [ [ /(.*)/m, [ [:name] ] ] ]
    end

    newproperty(:package_settings, :required_features=>:package_settings) do
      desc "Settings that can change the contents or configuration of a package.

        The formatting and effects of package_settings are provider-specific; any
        provider that implements them must explain how to use them in its
        documentation. (Our general expectation is that if a package is
        installed but its settings are out of sync, the provider should
        re-install that package with the desired settings.)

        An example of how package_settings could be used is FreeBSD's port build
        options --- a future version of the provider could accept a hash of options,
        and would reinstall the port if the installed version lacked the correct
        settings.

            package { 'www/apache22':
              package_settings => { 'SUEXEC' => false }
            }

        Again, check the documentation of your platform's package provider to see
        the actual usage."

      validate do |value|
        if provider.respond_to?(:package_settings_validate)
          provider.package_settings_validate(value)
        else
          super(value)
        end
      end

      munge do |value|
        if provider.respond_to?(:package_settings_munge)
          provider.package_settings_munge(value)
        else
          super(value)
        end
      end

      def insync?(is)
        provider.package_settings_insync?(should, is)
      end

      def should_to_s(newvalue)
        if provider.respond_to?(:package_settings_should_to_s)
          provider.package_settings_should_to_s(should, newvalue)
        else
          super(newvalue)
        end
      end

      def is_to_s(currentvalue)
        if provider.respond_to?(:package_settings_is_to_s)
          provider.package_settings_is_to_s(should, currentvalue)
        else
          super(currentvalue)
        end
      end

      def change_to_s(currentvalue, newvalue)
        if provider.respond_to?(:package_settings_change_to_s)
          provider.package_settings_change_to_s(currentvalue, newvalue)
        else
          super(currentvalue,newvalue)
        end
      end
    end

    newproperty(:flavor, :required_features => :supports_flavors) do
      desc "OpenBSD and DNF modules support 'flavors', which are
        further specifications for which type of package you want."
      validate do |value|
        if [:disabled, "disabled"].include?(@resource[:ensure]) && value
          raise ArgumentError, _('Cannot have both `ensure => disabled` and `flavor`')
        end
      end
    end

    newparam(:source) do
      desc "Where to find the package file. This is mostly used by providers that don't
        automatically download packages from a central repository. (For example:
        the `yum` provider ignores this attribute, `apt` provider uses it if present
        and the `rpm` and `dpkg` providers require it.)

        Different providers accept different values for `source`. Most providers
        accept paths to local files stored on the target system. Some providers
        may also accept URLs or network drive paths. Puppet will not
        automatically retrieve source files for you, and usually just passes the
        value of `source` to the package installation command.

        You can use a `file` resource if you need to manually copy package files
        to the target system."

      validate do |value|
        provider.validate_source(value)
      end
    end

    newparam(:instance) do
      desc "A read-only parameter set by the package."
    end

    newparam(:status) do
      desc "A read-only parameter set by the package."
    end

    newparam(:adminfile) do
      desc "A file containing package defaults for installing packages.

        This attribute is only used on Solaris. Its value should be a path to a
        local file stored on the target system. Solaris's package tools expect
        either an absolute file path or a relative path to a file in
        `/var/sadm/install/admin`.

        The value of `adminfile` will be passed directly to the `pkgadd` or
        `pkgrm` command with the `-a <ADMINFILE>` option."
    end

    newparam(:responsefile) do
      desc "A file containing any necessary answers to questions asked by
        the package.  This is currently used on Solaris and Debian.  The
        value will be validated according to system rules, but it should
        generally be a fully qualified path."
    end

    newparam(:configfiles) do
      desc "Whether to keep or replace modified config files when installing or
        upgrading a package. This only affects the `apt` and `dpkg` providers."

      defaultto :keep

      newvalues(:keep, :replace)
    end

    newparam(:category) do
      desc "A read-only parameter set by the package."
    end
    newparam(:platform) do
      desc "A read-only parameter set by the package."
    end
    newparam(:root) do
      desc "A read-only parameter set by the package."
    end
    newparam(:vendor) do
      desc "A read-only parameter set by the package."
    end
    newparam(:description) do
      desc "A read-only parameter set by the package."
    end

    newparam(:allowcdrom) do
      desc "Tells apt to allow cdrom sources in the sources.list file.
        Normally apt will bail if you try this."

      newvalues(:true, :false)
    end

    newparam(:enable_only, :boolean => false, :parent => Puppet::Parameter::Boolean) do
      desc <<-EOT
        Tells `dnf module` to only enable a specific module, instead
        of installing its default profile.

        Modules with no default profile will be enabled automatically
        without the use of this parameter.

        Conflicts with the `flavor` property, which selects a profile
        to install.
      EOT
      defaultto false

      validate do |value|
        if [true, :true, "true"].include?(value) && @resource[:flavor]
          raise ArgumentError, _('Cannot have both `enable_only => true` and `flavor`')
        end
        if [:disabled, "disabled"].include?(@resource[:ensure])
          raise ArgumentError, _('Cannot have both `ensure => disabled` and `enable_only => true`')
        end
      end
    end

    newparam(:install_only, :boolean => false, :parent => Puppet::Parameter::Boolean, :required_features => :install_only) do
      desc <<-EOT
        It should be set for packages that should only ever be installed,
        never updated. Kernels in particular fall into this category.
      EOT
      defaultto false
    end

    newparam(:install_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :install_options) do
      desc <<-EOT
        An array of additional options to pass when installing a package. These
        options are package-specific, and should be documented by the software
        vendor.  One commonly implemented option is `INSTALLDIR`:

            package { 'mysql':
              ensure          => installed,
              source          => 'N:/packages/mysql-5.5.16-winx64.msi',
              install_options => [ '/S', { 'INSTALLDIR' => 'C:\\mysql-5.5' } ],
            }

        Each option in the array can either be a string or a hash, where each
        key and value pair are interpreted in a provider specific way.  Each
        option will automatically be quoted when passed to the install command.

        With Windows packages, note that file paths in an install option must
        use backslashes. (Since install options are passed directly to the
        installation command, forward slashes won't be automatically converted
        like they are in `file` resources.) Note also that backslashes in
        double-quoted strings _must_ be escaped and backslashes in single-quoted
        strings _can_ be escaped.
      EOT
    end

    newparam(:uninstall_options, :parent => Puppet::Parameter::PackageOptions, :required_features => :uninstall_options) do
      desc <<-EOT
        An array of additional options to pass when uninstalling a package. These
        options are package-specific, and should be documented by the software
        vendor.  For example:

            package { 'VMware Tools':
              ensure            => absent,
              uninstall_options => [ { 'REMOVE' => 'Sync,VSS' } ],
            }

        Each option in the array can either be a string or a hash, where each
        key and value pair are interpreted in a provider specific way.  Each
        option will automatically be quoted when passed to the uninstall
        command.

        On Windows, this is the **only** place in Puppet where backslash
        separators should be used.  Note that backslashes in double-quoted
        strings _must_ be double-escaped and backslashes in single-quoted
        strings _may_ be double-escaped.
      EOT
    end

    newparam(:allow_virtual, :boolean => true, :parent => Puppet::Parameter::Boolean, :required_features => :virtual_packages) do
      desc 'Specifies if virtual package names are allowed for install and uninstall.'

      defaultto do
        provider_class = provider.class
        if provider_class.respond_to?(:defaultto_allow_virtual)
          provider_class.defaultto_allow_virtual
        else
          true
        end
      end
    end

    autorequire(:file) do
      autos = []
      [:responsefile, :adminfile].each { |param|
        val = self[param]
        if val
          autos << val
        end
      }

      source = self[:source]
      if source && absolute_path?(source)
        autos << source
      end
      autos
    end

    # This only exists for testing.
    def clear
      obj = @parameters[:ensure]
      if obj
        obj.latest = nil
      end
    end

    # The 'query' method returns a hash of info if the package
    # exists and returns nil if it does not.
    def exists?
      @provider.get(:ensure) != :absent
    end

    def present?(current_values)
      super && current_values[:ensure] != :purged
    end

    # This parameter exists to ensure backwards compatibility is preserved.
    # See https://github.com/puppetlabs/puppet/pull/2614 for discussion.
    # If/when a metaparameter for controlling how arbitrary resources respond
    # to refreshing is created, that will supersede this, and this will be
    # deprecated.
    newparam(:reinstall_on_refresh) do
      desc "Whether this resource should respond to refresh events (via `subscribe`,
        `notify`, or the `~>` arrow) by reinstalling the package. Only works for
        providers that support the `reinstallable` feature.

        This is useful for source-based distributions, where you may want to
        recompile a package if the build options change.

        If you use this, be careful of notifying classes when you want to restart
        services. If the class also contains a refreshable package, doing so could
        cause unnecessary re-installs."
      newvalues(:true, :false)

      defaultto :false
    end

    # When a refresh event is triggered, calls reinstall on providers
    # that support the reinstall_on_refresh parameter.
    def refresh
      if provider.reinstallable? &&
        @parameters[:reinstall_on_refresh].value == :true &&
        @parameters[:ensure].value != :purged &&
        @parameters[:ensure].value != :absent

        provider.reinstall
      end
    end

    newproperty(:mark, :required_features => :holdable) do
      mark_doc='Valid values are: hold/none'
      desc <<-EOT
        Set to hold to tell Debian apt/Solaris pkg to hold the package version

        #{mark_doc}
        Default is "none". Mark can be specified with or without `ensure`,
        if `ensure` is missing will default to "present".

        Mark cannot be specified together with "purged", or "absent"
        values for `ensure`.
      EOT
      newvalues(:hold, :none)
      munge do |value|
        case value
        when "hold", :hold
          :hold
        when "none", :none
          :none
        else
          raise ArgumentError, _('Invalid hold value %{value}. %{doc}') % { value: value.inspect, doc: mark_doc}
        end
      end

      def insync?(is)
        @should[0] == is
      end

      def should
        @should[0] if @should && @should.is_a?(Array) && @should.size == 1
      end

      def retrieve
        provider.properties[:mark]
      end

      def sync
        if @should[0] == :hold
          provider.hold
        else
          provider.unhold
        end
      end
    end

    validate do
      if @parameters[:mark] && [:absent, :purged].include?(@parameters[:ensure].should)
        raise ArgumentError, _('You cannot use "mark" property while "ensure" is one of ["absent", "purged"]')
      end
    end
  end
end