File: inifile.rb

package info (click to toggle)
puppet-agent 8.10.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 27,392 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (408 lines) | stat: -rw-r--r-- 12,653 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
require 'puppet/util/inifile'

module Puppet::Provider::Yumrepo # rubocop:disable Style/ClassAndModuleChildren
  module IniConfig
    class Section < Puppet::Util::IniConfig::Section; end

    # Examines a file on disk and identifies whether a section exists.
    class PhysicalFile < Puppet::Util::IniConfig::PhysicalFile
      def store
        unlinked = false
        if @destroy_empty && (sections.empty? || sections.all?(&:destroy?))
          ::File.unlink(@file)
          unlinked = true
        elsif sections.any?(&:dirty?)
          text = self.format
          @filetype.write(text)
        end
        sections.each(&:mark_clean)
        unlinked
      end

      private

      def section_exists?(name)
        section = @file_collection.get_section(name) if @file_collection
        if get_section(name)
          true
        elsif section && !section.destroy?
          true
        else
          false
        end
      end
    end

    # Creates a collection of new files on disk.
    class FileCollection < Puppet::Util::IniConfig::FileCollection
      def store
        @files.delete_if do |_, file|
          file.store
        end
      end

      private

      # Create a new physical file and set required attributes on that file.
      def new_physical_file(file)
        @files[file] = PhysicalFile.new(file, destroy_empty: true)
        @files[file].file_collection = self
        @files[file]
      end
    end
    File = FileCollection
  end
end

