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
|
require 'puppet/provider/parsedfile'
services = nil
case Facter.value(:operatingsystem)
when "Solaris": services = "/etc/inet/services"
else
services = "/etc/services"
end
Puppet::Type.type(:port).provide(:parsed,
:parent => Puppet::Provider::ParsedFile,
:default_target => services,
:filetype => :flat
) do
text_line :comment, :match => /^\s*#/
text_line :blank, :match => /^\s*$/
# We're cheating horribly here -- we don't support ddp, because it assigns
# the same number to already-used names, and the same name to different
# numbers.
text_line :ddp, :match => /^\S+\s+\d+\/ddp/
# Also, just ignore the lines on OS X that don't have service names.
text_line :funky_darwin, :match => /^\s+\d+\//
# We have to manually parse the line, since it's so darn complicated.
record_line :parsed, :fields => %w{name port protocols alias} do |line|
if line =~ /\/ddp/
raise "missed ddp in %s" % line
end
# The record might contain multiple port lines separated by \n.
hashes = line.split("\n").collect { |l| parse_port(l) }
# It's easy if there's just one hash.
if hashes.length == 1
return hashes.shift
end
# Else, merge the two records into one.
return port_merge(*hashes)
end
# Override how we split into lines, so that we always treat both protocol
# lines as a single line. This drastically simplifies merging the two lines
# into one record.
def self.lines(text)
names = {}
lines = []
# We organize by number, because that's apparently how the ports work.
# You'll never be able to use Puppet to manage multiple entries
# with the same name but different numbers, though.
text.split("\n").each do |line|
if line =~ /^([-\w]+)\s+(\d+)\/[^d]/ # We want to skip ddp proto stuff
names[$1] ||= []
names[$1] << line
lines << [:special, $1]
else
lines << line
end
end
# Now, return each line in order, but join the ones with the same name
lines.collect do |line|
if line.is_a?(Array)
name = line[1]
if names[name]
t = names[name].join("\n")
names.delete(name)
t
end
else
line
end
end.reject { |l| l.nil? }
end
# Parse a single port line, returning a hash.
def self.parse_port(line)
hash = {}
if line.sub!(/^(\S+)\s+(\d+)\/(\w+)\s*/, '')
hash[:name] = $1
hash[:number] = $2
hash[:protocols] = [$3]
unless line == ""
line.sub!(/^([^#]+)\s*/) do |value|
aliases = $1
# Remove any trailing whitespace
aliases.strip!
unless aliases =~ /^\s*$/
hash[:alias] = aliases.split(/\s+/)
end
""
end
line.sub!(/^\s*#\s*(.+)$/) do |value|
desc = $1
unless desc =~ /^\s*$/
hash[:description] = desc.sub(/\s*$/, '')
end
""
end
end
else
if line =~ /^\s+\d+/ and
Facter["operatingsystem"].value == "Darwin"
#Puppet.notice "Skipping wonky OS X port entry %s" %
# line.inspect
next
end
Puppet.notice "Ignoring unparseable line '%s' in %s" % [line, self.target]
end
if hash.empty?
return nil
else
return hash
end
end
# Merge two records into one.
def self.port_merge(one, two)
keys = [one.keys, two.keys].flatten.uniq
# We'll be returning the 'one' hash. so make any necessary modifications
# to it.
keys.each do |key|
# The easy case
if one[key] == two[key]
next
elsif one[key] and ! two[key]
next
elsif ! one[key] and two[key]
one[key] = two[key]
elsif one[key].is_a?(Array) and two[key].is_a?(Array)
one[key] = [one[key], two[key]].flatten.uniq
else
# Keep the info from the first hash, so don't do anything
#Puppet.notice "Cannot merge %s in %s with %s" %
# [key, one.inspect, two.inspect]
end
end
return one
end
# Convert the current object into one or more services entry.
def self.to_line(hash)
unless hash[:record_type] == :parsed
return super
end
# Strangely, most sites seem to use tabs as separators.
hash[:protocols].collect { |proto|
str = "%s\t\t%s/%s" % [hash[:name], hash[:number], proto]
if value = hash[:alias] and value != :absent
str += "\t\t%s" % value.join(" ")
end
if value = hash[:description] and value != :absent
str += "\t# %s" % value
end
str
}.join("\n")
end
end
# $Id$
|