File: file_setting.rb

package info (click to toggle)
puppet-agent 8.10.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 27,404 kB
  • sloc: ruby: 286,820; sh: 492; xml: 116; makefile: 88; cs: 68
file content (232 lines) | stat: -rw-r--r-- 6,737 bytes parent folder | download | duplicates (2)
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
# frozen_string_literal: true

# A file.
class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting
  class SettingError < StandardError; end

  # An unspecified user or group
  #
  # @api private
  class Unspecified
    def value
      nil
    end
  end

  # A "root" user or group
  #
  # @api private
  class Root
    def value
      "root"
    end
  end

  # A "service" user or group that picks up values from settings when the
  # referenced user or group is safe to use (it exists or will be created), and
  # uses the given fallback value when not safe.
  #
  # @api private
  class Service
    # @param name [Symbol] the name of the setting to use as the service value
    # @param fallback [String, nil] the value to use when the service value cannot be used
    # @param settings [Puppet::Settings] the puppet settings object
    # @param available_method [Symbol] the name of the method to call on
    #   settings to determine if the value in settings is available on the system
    #
    def initialize(name, fallback, settings, available_method)
      @settings = settings
      @available_method = available_method
      @name = name
      @fallback = fallback
    end

    def value
      if safe_to_use_settings_value?
        @settings[@name]
      else
        @fallback
      end
    end

    private

    def safe_to_use_settings_value?
      @settings[:mkusers] or @settings.send(@available_method)
    end
  end

  attr_accessor :mode

  def initialize(args)
    @group = Unspecified.new
    @owner = Unspecified.new
    super(args)
  end

  # @param value [String] the group to use on the created file (can only be "root" or "service")
  # @api public
  def group=(value)
    @group = case value
             when "root"
               Root.new
             when "service"
               # Group falls back to `nil` because we cannot assume that a "root" group exists.
               # Some systems have root group, others have wheel, others have something else.
               Service.new(:group, nil, @settings, :service_group_available?)
             else
               unknown_value(':group', value)
             end
  end

  # @param value [String] the owner to use on the created file (can only be "root" or "service")
  # @api public
  def owner=(value)
    @owner = case value
             when "root"
               Root.new
             when "service"
               Service.new(:user, "root", @settings, :service_user_available?)
             else
               unknown_value(':owner', value)
             end
  end

  # @return [String, nil] the name of the group to use for the file or nil if the group should not be managed
  # @api public
  def group
    @group.value
  end

  # @return [String, nil] the name of the user to use for the file or nil if the user should not be managed
  # @api public
  def owner
    @owner.value
  end

  def set_meta(meta)
    self.owner = meta.owner if meta.owner
    self.group = meta.group if meta.group
    self.mode = meta.mode if meta.mode
  end

  def munge(value)
    if value.is_a?(String) and value != ':memory:' # for sqlite3 in-memory tests
      value = File.expand_path(value)
    end
    value
  end

  def type
    :file
  end

  # Turn our setting thing into a Puppet::Resource instance.
  def to_resource
    type = self.type
    return nil unless type

    path = value

    return nil unless path.is_a?(String)

    # Make sure the paths are fully qualified.
    path = File.expand_path(path)

    return nil unless type == :directory || Puppet::FileSystem.exist?(path)
    return nil if path =~ %r{^/dev} || path =~ %r{^[A-Z]:/dev}i

    resource = Puppet::Resource.new(:file, path)

    if Puppet[:manage_internal_file_permissions]
      if mode
        # This ends up mimicking the munge method of the mode
        # parameter to make sure that we're always passing the string
        # version of the octal number.  If we were setting the
        # 'should' value for mode rather than the 'is', then the munge
        # method would be called for us automatically.  Normally, one
        # wouldn't need to call the munge method manually, since
        # 'should' gets set by the provider and it should be able to
        # provide the data in the appropriate format.
        mode = self.mode
        mode = mode.to_i(8) if mode.is_a?(String)
        mode = mode.to_s(8)
        resource[:mode] = mode
      end

      # REMIND fails on Windows because chown/chgrp functionality not supported yet
      if Puppet.features.root? and !Puppet::Util::Platform.windows?
        resource[:owner] = owner if owner
        resource[:group] = group if group
      end
    end

    resource[:ensure] = type
    resource[:loglevel] = :debug
    resource[:links] = :follow
    resource[:backup] = false

    resource.tag(section, name, "settings")

    resource
  end

  # @api private
  # @param option [String] Extra file operation mode information to use
  #   (defaults to read-only mode 'r')
  #   This is the standard mechanism Ruby uses in the IO class, and therefore
  #   encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*"
  #   for example, a:ASCII or w+:UTF-8
  def exclusive_open(option = 'r', &block)
    controlled_access do |mode|
      Puppet::FileSystem.exclusive_open(file(), mode, option, &block)
    end
  end

  # @api private
  # @param option [String] Extra file operation mode information to use
  #   (defaults to read-only mode 'r')
  #   This is the standard mechanism Ruby uses in the IO class, and therefore
  #   encoding may be explicitly like fmode : encoding or fmode : "BOM|UTF-*"
  #   for example, a:ASCII or w+:UTF-8
  def open(option = 'r', &block)
    controlled_access do |mode|
      Puppet::FileSystem.open(file, mode, option, &block)
    end
  end

  private

  def file
    Puppet::FileSystem.pathname(value)
  end

  def unknown_value(parameter, value)
    raise SettingError, _("The %{parameter} parameter for the setting '%{name}' must be either 'root' or 'service', not '%{value}'") % { parameter: parameter, name: name, value: value }
  end

  def controlled_access(&block)
    chown = nil
    if Puppet.features.root?
      chown = [owner, group]
    else
      chown = [nil, nil]
    end

    Puppet::Util::SUIDManager.asuser(*chown) do
      # Update the umask to make non-executable files
      Puppet::Util.withumask(File.umask ^ 0o111) do
        yielded_value = case mode
                        when String
                          mode.to_i(8)
                        when NilClass
                          0o640
                        else
                          mode
                        end

        yield yielded_value
      end
    end
  end
end