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
|
require 'puppet/util/checksums'
# Keep a copy of the file checksums, and notify when they change. This
# property never actually modifies the system, it only notices when the system
# changes on its own.
Puppet::Type.type(:file).newproperty(:checksum) do
include Puppet::Util::Checksums
desc "How to check whether a file has changed. This state is used internally
for file copying, but it can also be used to monitor files somewhat
like Tripwire without managing the file contents in any way. You can
specify that a file's checksum should be monitored and then subscribe to
the file from another object and receive events to signify
checksum changes, for instance.
There are a number of checksum types available including MD5 hashing (and
an md5lite variation that only hashes the first 500 characters of the
file."
@event = :file_changed
@unmanaged = true
@validtypes = %w{md5 md5lite timestamp mtime time}
def self.validtype?(type)
@validtypes.include?(type)
end
@validtypes.each do |ctype|
newvalue(ctype) do
handlesum()
end
end
str = @validtypes.join("|")
# This is here because Puppet sets this internally, using
# {md5}......
newvalue(/^\{#{str}\}/) do
handlesum()
end
newvalue(:nosum) do
# nothing
:nochange
end
# If they pass us a sum type, behave normally, but if they pass
# us a sum type + sum, stick the sum in the cache.
munge do |value|
if value =~ /^\{(\w+)\}(.+)$/
type = symbolize($1)
sum = $2
cache(type, sum)
return type
else
if FileTest.directory?(@resource[:path])
return :time
elsif @resource[:source] and value.to_s != "md5"
self.warning("Files with source set must use md5 as checksum. Forcing to md5 from %s for %s" % [ value, @resource[:path] ])
return :md5
else
return symbolize(value)
end
end
end
# Store the checksum in the data cache, or retrieve it if only the
# sum type is provided.
def cache(type, sum = nil)
return unless c = resource.catalog and c.host_config?
unless type
raise ArgumentError, "A type must be specified to cache a checksum"
end
type = symbolize(type)
type = :mtime if type == :timestamp
type = :ctime if type == :time
unless state = @resource.cached(:checksums)
self.debug "Initializing checksum hash"
state = {}
@resource.cache(:checksums, state)
end
if sum
unless sum =~ /\{\w+\}/
sum = "{%s}%s" % [type, sum]
end
state[type] = sum
else
return state[type]
end
end
# Because source and content and whomever else need to set the checksum
# and do the updating, we provide a simple mechanism for doing so.
def checksum=(value)
munge(@should)
self.updatesum(value)
end
def checktype
self.should || :md5
end
# Checksums need to invert how changes are printed.
def change_to_s(currentvalue, newvalue)
begin
if currentvalue == :absent
return "defined '%s' as '%s'" %
[self.name, self.currentsum]
elsif newvalue == :absent
return "undefined %s from '%s'" %
[self.name, self.is_to_s(currentvalue)]
else
if defined? @cached and @cached
return "%s changed '%s' to '%s'" %
[self.name, @cached, self.is_to_s(currentvalue)]
else
return "%s changed '%s' to '%s'" %
[self.name, self.currentsum, self.is_to_s(currentvalue)]
end
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
def currentsum
cache(checktype())
end
# Retrieve the cached sum
def getcachedsum
hash = nil
unless hash = @resource.cached(:checksums)
hash = {}
@resource.cache(:checksums, hash)
end
sumtype = self.should
if hash.include?(sumtype)
#self.notice "Found checksum %s for %s" %
# [hash[sumtype] ,@resource[:path]]
sum = hash[sumtype]
unless sum =~ /^\{\w+\}/
sum = "{%s}%s" % [sumtype, sum]
end
return sum
elsif hash.empty?
#self.notice "Could not find sum of type %s" % sumtype
return :nosum
else
#self.notice "Found checksum for %s but not of type %s" %
# [@resource[:path],sumtype]
return :nosum
end
end
# Calculate the sum from disk.
def getsum(checktype, file = nil)
sum = ""
checktype = :mtime if checktype == :timestamp
checktype = :ctime if checktype == :time
self.should = checktype = :md5 if @resource.property(:source)
file ||= @resource[:path]
return nil unless FileTest.exist?(file)
if ! FileTest.file?(file)
checktype = :mtime
end
method = checktype.to_s + "_file"
self.fail("Invalid checksum type %s" % checktype) unless respond_to?(method)
return "{%s}%s" % [checktype, send(method, file)]
end
# At this point, we don't actually modify the system, we modify
# the stored state to reflect the current state, and then kick
# off an event to mark any changes.
def handlesum
currentvalue = self.retrieve
if currentvalue.nil?
raise Puppet::Error, "Checksum state for %s is somehow nil" %
@resource.title
end
if self.insync?(currentvalue)
self.debug "Checksum is already in sync"
return nil
end
# If we still can't retrieve a checksum, it means that
# the file still doesn't exist
if currentvalue == :absent
# if they're copying, then we won't worry about the file
# not existing yet
return nil unless @resource.property(:source)
end
# If the sums are different, then return an event.
if self.updatesum(currentvalue)
return :file_changed
else
return nil
end
end
def insync?(currentvalue)
@should = [checktype()]
if cache(checktype())
return currentvalue == currentsum()
else
# If there's no cached sum, then we don't want to generate
# an event.
return true
end
end
# Even though they can specify multiple checksums, the insync?
# mechanism can really only test against one, so we'll just retrieve
# the first specified sum type.
def retrieve(usecache = false)
# When the 'source' is retrieving, it passes "true" here so
# that we aren't reading the file twice in quick succession, yo.
currentvalue = currentsum()
return currentvalue if usecache and currentvalue
stat = nil
return :absent unless stat = @resource.stat
if stat.ftype == "link" and @resource[:links] != :follow
self.debug "Not checksumming symlink"
# @resource.delete(:checksum)
return currentvalue
end
# Just use the first allowed check type
currentvalue = getsum(checktype())
# If there is no sum defined, then store the current value
# into the cache, so that we're not marked as being
# out of sync. We don't want to generate an event the first
# time we get a sum.
self.updatesum(currentvalue) unless cache(checktype())
# @resource.debug "checksum state is %s" % self.is
return currentvalue
end
# Store the new sum to the state db.
def updatesum(newvalue)
return unless c = resource.catalog and c.host_config?
result = false
# if we're replacing, vs. updating
if sum = cache(checktype())
return false if newvalue == sum
self.debug "Replacing %s checksum %s with %s" % [@resource.title, sum, newvalue]
result = true
else
@resource.debug "Creating checksum %s" % newvalue
result = false
end
# Cache the sum so the log message can be right if possible.
@cached = sum
cache(checktype(), newvalue)
return result
end
end
|