puppet 0.9.2 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/CHANGELOG +58 -0
- data/README +21 -18
- data/Rakefile +176 -36
- data/bin/puppet +34 -48
- data/bin/puppetca +41 -28
- data/bin/puppetd +87 -65
- data/bin/puppetdoc +99 -23
- data/bin/puppetmasterd +72 -91
- data/conf/redhat/client.init +80 -0
- data/conf/redhat/client.sysconfig +11 -0
- data/conf/redhat/fileserver.conf +12 -0
- data/conf/redhat/puppet.spec +130 -0
- data/conf/redhat/server.init +89 -0
- data/conf/redhat/server.sysconfig +9 -0
- data/examples/code/allatonce +2 -2
- data/examples/code/assignments +1 -1
- data/examples/code/classing +2 -2
- data/examples/code/components +2 -2
- data/examples/code/file.bl +5 -5
- data/examples/code/filedefaults +2 -2
- data/examples/code/fileparsing +1 -1
- data/examples/code/filerecursion +1 -1
- data/examples/code/functions +1 -1
- data/examples/code/groups +1 -1
- data/examples/code/importing +1 -1
- data/examples/code/nodes +1 -1
- data/examples/code/one +1 -1
- data/examples/code/relationships +2 -2
- data/examples/code/simpletests +5 -5
- data/examples/code/snippets/argumentdefaults +2 -2
- data/examples/code/snippets/casestatement +16 -8
- data/examples/code/snippets/classheirarchy.pp +4 -4
- data/examples/code/snippets/classincludes.pp +4 -4
- data/examples/code/snippets/classpathtest +2 -2
- data/examples/code/snippets/componentmetaparams.pp +11 -0
- data/examples/code/snippets/dirchmod +5 -5
- data/examples/code/snippets/emptyclass.pp +9 -0
- data/examples/code/snippets/failmissingexecpath.pp +1 -1
- data/examples/code/snippets/falsevalues.pp +1 -1
- data/examples/code/snippets/filecreate +5 -5
- data/examples/code/snippets/implicititeration +5 -5
- data/examples/code/snippets/multipleinstances +4 -4
- data/examples/code/snippets/namevartest +3 -3
- data/examples/code/snippets/scopetest +1 -1
- data/examples/code/snippets/selectorvalues.pp +3 -3
- data/examples/code/snippets/simpledefaults +2 -2
- data/examples/code/snippets/simpleselector +5 -5
- data/examples/code/snippets/singleary.pp +19 -0
- data/examples/root/etc/init.d/sleeper +3 -2
- data/ext/emacs/puppet-mode-init.el +6 -0
- data/ext/emacs/puppet-mode.el +189 -0
- data/ext/ldap/puppet.schema +17 -0
- data/ext/{module:puppet → module_puppet} +30 -31
- data/ext/vim/filetype.vim +9 -0
- data/ext/vim/puppet.vim +87 -0
- data/install.rb +63 -30
- data/lib/puppet.rb +216 -122
- data/lib/puppet/client.rb +51 -416
- data/lib/puppet/client/ca.rb +17 -0
- data/lib/puppet/client/dipper.rb +78 -0
- data/lib/puppet/client/file.rb +20 -0
- data/lib/puppet/client/log.rb +17 -0
- data/lib/puppet/client/master.rb +246 -0
- data/lib/puppet/client/proxy.rb +27 -0
- data/lib/puppet/client/status.rb +7 -0
- data/lib/puppet/config.rb +563 -13
- data/lib/puppet/daemon.rb +50 -22
- data/lib/puppet/element.rb +4 -4
- data/lib/puppet/event-loop.rb +1 -0
- data/lib/puppet/event-loop/better-definers.rb +367 -0
- data/lib/puppet/event-loop/event-loop.rb +355 -0
- data/lib/puppet/event-loop/signal-system.rb +220 -0
- data/lib/puppet/event.rb +9 -11
- data/lib/puppet/filetype.rb +195 -0
- data/lib/puppet/log.rb +35 -12
- data/lib/puppet/metric.rb +2 -2
- data/lib/puppet/networkclient.rb +145 -0
- data/lib/puppet/parameter.rb +335 -0
- data/lib/puppet/parser/ast.rb +42 -1453
- data/lib/puppet/parser/ast/astarray.rb +88 -0
- data/lib/puppet/parser/ast/branch.rb +47 -0
- data/lib/puppet/parser/ast/caseopt.rb +66 -0
- data/lib/puppet/parser/ast/casestatement.rb +78 -0
- data/lib/puppet/parser/ast/classdef.rb +78 -0
- data/lib/puppet/parser/ast/compdef.rb +111 -0
- data/lib/puppet/parser/ast/component.rb +105 -0
- data/lib/puppet/parser/ast/hostclass.rb +82 -0
- data/lib/puppet/parser/ast/leaf.rb +86 -0
- data/lib/puppet/parser/ast/node.rb +103 -0
- data/lib/puppet/parser/ast/nodedef.rb +68 -0
- data/lib/puppet/parser/ast/objectdef.rb +336 -0
- data/lib/puppet/parser/ast/objectparam.rb +30 -0
- data/lib/puppet/parser/ast/objectref.rb +76 -0
- data/lib/puppet/parser/ast/selector.rb +60 -0
- data/lib/puppet/parser/ast/typedefaults.rb +45 -0
- data/lib/puppet/parser/ast/vardef.rb +44 -0
- data/lib/puppet/parser/interpreter.rb +31 -14
- data/lib/puppet/parser/lexer.rb +2 -4
- data/lib/puppet/parser/parser.rb +332 -242
- data/lib/puppet/parser/scope.rb +55 -38
- data/lib/puppet/server.rb +43 -44
- data/lib/puppet/server/authstore.rb +3 -6
- data/lib/puppet/server/ca.rb +5 -2
- data/lib/puppet/server/filebucket.rb +2 -4
- data/lib/puppet/server/fileserver.rb +28 -12
- data/lib/puppet/server/logger.rb +15 -4
- data/lib/puppet/server/master.rb +62 -7
- data/lib/puppet/sslcertificates.rb +41 -607
- data/lib/puppet/sslcertificates/ca.rb +291 -0
- data/lib/puppet/sslcertificates/certificate.rb +283 -0
- data/lib/puppet/statechange.rb +6 -1
- data/lib/puppet/storage.rb +67 -56
- data/lib/puppet/transaction.rb +25 -9
- data/lib/puppet/transportable.rb +102 -22
- data/lib/puppet/type.rb +1096 -315
- data/lib/puppet/type/component.rb +30 -21
- data/lib/puppet/type/cron.rb +409 -448
- data/lib/puppet/type/exec.rb +234 -174
- data/lib/puppet/type/group.rb +65 -82
- data/lib/puppet/type/nameservice.rb +247 -3
- data/lib/puppet/type/nameservice/netinfo.rb +29 -40
- data/lib/puppet/type/nameservice/objectadd.rb +52 -66
- data/lib/puppet/type/nameservice/posix.rb +6 -194
- data/lib/puppet/type/package.rb +447 -295
- data/lib/puppet/type/package/apt.rb +51 -50
- data/lib/puppet/type/package/bsd.rb +82 -0
- data/lib/puppet/type/package/dpkg.rb +85 -88
- data/lib/puppet/type/package/rpm.rb +67 -63
- data/lib/puppet/type/package/sun.rb +119 -98
- data/lib/puppet/type/package/yum.rb +41 -37
- data/lib/puppet/type/parsedtype.rb +295 -0
- data/lib/puppet/type/parsedtype/host.rb +143 -0
- data/lib/puppet/type/parsedtype/port.rb +232 -0
- data/lib/puppet/type/parsedtype/sshkey.rb +129 -0
- data/lib/puppet/type/pfile.rb +484 -460
- data/lib/puppet/type/pfile/checksum.rb +237 -181
- data/lib/puppet/type/pfile/content.rb +67 -0
- data/lib/puppet/type/pfile/ensure.rb +212 -0
- data/lib/puppet/type/pfile/group.rb +106 -105
- data/lib/puppet/type/pfile/mode.rb +98 -101
- data/lib/puppet/type/pfile/source.rb +228 -209
- data/lib/puppet/type/pfile/type.rb +18 -21
- data/lib/puppet/type/pfile/uid.rb +127 -130
- data/lib/puppet/type/pfilebucket.rb +68 -63
- data/lib/puppet/type/schedule.rb +341 -0
- data/lib/puppet/type/service.rb +351 -255
- data/lib/puppet/type/service/base.rb +9 -14
- data/lib/puppet/type/service/debian.rb +32 -38
- data/lib/puppet/type/service/init.rb +130 -130
- data/lib/puppet/type/service/smf.rb +48 -20
- data/lib/puppet/type/state.rb +229 -16
- data/lib/puppet/type/symlink.rb +51 -63
- data/lib/puppet/type/tidy.rb +105 -102
- data/lib/puppet/type/user.rb +118 -180
- data/lib/puppet/util.rb +100 -6
- data/test/certmgr/certmgr.rb +0 -1
- data/test/client/client.rb +4 -4
- data/test/executables/puppetbin.rb +7 -14
- data/test/executables/puppetca.rb +18 -24
- data/test/executables/puppetd.rb +7 -16
- data/test/executables/puppetmasterd.rb +7 -9
- data/test/executables/puppetmodule.rb +11 -16
- data/test/language/ast.rb +11 -7
- data/test/language/interpreter.rb +1 -1
- data/test/language/scope.rb +2 -0
- data/test/language/snippets.rb +30 -5
- data/test/language/transportable.rb +77 -0
- data/test/other/config.rb +316 -0
- data/test/other/events.rb +22 -21
- data/test/other/log.rb +14 -14
- data/test/other/metrics.rb +4 -8
- data/test/other/overrides.rb +5 -5
- data/test/other/relationships.rb +4 -2
- data/test/other/storage.rb +64 -3
- data/test/other/transactions.rb +20 -20
- data/test/parser/parser.rb +7 -4
- data/test/puppet/conffiles.rb +12 -12
- data/test/puppet/defaults.rb +13 -11
- data/test/puppet/utiltest.rb +14 -11
- data/test/puppettest.rb +156 -48
- data/test/server/bucket.rb +2 -2
- data/test/server/fileserver.rb +6 -6
- data/test/server/logger.rb +19 -11
- data/test/server/master.rb +33 -4
- data/test/server/server.rb +2 -7
- data/test/types/basic.rb +5 -7
- data/test/types/component.rb +22 -18
- data/test/types/cron.rb +111 -44
- data/test/types/exec.rb +116 -59
- data/test/types/file.rb +262 -137
- data/test/types/filebucket.rb +13 -15
- data/test/types/fileignoresource.rb +12 -16
- data/test/types/filesources.rb +73 -48
- data/test/types/filetype.rb +13 -15
- data/test/types/group.rb +15 -13
- data/test/types/host.rb +146 -0
- data/test/types/package.rb +74 -63
- data/test/types/port.rb +139 -0
- data/test/types/query.rb +8 -8
- data/test/types/schedule.rb +335 -0
- data/test/types/service.rb +137 -21
- data/test/types/sshkey.rb +140 -0
- data/test/types/symlink.rb +3 -5
- data/test/types/tidy.rb +5 -14
- data/test/types/type.rb +67 -11
- data/test/types/user.rb +25 -23
- metadata +186 -122
- data/lib/puppet/type/pfile/create.rb +0 -108
- data/lib/puppet/type/pprocess.rb +0 -97
- data/lib/puppet/type/typegen.rb +0 -149
- data/lib/puppet/type/typegen/filerecord.rb +0 -243
- data/lib/puppet/type/typegen/filetype.rb +0 -316
- data/test/other/state.rb +0 -106
data/lib/puppet/type/pfile.rb
CHANGED
@@ -6,264 +6,264 @@ require 'fileutils'
|
|
6
6
|
require 'puppet/type/state'
|
7
7
|
require 'puppet/server/fileserver'
|
8
8
|
|
9
|
-
# We put all of the states in separate files, because there are so many
|
10
|
-
# of them.
|
11
|
-
require 'puppet/type/pfile/type'
|
12
|
-
require 'puppet/type/pfile/create'
|
13
|
-
require 'puppet/type/pfile/checksum'
|
14
|
-
require 'puppet/type/pfile/uid'
|
15
|
-
require 'puppet/type/pfile/mode'
|
16
|
-
require 'puppet/type/pfile/group'
|
17
|
-
require 'puppet/type/pfile/source'
|
18
|
-
|
19
9
|
module Puppet
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
:ignore
|
41
|
-
]
|
42
|
-
|
43
|
-
@paramdoc[:path] = "The path to the file to manage. Must be fully
|
44
|
-
qualified."
|
45
|
-
|
46
|
-
@paramdoc[:backup] = "Whether files should be backed up before
|
47
|
-
being replaced. If a ``filebucket`` is specified, files will be
|
10
|
+
newtype(:file) do
|
11
|
+
@doc = "Manages local files, including setting ownership and
|
12
|
+
permissions, creation of both files and directories, and
|
13
|
+
retrieving entire files from remote servers. As Puppet matures, it
|
14
|
+
expected that the ``file`` element will be used less and less to
|
15
|
+
manage content, and instead native elements will be used to do so.
|
16
|
+
|
17
|
+
If you find that you are often copying files in from a central
|
18
|
+
location, rather than using native elements, please contact
|
19
|
+
Reductive Labs and we can hopefully work with you to develop a
|
20
|
+
native element to support what you are doing."
|
21
|
+
|
22
|
+
newparam(:path) do
|
23
|
+
desc "The path to the file to manage. Must be fully qualified."
|
24
|
+
isnamevar
|
25
|
+
end
|
26
|
+
|
27
|
+
newparam(:backup) do
|
28
|
+
desc "Whether files should be backed up before
|
29
|
+
being replaced. If a filebucket_ is specified, files will be
|
48
30
|
backed up there; else, they will be backed up in the same directory
|
49
31
|
with a ``.puppet-bak`` extension."
|
50
32
|
|
51
|
-
|
33
|
+
defaultto true
|
34
|
+
|
35
|
+
munge do |value|
|
36
|
+
case value
|
37
|
+
when false, "false":
|
38
|
+
false
|
39
|
+
when true, "true":
|
40
|
+
".puppet-bak"
|
41
|
+
when Array:
|
42
|
+
case value[0]
|
43
|
+
when "filebucket":
|
44
|
+
if bucket = Puppet.type(:filebucket).bucket(value[1])
|
45
|
+
bucket
|
46
|
+
else
|
47
|
+
self.fail "Could not retrieve filebucket %s" %
|
48
|
+
value[1]
|
49
|
+
end
|
50
|
+
else
|
51
|
+
self.fail "Invalid backup object type %s" %
|
52
|
+
value[0].inspect
|
53
|
+
end
|
54
|
+
else
|
55
|
+
self.fail "Invalid backup type %s" %
|
56
|
+
value.inspect
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
newparam(:linkmaker) do
|
62
|
+
desc "An internal parameter used by the *symlink*
|
52
63
|
type to do recursive link creation."
|
64
|
+
end
|
53
65
|
|
54
|
-
|
66
|
+
newparam(:recurse) do
|
67
|
+
desc "Whether and how deeply to do recursive
|
55
68
|
management. **false**/*true*/*inf*/*number*"
|
56
69
|
|
57
|
-
|
70
|
+
munge do |value|
|
71
|
+
value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
newparam(:ignore) do
|
76
|
+
desc "A parameter which omits action on files matching
|
58
77
|
specified patterns during recursion. Uses Ruby's builtin globbing
|
59
78
|
engine, so shell metacharacters are fully supported, e.g. ``[a-z]*``.
|
60
79
|
Matches that would descend into the directory structure are ignored,
|
61
80
|
e.g., ``*/*``."
|
81
|
+
|
82
|
+
defaultto false
|
62
83
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
84
|
+
validate do |value|
|
85
|
+
unless value.is_a?(Array) or value.is_a?(String) or value == false
|
86
|
+
self.devfail "Ignore must be a string or an Array"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
autorequire(:file) do
|
92
|
+
cur = []
|
93
|
+
# Skip the nil in the beginning and don't add ourselves as a prereq
|
94
|
+
# either.
|
95
|
+
self.name.split(File::SEPARATOR)[1..-2].collect { |dir|
|
96
|
+
cur << dir
|
97
|
+
"/" + cur.join(File::SEPARATOR)
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
validate do
|
102
|
+
if self[:content] and self[:source]
|
103
|
+
self.fail "You cannot specify both content and a source"
|
104
|
+
end
|
105
|
+
end
|
74
106
|
|
75
|
-
|
107
|
+
@depthfirst = false
|
76
108
|
|
77
|
-
PINPARAMS = [:mode, :type, :owner, :group, :checksum]
|
78
109
|
|
110
|
+
def argument?(arg)
|
111
|
+
@arghash.include?(arg)
|
112
|
+
end
|
79
113
|
|
80
|
-
|
81
|
-
|
114
|
+
def handlebackup(file = nil)
|
115
|
+
# let the path be specified
|
116
|
+
file ||= self[:path]
|
117
|
+
# if they specifically don't want a backup, then just say
|
118
|
+
# we're good
|
119
|
+
unless FileTest.exists?(file)
|
120
|
+
return true
|
82
121
|
end
|
83
122
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
# if they specifically don't want a backup, then just say
|
88
|
-
# we're good
|
89
|
-
unless FileTest.exists?(file)
|
90
|
-
return true
|
91
|
-
end
|
92
|
-
|
93
|
-
unless self[:backup]
|
94
|
-
return true
|
95
|
-
end
|
123
|
+
unless self[:backup]
|
124
|
+
return true
|
125
|
+
end
|
96
126
|
|
97
|
-
|
98
|
-
|
99
|
-
|
127
|
+
case File.stat(file).ftype
|
128
|
+
when "directory":
|
129
|
+
# we don't need to backup directories
|
130
|
+
return true
|
131
|
+
when "file":
|
132
|
+
backup = self[:backup]
|
133
|
+
case backup
|
134
|
+
when Puppet::Client::Dipper:
|
135
|
+
sum = backup.backup(file)
|
136
|
+
self.info "Filebucketed %s with sum %s" %
|
137
|
+
[file, sum]
|
100
138
|
return true
|
101
|
-
when
|
102
|
-
|
103
|
-
|
104
|
-
when Puppet::Client::Dipper:
|
105
|
-
sum = backup.backup(file)
|
106
|
-
self.info "Filebucketed %s with sum %s" %
|
107
|
-
[file, sum]
|
108
|
-
return true
|
109
|
-
when String:
|
110
|
-
newfile = file + backup
|
111
|
-
if FileTest.exists?(newfile)
|
112
|
-
begin
|
113
|
-
File.unlink(newfile)
|
114
|
-
rescue => detail
|
115
|
-
self.err "Could not remove old backup: %s" %
|
116
|
-
detail
|
117
|
-
return false
|
118
|
-
end
|
119
|
-
end
|
139
|
+
when String:
|
140
|
+
newfile = file + backup
|
141
|
+
if FileTest.exists?(newfile)
|
120
142
|
begin
|
121
|
-
|
122
|
-
file + backup)
|
123
|
-
return true
|
143
|
+
File.unlink(newfile)
|
124
144
|
rescue => detail
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
[file, detail.message])
|
145
|
+
self.err "Could not remove old backup: %s" %
|
146
|
+
detail
|
147
|
+
return false
|
129
148
|
end
|
130
|
-
|
131
|
-
|
132
|
-
|
149
|
+
end
|
150
|
+
begin
|
151
|
+
FileUtils.cp(file,
|
152
|
+
file + backup)
|
153
|
+
return true
|
154
|
+
rescue => detail
|
155
|
+
# since they said they want a backup, let's error out
|
156
|
+
# if we couldn't make one
|
157
|
+
self.fail "Could not back %s up: %s" %
|
158
|
+
[file, detail.message]
|
133
159
|
end
|
134
160
|
else
|
135
|
-
self.
|
136
|
-
File.stat(file).ftype
|
161
|
+
self.err "Invalid backup type %s" % backup
|
137
162
|
return false
|
138
163
|
end
|
164
|
+
else
|
165
|
+
self.notice "Cannot backup files of type %s" %
|
166
|
+
File.stat(file).ftype
|
167
|
+
return false
|
139
168
|
end
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
169
|
+
end
|
170
|
+
|
171
|
+
def handleignore(children)
|
172
|
+
return children unless self[:ignore]
|
173
|
+
self[:ignore].each { |ignore|
|
174
|
+
ignored = []
|
175
|
+
Dir.glob(File.join(self.name,ignore), File::FNM_DOTMATCH) { |match|
|
176
|
+
ignored.push(File.basename(match))
|
148
177
|
}
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@arghash.delete(self.class.namevar)
|
158
|
-
|
159
|
-
if @arghash.include?(:source)
|
160
|
-
@arghash.delete(:source)
|
161
|
-
end
|
162
|
-
|
163
|
-
@stat = nil
|
164
|
-
@parameters = Hash.new(false)
|
165
|
-
|
166
|
-
# default to true
|
167
|
-
self[:backup] = true
|
168
|
-
|
169
|
-
# Used for caching clients
|
170
|
-
@clients = {}
|
171
|
-
|
172
|
-
super
|
173
|
-
end
|
178
|
+
children = children - ignored
|
179
|
+
}
|
180
|
+
return children
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize(hash)
|
184
|
+
# clean out as many references to any file paths as possible
|
185
|
+
# this was the source of many, many bugs
|
174
186
|
|
175
|
-
|
176
|
-
|
177
|
-
when false, "false":
|
178
|
-
@parameters[:backup] = false
|
179
|
-
when true, "true":
|
180
|
-
@parameters[:backup] = ".puppet-bak"
|
181
|
-
when Array:
|
182
|
-
case value[0]
|
183
|
-
when "filebucket":
|
184
|
-
if bucket = Puppet::Type::PFileBucket.bucket(value[1])
|
185
|
-
@parameters[:backup] = bucket
|
186
|
-
else
|
187
|
-
@parameters[:backup] = ".puppet-bak"
|
188
|
-
raise Puppet::Error,
|
189
|
-
"Could not retrieve filebucket %s" %
|
190
|
-
value[1]
|
191
|
-
end
|
192
|
-
else
|
193
|
-
raise Puppet::Error, "Invalid backup object type %s" %
|
194
|
-
value[0].inspect
|
195
|
-
end
|
196
|
-
else
|
197
|
-
raise Puppet::Error, "Invalid backup type %s" %
|
198
|
-
value.inspect
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def paramignore=(value)
|
203
|
-
|
204
|
-
#Make sure the value of ignore is in correct type
|
205
|
-
unless value.is_a?(Array) or value.is_a?(String)
|
206
|
-
raise Puppet::DevError.new("Ignore must be a string or an Array")
|
207
|
-
end
|
208
|
-
|
209
|
-
@parameters[:ignore] = value
|
210
|
-
end
|
187
|
+
@arghash = self.argclean(hash)
|
188
|
+
@arghash.delete(self.class.namevar)
|
211
189
|
|
212
|
-
|
213
|
-
|
214
|
-
|
190
|
+
if @arghash.include?(:source)
|
191
|
+
@arghash.delete(:source)
|
192
|
+
end
|
215
193
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
194
|
+
@stat = nil
|
195
|
+
|
196
|
+
# Used for caching clients
|
197
|
+
@clients = {}
|
198
|
+
|
199
|
+
super
|
200
|
+
end
|
201
|
+
|
202
|
+
# Create a new file or directory object as a child to the current
|
203
|
+
# object.
|
204
|
+
def newchild(path, local, hash = {})
|
205
|
+
# make local copy of arguments
|
206
|
+
args = @arghash.dup
|
207
|
+
|
208
|
+
if path =~ %r{^#{File::SEPARATOR}}
|
209
|
+
self.devfail(
|
210
|
+
"Must pass relative paths to PFile#newchild()"
|
211
|
+
)
|
212
|
+
else
|
213
|
+
path = File.join(self.name, path)
|
214
|
+
end
|
223
215
|
|
224
|
-
|
216
|
+
args[:path] = path
|
225
217
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
args[:recurse] -= 1 # reduce the level of recursion
|
231
|
-
end
|
218
|
+
unless hash.include?(:recurse)
|
219
|
+
if args.include?(:recurse)
|
220
|
+
if args[:recurse].is_a?(Integer)
|
221
|
+
args[:recurse] -= 1 # reduce the level of recursion
|
232
222
|
end
|
233
|
-
|
234
223
|
end
|
235
224
|
|
236
|
-
|
237
|
-
|
225
|
+
end
|
226
|
+
|
227
|
+
hash.each { |key,value|
|
228
|
+
args[key] = value
|
229
|
+
}
|
230
|
+
|
231
|
+
child = nil
|
232
|
+
klass = nil
|
233
|
+
|
234
|
+
# We specifically look in @parameters here, because 'linkmaker' isn't
|
235
|
+
# a valid attribute for subclasses, so using 'self[:linkmaker]' throws
|
236
|
+
# an error.
|
237
|
+
if @parameters.include?(:linkmaker) and
|
238
|
+
args.include?(:source) and ! FileTest.directory?(args[:source])
|
239
|
+
klass = Puppet.type(:symlink)
|
240
|
+
|
241
|
+
self.debug "%s is a link" % path
|
242
|
+
# clean up the args a lot for links
|
243
|
+
old = args.dup
|
244
|
+
args = {
|
245
|
+
:target => old[:source],
|
246
|
+
:path => path
|
238
247
|
}
|
248
|
+
else
|
249
|
+
klass = self.class
|
250
|
+
end
|
239
251
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
self.debug "
|
247
|
-
|
248
|
-
|
249
|
-
args = {
|
250
|
-
:target => old[:source],
|
251
|
-
:path => path
|
252
|
-
}
|
253
|
-
else
|
254
|
-
klass = self.class
|
252
|
+
# The child might already exist because 'localrecurse' runs
|
253
|
+
# before 'sourcerecurse'. I could push the override stuff into
|
254
|
+
# a separate method or something, but the work is the same other
|
255
|
+
# than this last bit, so it doesn't really make sense.
|
256
|
+
if child = klass[path]
|
257
|
+
unless @children.include?(child)
|
258
|
+
self.debug "Not managing more explicit file %s" %
|
259
|
+
path
|
260
|
+
return nil
|
255
261
|
end
|
256
262
|
|
257
|
-
#
|
258
|
-
#
|
259
|
-
#
|
260
|
-
|
261
|
-
if child = klass[path]
|
262
|
-
unless @children.include?(child)
|
263
|
-
self.notice "Not managing more explicit file %s" %
|
264
|
-
path
|
265
|
-
return nil
|
266
|
-
end
|
263
|
+
# This is only necessary for sourcerecurse, because we might have
|
264
|
+
# created the object with different 'should' values than are
|
265
|
+
# set remotely.
|
266
|
+
unless local
|
267
267
|
args.each { |var,value|
|
268
268
|
next if var == :path
|
269
269
|
next if var == :name
|
@@ -272,298 +272,322 @@ module Puppet
|
|
272
272
|
child[var] = value
|
273
273
|
end
|
274
274
|
}
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
@children << child
|
286
|
-
rescue Puppet::Error => detail
|
287
|
-
self.notice(
|
288
|
-
"Cannot manage: %s" %
|
289
|
-
[detail.message]
|
290
|
-
)
|
291
|
-
self.debug args.inspect
|
292
|
-
child = nil
|
293
|
-
rescue => detail
|
294
|
-
self.notice(
|
295
|
-
"Cannot manage: %s" %
|
296
|
-
[detail]
|
297
|
-
)
|
298
|
-
self.debug args.inspect
|
299
|
-
child = nil
|
275
|
+
end
|
276
|
+
else # create it anew
|
277
|
+
#notice "Creating new file with args %s" % args.inspect
|
278
|
+
args[:parent] = self
|
279
|
+
begin
|
280
|
+
child = klass.implicitcreate(args)
|
281
|
+
|
282
|
+
# implicit creation can return nil
|
283
|
+
if child.nil?
|
284
|
+
return nil
|
300
285
|
end
|
286
|
+
@children << child
|
287
|
+
rescue Puppet::Error => detail
|
288
|
+
self.notice(
|
289
|
+
"Cannot manage: %s" %
|
290
|
+
[detail.message]
|
291
|
+
)
|
292
|
+
self.debug args.inspect
|
293
|
+
child = nil
|
294
|
+
rescue => detail
|
295
|
+
self.notice(
|
296
|
+
"Cannot manage: %s" %
|
297
|
+
[detail]
|
298
|
+
)
|
299
|
+
self.debug args.inspect
|
300
|
+
child = nil
|
301
301
|
end
|
302
|
-
return child
|
303
302
|
end
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
@path <<
|
320
|
-
else
|
321
|
-
super
|
303
|
+
return child
|
304
|
+
end
|
305
|
+
|
306
|
+
# Paths are special for files, because we don't actually want to show
|
307
|
+
# the parent's full path.
|
308
|
+
def path
|
309
|
+
unless defined? @path
|
310
|
+
if defined? @parent
|
311
|
+
# We only need to behave specially when our parent is also
|
312
|
+
# a file
|
313
|
+
if @parent.is_a?(self.class)
|
314
|
+
# Remove the parent file name
|
315
|
+
ppath = @parent.path.sub(/\/?file=.+/, '')
|
316
|
+
@path = []
|
317
|
+
if ppath != "/" and ppath != ""
|
318
|
+
@path << ppath
|
322
319
|
end
|
320
|
+
@path << self.class.name.to_s + "=" + self.name
|
323
321
|
else
|
324
|
-
|
325
|
-
# bother with that. And we don't add the hostname
|
326
|
-
# here, it gets added in the log server thingy.
|
327
|
-
if self.name == "puppet[top]"
|
328
|
-
@path = ["/"]
|
329
|
-
else
|
330
|
-
# We assume that if we don't have a parent that we
|
331
|
-
# should not cache the path
|
332
|
-
@path = [self.class.name.to_s + "=" + self.name]
|
333
|
-
end
|
322
|
+
super
|
334
323
|
end
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
if recurse.is_a?(String)
|
346
|
-
if recurse =~ /^[0-9]+$/
|
347
|
-
recurse = Integer(recurse)
|
348
|
-
#elsif recurse =~ /^inf/ # infinite recursion
|
349
|
-
else # anything else is infinite recursion
|
350
|
-
recurse = true
|
324
|
+
else
|
325
|
+
# The top-level name is always puppet[top], so we don't
|
326
|
+
# bother with that. And we don't add the hostname
|
327
|
+
# here, it gets added in the log server thingy.
|
328
|
+
if self.name == "puppet[top]"
|
329
|
+
@path = ["/"]
|
330
|
+
else
|
331
|
+
# We assume that if we don't have a parent that we
|
332
|
+
# should not cache the path
|
333
|
+
@path = [self.class.name.to_s + "=" + self.name]
|
351
334
|
end
|
352
335
|
end
|
336
|
+
end
|
353
337
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
338
|
+
return @path.join("/")
|
339
|
+
end
|
340
|
+
|
341
|
+
# Recurse into the directory. This basically just calls 'localrecurse'
|
342
|
+
# and maybe 'sourcerecurse'.
|
343
|
+
def recurse
|
344
|
+
recurse = self[:recurse]
|
345
|
+
# we might have a string, rather than a number
|
346
|
+
if recurse.is_a?(String)
|
347
|
+
if recurse =~ /^[0-9]+$/
|
348
|
+
recurse = Integer(recurse)
|
349
|
+
#elsif recurse =~ /^inf/ # infinite recursion
|
350
|
+
else # anything else is infinite recursion
|
351
|
+
recurse = true
|
358
352
|
end
|
353
|
+
end
|
359
354
|
|
360
|
-
|
361
|
-
|
362
|
-
|
355
|
+
# are we at the end of the recursion?
|
356
|
+
if recurse == 0
|
357
|
+
self.info "finished recursing"
|
358
|
+
return
|
359
|
+
end
|
363
360
|
|
364
|
-
|
365
|
-
|
366
|
-
self.sourcerecurse(recurse)
|
367
|
-
end
|
361
|
+
if recurse.is_a?(Integer)
|
362
|
+
recurse -= 1
|
368
363
|
end
|
369
364
|
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
end
|
365
|
+
self.localrecurse(recurse)
|
366
|
+
if @states.include?(:source)
|
367
|
+
self.sourcerecurse(recurse)
|
368
|
+
end
|
369
|
+
end
|
376
370
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
self.notice "Cannot manage %s: permission denied" % self.name
|
384
|
-
return
|
385
|
-
end
|
371
|
+
def localrecurse(recurse)
|
372
|
+
unless FileTest.exist?(self.name) and self.stat.directory?
|
373
|
+
#self.info "%s is not a directory; not recursing" %
|
374
|
+
# self.name
|
375
|
+
return
|
376
|
+
end
|
386
377
|
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
children = handleignore(children)
|
392
|
-
end
|
393
|
-
|
394
|
-
added = []
|
395
|
-
children.each { |file|
|
396
|
-
file = File.basename(file)
|
397
|
-
next if file =~ /^\.\.?$/ # skip . and ..
|
398
|
-
if child = self.newchild(file, :recurse => recurse)
|
399
|
-
unless @children.include?(child)
|
400
|
-
self.push child
|
401
|
-
added.push file
|
378
|
+
unless FileTest.readable? self.name
|
379
|
+
self.notice "Cannot manage %s: permission denied" % self.name
|
380
|
+
return
|
381
|
+
end
|
402
382
|
|
403
|
-
|
404
|
-
|
383
|
+
children = Dir.entries(self.name)
|
384
|
+
|
385
|
+
#Get rid of ignored children
|
386
|
+
if @parameters.include?(:ignore)
|
387
|
+
children = handleignore(children)
|
388
|
+
end
|
389
|
+
|
390
|
+
added = []
|
391
|
+
children.each { |file|
|
392
|
+
file = File.basename(file)
|
393
|
+
next if file =~ /^\.\.?$/ # skip . and ..
|
394
|
+
if child = self.newchild(file, true, :recurse => recurse)
|
395
|
+
unless @children.include?(child)
|
396
|
+
self.push child
|
397
|
+
added.push file
|
405
398
|
end
|
406
|
-
|
399
|
+
end
|
400
|
+
}
|
401
|
+
end
|
402
|
+
|
403
|
+
# This recurses against the remote source and makes sure the local
|
404
|
+
# and remote structures match. It's run after 'localrecurse'.
|
405
|
+
def sourcerecurse(recurse)
|
406
|
+
# FIXME sourcerecurse should support purging non-remote files
|
407
|
+
source = @states[:source].source
|
408
|
+
|
409
|
+
unless ! source.nil? and source !~ /^\s*$/
|
410
|
+
self.notice "source %s does not exist" % @states[:source].should
|
411
|
+
return nil
|
412
|
+
end
|
413
|
+
|
414
|
+
sourceobj, path = uri2obj(source)
|
415
|
+
|
416
|
+
# we'll set this manually as necessary
|
417
|
+
if @arghash.include?(:ensure)
|
418
|
+
@arghash.delete(:ensure)
|
407
419
|
end
|
408
420
|
|
409
|
-
#
|
410
|
-
#
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
421
|
+
# okay, we've got our source object; now we need to
|
422
|
+
# build up a local file structure to match the remote
|
423
|
+
# one
|
424
|
+
|
425
|
+
server = sourceobj.server
|
426
|
+
sum = "md5"
|
427
|
+
if state = self.state(:checksum)
|
428
|
+
sum = state.checktype
|
429
|
+
end
|
430
|
+
r = false
|
431
|
+
if recurse
|
432
|
+
unless recurse == 0
|
433
|
+
r = 1
|
420
434
|
end
|
435
|
+
end
|
421
436
|
|
422
|
-
|
423
|
-
# build up a local file structure to match the remote
|
424
|
-
# one
|
437
|
+
ignore = self[:ignore]
|
425
438
|
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
439
|
+
#self.warning "Listing path %s" % path.inspect
|
440
|
+
desc = server.list(path, r, ignore)
|
441
|
+
|
442
|
+
desc.split("\n").each { |line|
|
443
|
+
file, type = line.split("\t")
|
444
|
+
next if file == "/"
|
445
|
+
name = file.sub(/^\//, '')
|
446
|
+
#self.warning "child name is %s" % name
|
447
|
+
args = {:source => source + file}
|
448
|
+
if type == file
|
449
|
+
args[:recurse] = nil
|
430
450
|
end
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
451
|
+
self.newchild(name, false, args)
|
452
|
+
#self.newchild(hash, source, recurse)
|
453
|
+
#hash2child(hash, source, recurse)
|
454
|
+
}
|
455
|
+
end
|
456
|
+
|
457
|
+
# a wrapper method to make sure the file exists before doing anything
|
458
|
+
def retrieve
|
459
|
+
if @states.include?(:source)
|
460
|
+
# This probably isn't the best place for it, but we need
|
461
|
+
# to make sure that we have a corresponding checksum state.
|
462
|
+
unless @states.include?(:checksum)
|
463
|
+
self[:checksum] = "md5"
|
436
464
|
end
|
465
|
+
@states[:source].retrieve
|
466
|
+
end
|
437
467
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
#
|
448
|
-
|
449
|
-
|
450
|
-
args[:recurse] = nil
|
451
|
-
end
|
452
|
-
self.newchild(name, args)
|
453
|
-
#self.newchild(hash, source, recurse)
|
454
|
-
#hash2child(hash, source, recurse)
|
468
|
+
if @parameters.include?(:recurse)
|
469
|
+
self.recurse
|
470
|
+
end
|
471
|
+
|
472
|
+
unless stat = self.stat(true)
|
473
|
+
self.debug "File does not exist"
|
474
|
+
@states.each { |name,state|
|
475
|
+
# We've already retreived the source, and we don't
|
476
|
+
# want to overwrite whatever it did. This is a bit
|
477
|
+
# of a hack, but oh well, source is definitely special.
|
478
|
+
next if name == :source
|
479
|
+
state.is = :absent
|
455
480
|
}
|
481
|
+
return
|
456
482
|
end
|
457
483
|
|
458
|
-
|
459
|
-
|
460
|
-
if @states.include?(:source)
|
461
|
-
# This probably isn't the best place for it, but we need
|
462
|
-
# to make sure that we have a corresponding checksum state.
|
463
|
-
unless @states.include?(:checksum)
|
464
|
-
self[:checksum] = "md5"
|
465
|
-
end
|
466
|
-
@states[:source].retrieve
|
467
|
-
end
|
484
|
+
super
|
485
|
+
end
|
468
486
|
|
469
|
-
|
470
|
-
|
487
|
+
# Set the checksum, from another state. There are multiple states that
|
488
|
+
# modify the contents of a file, and they need the ability to make sure
|
489
|
+
# that the checksum value is in sync.
|
490
|
+
def setchecksum(sum = nil)
|
491
|
+
if @states.include? :checksum
|
492
|
+
if sum
|
493
|
+
@states[:checksum].checksum = sum
|
494
|
+
else
|
495
|
+
# If they didn't pass in a sum, then tell checksum to
|
496
|
+
# figure it out.
|
497
|
+
@states[:checksum].retrieve
|
498
|
+
@states[:checksum].checksum = @states[:checksum].is
|
471
499
|
end
|
500
|
+
end
|
501
|
+
end
|
472
502
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
503
|
+
def stat(refresh = false)
|
504
|
+
if @stat.nil? or refresh == true
|
505
|
+
begin
|
506
|
+
@stat = File.lstat(self.name)
|
507
|
+
rescue Errno::ENOENT => error
|
508
|
+
@stat = nil
|
509
|
+
rescue => error
|
510
|
+
self.debug "Failed to stat %s: %s" %
|
511
|
+
[self.name,error]
|
512
|
+
@stat = nil
|
483
513
|
end
|
484
|
-
|
485
|
-
super
|
486
514
|
end
|
487
515
|
|
488
|
-
|
489
|
-
|
490
|
-
begin
|
491
|
-
@stat = File.lstat(self.name)
|
492
|
-
rescue Errno::ENOENT => error
|
493
|
-
@stat = nil
|
494
|
-
rescue => error
|
495
|
-
self.debug "Failed to stat %s: %s" %
|
496
|
-
[self.name,error]
|
497
|
-
@stat = nil
|
498
|
-
end
|
499
|
-
end
|
516
|
+
return @stat
|
517
|
+
end
|
500
518
|
|
501
|
-
|
519
|
+
def uri2obj(source)
|
520
|
+
sourceobj = FileSource.new
|
521
|
+
path = nil
|
522
|
+
if source =~ /^\//
|
523
|
+
source = "file://localhost/%s" % source
|
524
|
+
sourceobj.mount = "localhost"
|
525
|
+
sourceobj.local = true
|
526
|
+
end
|
527
|
+
begin
|
528
|
+
uri = URI.parse(source)
|
529
|
+
rescue => detail
|
530
|
+
self.fail "Could not understand source %s: %s" %
|
531
|
+
[source, detail.to_s]
|
502
532
|
end
|
503
533
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
534
|
+
case uri.scheme
|
535
|
+
when "file":
|
536
|
+
unless defined? @@localfileserver
|
537
|
+
@@localfileserver = Puppet::Server::FileServer.new(
|
538
|
+
:Local => true,
|
539
|
+
:Mount => { "/" => "localhost" },
|
540
|
+
:Config => false
|
541
|
+
)
|
542
|
+
#@@localfileserver.mount("/", "localhost")
|
511
543
|
end
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
544
|
+
sourceobj.server = @@localfileserver
|
545
|
+
path = "/localhost" + uri.path
|
546
|
+
when "puppet":
|
547
|
+
args = { :Server => uri.host }
|
548
|
+
if uri.port
|
549
|
+
args[:Port] = uri.port
|
517
550
|
end
|
551
|
+
# FIXME We should cache a copy of this server
|
552
|
+
#sourceobj.server = Puppet::NetworkClient.new(args)
|
553
|
+
unless @clients.include?(source)
|
554
|
+
@clients[source] = Puppet::Client::FileClient.new(args)
|
555
|
+
end
|
556
|
+
sourceobj.server = @clients[source]
|
518
557
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
:Mount => { "/" => "localhost" },
|
525
|
-
:Config => false
|
526
|
-
)
|
527
|
-
#@@localfileserver.mount("/", "localhost")
|
528
|
-
end
|
529
|
-
sourceobj.server = @@localfileserver
|
530
|
-
path = "/localhost" + uri.path
|
531
|
-
when "puppet":
|
532
|
-
args = { :Server => uri.host }
|
533
|
-
if uri.port
|
534
|
-
args[:Port] = uri.port
|
535
|
-
end
|
536
|
-
# FIXME We should cache a copy of this server
|
537
|
-
#sourceobj.server = Puppet::NetworkClient.new(args)
|
538
|
-
unless @clients.include?(source)
|
539
|
-
@clients[source] = Puppet::Client::FileClient.new(args)
|
540
|
-
end
|
541
|
-
sourceobj.server = @clients[source]
|
542
|
-
|
543
|
-
tmp = uri.path
|
544
|
-
if tmp =~ %r{^/(\w+)}
|
545
|
-
sourceobj.mount = $1
|
546
|
-
path = tmp
|
547
|
-
#path = tmp.sub(%r{^/\w+},'') || "/"
|
548
|
-
else
|
549
|
-
raise Puppet::Error, "Invalid source path %s" % tmp
|
550
|
-
end
|
558
|
+
tmp = uri.path
|
559
|
+
if tmp =~ %r{^/(\w+)}
|
560
|
+
sourceobj.mount = $1
|
561
|
+
path = tmp
|
562
|
+
#path = tmp.sub(%r{^/\w+},'') || "/"
|
551
563
|
else
|
552
|
-
|
553
|
-
"Got other recursive file proto %s from %s" %
|
554
|
-
[uri.scheme, source]
|
564
|
+
self.fail "Invalid source path %s" % tmp
|
555
565
|
end
|
556
|
-
|
557
|
-
|
566
|
+
else
|
567
|
+
self.fail "Got other recursive file proto %s from %s" %
|
568
|
+
[uri.scheme, source]
|
558
569
|
end
|
559
|
-
|
560
|
-
|
570
|
+
|
571
|
+
return [sourceobj, path.sub(/\/\//, '/')]
|
572
|
+
end
|
573
|
+
end # Puppet.type(:pfile)
|
561
574
|
|
562
575
|
# the filesource class can't include the path, because the path
|
563
576
|
# changes for every file instance
|
564
577
|
class FileSource
|
565
578
|
attr_accessor :mount, :root, :server, :local
|
566
579
|
end
|
567
|
-
end
|
568
580
|
|
569
|
-
#
|
581
|
+
# We put all of the states in separate files, because there are so many
|
582
|
+
# of them. The order these are loaded is important, because it determines
|
583
|
+
# the order they are in the state list.
|
584
|
+
require 'puppet/type/pfile/checksum'
|
585
|
+
require 'puppet/type/pfile/content' # can create the file
|
586
|
+
require 'puppet/type/pfile/source' # can create the file
|
587
|
+
require 'puppet/type/pfile/ensure' # can create the file
|
588
|
+
require 'puppet/type/pfile/uid'
|
589
|
+
require 'puppet/type/pfile/group'
|
590
|
+
require 'puppet/type/pfile/mode'
|
591
|
+
require 'puppet/type/pfile/type'
|
592
|
+
end
|
593
|
+
# $Id: pfile.rb 883 2006-02-08 16:53:34Z luke $
|