File: dnfmodule.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 (141 lines) | stat: -rw-r--r-- 4,433 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
# dnfmodule - A puppet package provider for DNF modules
#
# Installing a module:
#  package { 'postgresql':
#   provider => 'dnfmodule',
#   ensure   => '9.6',  # install a specific stream
#   flavor   => 'client',  # install a specific profile
# }


require_relative '../../../puppet/provider/package'

Puppet::Type.type(:package).provide :dnfmodule, :parent => :dnf do

  has_feature :installable, :uninstallable, :versionable, :supports_flavors, :disableable
  #has_feature :upgradeable
  # it's not (yet) feasible to make this upgradeable since module streams don't
  # always have matching version types (i.e. idm has streams DL1 and client,
  # other modules have semver streams, others have string streams... we cannot
  # programatically determine a latest version for ensure => 'latest'

  commands :dnf => '/usr/bin/dnf'

  def self.current_version
    @current_version ||= dnf('--version').split.first
  end

  def self.prefetch(packages)
    if Puppet::Util::Package.versioncmp(current_version, '3.0.1') < 0
      raise Puppet::Error, _("Modules are not supported on DNF versions lower than 3.0.1")
    end
    super
  end

  def self.instances
    packages = []
    cmd = "#{command(:dnf)} module list -d 0 -e #{error_level}"
    execute(cmd).each_line do |line|
      # select only lines with actual packages since DNF clutters the output
      next unless line =~ /\[[eix]\][, ]/
      line.gsub!(/\[d\]/, '')  # we don't care about the default flag

      flavor = if line.include?('[i]')
                 line.split('[i]').first.split.last
               else
                 :absent
               end

      packages << new(
        name: line.split[0],
        ensure: if line.include?('[x]')
                  :disabled
                else
                  line.split[1]
                end,
        flavor: flavor,
        provider: name
      )
    end
    packages
  end

  def query
    pkg = self.class.instances.find do |package|
            @resource[:name] == package.name
          end
    pkg ? pkg.properties : nil
  end

  # to install specific streams and profiles:
  # $ dnf module install module-name:stream/profile
  # $ dnf module install perl:5.24/minimal
  # if unspecified, they will be defaulted (see [d] param in dnf module list output)
  def install
    # ensure we start fresh (remove existing stream)
    uninstall unless [:absent, :purged].include?(@property_hash[:ensure])

    args = @resource[:name].dup
    case @resource[:ensure]
    when true, false, Symbol
      # pass
    else
      args << ":#{@resource[:ensure]}"
    end
    args << "/#{@resource[:flavor]}" if @resource[:flavor]

    if @resource[:enable_only] == true
      enable(args)
    else
      begin
        execute([command(:dnf), 'module', 'install', '-d', '0', '-e', self.class.error_level, '-y', args])
      rescue Puppet::ExecutionFailure => e
        # module has no default profile and no profile was requested, so just enable the stream
        # DNF versions prior to 4.2.8 do not need this workaround
        # see https://bugzilla.redhat.com/show_bug.cgi?id=1669527
        if @resource[:flavor] == nil && e.message =~ /^(?:missing|broken) groups or modules: #{Regexp.quote(@resource[:name])}$/
          enable(args)
        else
          raise
        end
      end
    end
  end

  # should only get here when @resource[ensure] is :disabled
  def insync?(is)
    if resource[:ensure] == :disabled
      # in sync only if package is already disabled
      pkg = self.class.instances.find do |package|
        @resource[:name] == package.name && package.properties[:ensure] == :disabled
      end
      return true if pkg
    end
    return false
  end

  def enable(args = @resource[:name])
    execute([command(:dnf), 'module', 'enable', '-d', '0', '-e', self.class.error_level, '-y', args])
  end

  def uninstall
    execute([command(:dnf), 'module', 'remove', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
    reset  # reset module to the default stream
  end

  def disable(args = @resource[:name])
    execute([command(:dnf), 'module', 'disable', '-d', '0', '-e', self.class.error_level, '-y', args])
  end

  def reset
    execute([command(:dnf), 'module', 'reset', '-d', '0', '-e', self.class.error_level, '-y', @resource[:name]])
  end

  def flavor
    @property_hash[:flavor]
  end

  def flavor=(value)
    install if flavor != @resource.should(:flavor)
  end
end