Puppet::Type.type(:yumrepo).provide(:inifile) do
  desc <<-EOD
    Manage yum repo configurations by parsing yum INI configuration files.

    ### Fetching instances

    When fetching repo instances, directory entries in '/etc/yum/repos.d',
    '/etc/yum.repos.d', and the directory optionally specified by the reposdir
    key in '/etc/yum.conf' will be checked. If a given directory does not exist it
    will be ignored. In addition, all sections in '/etc/yum.conf' aside from
    'main' will be created as sections.

    ### Storing instances

    When creating a new repository, a new section will be added in the first
    yum repo directory that exists. The custom directory specified by the
    '/etc/yum.conf' reposdir property is checked first, followed by
    '/etc/yum/repos.d', and then '/etc/yum.repos.d'. If none of these exist, the
    section will be created in '/etc/yum.conf'.
  EOD

  PROPERTIES = Puppet::Type.type(:yumrepo).validproperties

  # Retrieve all providers based on existing yum repositories
  #
  # @api public
  # @return [Array<Puppet::Provider>] providers generated from existing yum
  #   repository definitions.
  def self.instances
    instances = []

    virtual_inifile.each_section do |section|
      # Ignore the 'main' section in yum.conf since it's not a repository.
      next if section.name == 'main'

      attributes_hash = { name: section.name, target: section.file, ensure: :present, provider: :yumrepo }

      section.entries.each do |key, value|
        key = key.to_sym
        if valid_property?(key)
          attributes_hash[key] = value
        elsif key == :name
          attributes_hash[:descr] = value
        end
      end
      instances << new(attributes_hash)
    end

    instances
  end

  # Match catalog type instances to provider instances.
  #
  # @api public
  # @param resources [Array<Puppet::Type::Yumrepo>] Resources to prefetch.
  # @return [void]
  def self.prefetch(resources)
    repos = instances
    resources.each_key do |name|
      provider = repos.find { |repo| repo.name == name }
      if provider
        resources[name].provider = provider
      end
    end
  end

  # Return a list of existing directories that could contain repo files.
  #
  # @api private
  # @param conf [String] Configuration file to look for directories in.
  # @param dirs [Array<String>] Default locations for yum repos.
  # @return [Array<String>] All present directories that may contain yum repo configs.
  def self.reposdir(conf = '/etc/yum.conf', dirs = ['/etc/yum.repos.d', '/etc/yum/repos.d'])
    reposdir = find_conf_value('reposdir', conf)
    # Use directories in reposdir if they are set instead of default
    if reposdir
      # Follow the code from the yum/config.py
      reposdir.tr!("\n", ' ')
      reposdir.tr!(',', ' ')
      dirs = reposdir.split
    end
    dirs.select! { |dir| Puppet::FileSystem.exist?(dir) }
    if dirs.empty?
      Puppet.debug('No yum directories were found on the local filesystem')
    end

    dirs
  end

  # Used for testing only
  # @api private
  def self.clear
    @virtual = nil
  end

  # Helper method to look up specific values in ini style files.
  #
  # @api private
  # @param value [String] Value to look for in the configuration file.
  # @param conf [String] Configuration file to check for value.
  # @return [String] The value of a looked up key from the configuration file.
  def self.find_conf_value(value, conf = '/etc/yum.conf')
    return unless Puppet::FileSystem.exist?(conf)

    file = Puppet::Provider::Yumrepo::IniConfig::PhysicalFile.new(conf)
    file.read
    main = file.get_section('main')
    main ? main[value] : nil
  end

  # Enumerate all files that may contain yum repository configs.
  # '/etc/yum.conf' is always included.
  #
  # @api private
  # @return [Array<String>
  def self.repofiles
    files = ['/etc/yum.conf']
    reposdir.each do |dir|
      Dir.glob("#{dir}/*.repo").each do |file|
        files << file
      end
    end

    files
  end

  # Build a virtual inifile by reading in numerous .repo files into a single
  # virtual file to ease manipulation.
  # @api private
  # @return [Puppet::Provider::Yumrepo::IniConfig::File] The virtual inifile representing
  #   multiple real files.
  def self.virtual_inifile
    unless @virtual
      @virtual = Puppet::Provider::Yumrepo::IniConfig::File.new
      repofiles.each do |file|
        @virtual.read(file) if Puppet::FileSystem.file?(file)
      end
    end
    @virtual
  end

  # Is the given key a valid type property?
  #
  # @api private
  # @param key [String] The property to look up.
  # @return [Boolean] Returns true if the property is defined in the type.
  def self.valid_property?(key)
    PROPERTIES.include?(key)
  end

  # Return an existing INI section or create a new section in the default location
  #
  # The default location is determined based on what yum repo directories
  # and files are present. If /etc/yum.conf has a value for 'reposdir' then that
  # is preferred. If no such INI property is found then the first default yum
  # repo directory that is present is used. If no default directories exist then
  # /etc/yum.conf is used.
  #
  # @param name [String] Section name to lookup in the virtual inifile.
  # @return [Puppet::Provider::Yumrepo::IniConfig::Section] The IniConfig section
  def self.section(name, target)
    path = repo_path(name, target)
    result = nil
    old_section = nil
    virtual_inifile.each_section do |section|
      if section.name.eql?(name)
        if target.nil? || target.eql?(:absent) || section.file.eql?(path)
          result = section
        else
          old_section = section
          section.destroy = true
        end
      end
    end
    # Create a new section if not found.
    unless result
      result = virtual_inifile.add_section(name, path)
      # Copy section from old target if found.
      old_section&.entries&.each { |entry| result.add_line(entry) }
    end
    result
  end

  # Save all yum repository files and force the mode to 0644
  # @api private
  # @return [void]
  def self.store(resource)
    inifile = virtual_inifile
    inifile.store

    target_mode = 0o644
    inifile.each_file do |file|
      next unless Puppet::FileSystem.exist?(file)
      current_mode = Puppet::FileSystem.stat(file).mode & 0o777
      next if current_mode == target_mode
      resource.info _('changing mode of %{file} from %{current_mode} to %{target_mode}') %
                    { file: file, current_mode: '%03o' % current_mode, target_mode: '%03o' % target_mode }
      Puppet::FileSystem.chmod(target_mode, file)
    end
  end

  def self.repo_path(name, target)
    dirs = reposdir
    path = if dirs.empty?
             # If no repo directories are present, default to using yum.conf.
             unless target.nil? || target.eql?(:absent)
               Puppet.debug("Using /etc/yum.conf instead of target #{target}")
             end
             '/etc/yum.conf'
           # The ordering of reposdir is [defaults, custom], and we want to use
           # the custom directory if present.else
           elsif target.nil? || target.eql?(:absent)
             File.join(dirs.last, "#{name}.repo")
           else
             parent = Puppet::FileSystem.dir_string(target)
             basename = Puppet::FileSystem.basename_string(target)
             suffix = target.end_with?('.repo') ? '' : '.repo'
             if parent == basename
               File.join(dirs.last, "#{target}#{suffix}")
             elsif dirs.include?(parent)
               "#{target}#{suffix}"
             else
               Puppet.debug("Parent directory of #{target} not a valid yum directory.")
               File.join(dirs.last, "#{basename}#{suffix}")
             end
           end
    path
  end

  # Create a new section for the given repository and set all the specified
  # properties in the section.
  #
  # @api public
  # @return [void]
  def create
    @property_hash[:ensure] = :present
    self.target = self.class.repo_path(name, @resource[:target]) unless @resource[:target].eql?(:absent)

    # Check to see if the file that would be created in the
    # default location for the yumrepo already exists on disk.
    # If it does, read it in to the virtual inifile
    path = self.class.repo_path(name, target)
    self.class.virtual_inifile.read(path) if Puppet::FileSystem.file?(path)

    # We fetch a list of properties from the type, then iterate
    # over them, avoiding ensure.  We're relying on .should to
    # check if the property has been set and should be modified,
    # and if so we set it in the virtual inifile.
    PROPERTIES.each do |property|
      next if property == :ensure

      value = @resource.should(property)
      send("#{property}=", value) if value
    end
  end

  # Does the given repository already exist?
  #
  # @api public
  # @return [Boolean]
  def exists?
    @property_hash[:ensure] == :present and (@resource[:target].eql?(:absent) or @property_hash[:target] == self.class.repo_path(name, @resource[:target]))
  end

  # Mark the given repository section for destruction.
  #
  # The actual removal of the section will be handled by {#flush} after the
  # resource has been fully evaluated.
  #
  # @api public
  # @return [void]
  def destroy
    # Flag file for deletion on flush.
    current_section.destroy = true

    @property_hash.clear
  end

  # Finalize the application of the given resource.
  #
  # @api public
  # @return [void]
  def flush
    self.class.store(self)
  end

  # Generate setters and getters for our INI properties.
  PROPERTIES.each do |property|
    # The ensure property uses #create, #exists, and #destroy we can't generate
    # meaningful setters and getters for this
    next if property == :ensure

    define_method(property) do
      get_property(property)
    end

    define_method("#{property}=") do |value|
      set_property(property, value)
    end
  end

  # Map the yumrepo 'descr' type property to the 'name' INI property.
  def descr
    unless @property_hash.key?(:descr)
      @property_hash[:descr] = current_section['name']
    end
    value = @property_hash[:descr]
    value.nil? ? :absent : value
  end

  def descr=(value)
    value = ((value == :absent) ? nil : value)
    current_section['name'] = value
    @property_hash[:descr] = value
  end

  def target
    unless @property_hash.key?(:target)
      @property_hash[:target] = self.class.repo_path(name, nil)
    end
    value = @property_hash[:target]
    value.nil? ? :absent : value
  end

  def target=(value)
    @property_hash[:target] = value
  end

  private

  def get_property(property)
    unless @property_hash.key?(property)
      @property_hash[property] = current_section[property.to_s]
    end
    value = @property_hash[property]
    value.nil? ? :absent : value
  end

  def set_property(property, value)
    value = ((value == :absent) ? nil : value)
    current_section[property.to_s] = value
    @property_hash[property] = value
  end

  def section(name)
    self.class.section(name, target)
  end

  def current_section
    self.class.section(name, target)
  end
end