File: content.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 (180 lines) | stat: -rw-r--r-- 6,315 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
# frozen_string_literal: true

require 'net/http'
require 'uri'
require 'tempfile'

require_relative '../../../puppet/util/checksums'
require_relative '../../../puppet/type/file/data_sync'

module Puppet
  Puppet::Type.type(:file).newproperty(:content) do
    include Puppet::Util::Checksums
    include Puppet::DataSync

    attr_reader :actual_content

    desc <<-'EOT'
      The desired contents of a file, as a string. This attribute is mutually
      exclusive with `source` and `target`.

      Newlines and tabs can be specified in double-quoted strings using
      standard escaped syntax --- \n for a newline, and \t for a tab.

      With very small files, you can construct content strings directly in
      the manifest...

          define resolve($nameserver1, $nameserver2, $domain, $search) {
              $str = "search ${search}
                  domain ${domain}
                  nameserver ${nameserver1}
                  nameserver ${nameserver2}
                  "

              file { '/etc/resolv.conf':
                content => $str,
              }
          }

      ...but for larger files, this attribute is more useful when combined with the
      [template](https://puppet.com/docs/puppet/latest/function.html#template)
      or [file](https://puppet.com/docs/puppet/latest/function.html#file)
      function.
    EOT

    # Store a checksum as the value, rather than the actual content.
    # Simplifies everything.
    munge do |value|
      if value == :absent
        value
      elsif value.is_a?(String) && checksum?(value)
        # XXX This is potentially dangerous because it means users can't write a file whose
        # entire contents are a plain checksum unless it is a Binary content.
        Puppet.puppet_deprecation_warning([
          # TRANSLATORS "content" is an attribute and should not be translated
          _('Using a checksum in a file\'s "content" property is deprecated.'),
          # TRANSLATORS "filebucket" is a resource type and should not be translated. The quoted occurrence of "content" is an attribute and should not be translated.
          _('The ability to use a checksum to retrieve content from the filebucket using the "content" property will be removed in a future release.'),
          # TRANSLATORS "content" is an attribute and should not be translated.
          _('The literal value of the "content" property will be written to the file.'),
          # TRANSLATORS "static catalogs" should not be translated.
          _('The checksum retrieval functionality is being replaced by the use of static catalogs.'),
          _('See https://puppet.com/docs/puppet/latest/static_catalogs.html for more information.')
        ].join(" "),
                                          :file => @resource.file,
                                          :line => @resource.line) if !@actual_content && !resource.parameter(:source)
        value
      else
        @actual_content = value.is_a?(Puppet::Pops::Types::PBinaryType::Binary) ? value.binary_buffer : value
        resource.parameter(:checksum).sum(@actual_content)
      end
    end

    # Checksums need to invert how changes are printed.
    def change_to_s(currentvalue, newvalue)
      # Our "new" checksum value is provided by the source.
      source = resource.parameter(:source)
      tmp = source.checksum if source
      if tmp
        newvalue = tmp
      end
      if currentvalue == :absent
        "defined content as '#{newvalue}'"
      elsif newvalue == :absent
        "undefined content from '#{currentvalue}'"
      else
        "content changed '#{currentvalue}' to '#{newvalue}'"
      end
    end

    def length
      (actual_content and actual_content.length) || 0
    end

    def content
      should
    end

    # Override this method to provide diffs if asked for.
    # Also, fix #872: when content is used, and replace is true, the file
    # should be insync when it exists
    def insync?(is)
      if resource[:source] && resource[:checksum_value]
        # Asserts that nothing has changed since validate ran.
        devfail "content property should not exist if source and checksum_value are specified"
      end

      contents_prop = resource.parameter(:source) || self
      checksum_insync?(contents_prop, is, !resource[:content].nil?) { |inner| super(inner) }
    end

    def property_matches?(current, desired)
      # If checksum_value is specified, it overrides comparing the content field.
      checksum_type = resource.parameter(:checksum).value
      checksum_value = resource.parameter(:checksum_value)
      if checksum_value
        desired = "{#{checksum_type}}#{checksum_value.value}"
      end

      # The inherited equality is always accepted, so use it if valid.
      return true if super(current, desired)

      date_matches?(checksum_type, current, desired)
    end

    def retrieve
      retrieve_checksum(resource)
    end

    # Make sure we're also managing the checksum property.
    def should=(value)
      # treat the value as a bytestring
      value = value.b if value.is_a?(String)
      @resource.newattr(:checksum) unless @resource.parameter(:checksum)
      super
    end

    # Just write our content out to disk.
    def sync
      contents_sync(resource.parameter(:source) || self)
    end

    def write(file)
      resource.parameter(:checksum).sum_stream { |sum|
        each_chunk_from { |chunk|
          sum << chunk
          file.print chunk
        }
      }
    end

    private

    # the content is munged so if it's a checksum source_or_content is nil
    # unless the checksum indirectly comes from source
    def each_chunk_from
      if actual_content.is_a?(String)
        yield actual_content
      elsif content_is_really_a_checksum? && actual_content.nil?
        yield read_file_from_filebucket
      elsif actual_content.nil?
        yield ''
      end
    end

    def content_is_really_a_checksum?
      checksum?(should)
    end

    def read_file_from_filebucket
      dipper = resource.bucket
      raise "Could not get filebucket from file" unless dipper

      sum = should.sub(/\{\w+\}/, '')

      dipper.getfile(sum)
    rescue => detail
      self.fail Puppet::Error, "Could not retrieve content for #{should} from filebucket: #{detail}", detail
    end
  end
end