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
|
require 'puppet/interface'
module Puppet::Interface::FaceCollection
@faces = Hash.new { |hash, key| hash[key] = {} }
def self.faces
unless @loaded
@loaded = true
$LOAD_PATH.each do |dir|
Dir.glob("#{dir}/puppet/face/*.rb").
collect {|f| File.basename(f, '.rb') }.
each {|name| self[name, :current] }
end
end
@faces.keys.select {|name| @faces[name].length > 0 }
end
def self.[](name, version)
name = underscorize(name)
get_face(name, version) or load_face(name, version)
end
def self.get_action_for_face(name, action_name, version)
name = underscorize(name)
# If the version they request specifically doesn't exist, don't search
# elsewhere. Usually this will start from :current and all...
return nil unless face = self[name, version]
unless action = face.get_action(action_name)
# ...we need to search for it bound to an o{lder,ther} version. Since
# we load all actions when the face is first references, this will be in
# memory in the known set of versions of the face.
(@faces[name].keys - [ :current ]).sort.reverse.each do |version|
break if action = @faces[name][version].get_action(action_name)
end
end
return action
end
# get face from memory, without loading.
def self.get_face(name, pattern)
return nil unless @faces.has_key? name
return @faces[name][:current] if pattern == :current
versions = @faces[name].keys - [ :current ]
found = SemVer.find_matching(pattern, versions)
return @faces[name][found]
end
# try to load the face, and return it.
def self.load_face(name, version)
# We always load the current version file; the common case is that we have
# the expected version and any compatibility versions in the same file,
# the default. Which means that this is almost always the case.
#
# We use require to avoid executing the code multiple times, like any
# other Ruby library that we might want to use. --daniel 2011-04-06
if safely_require name then
# If we wanted :current, we need to index to find that; direct version
# requests just work™ as they go. --daniel 2011-04-06
if version == :current then
# We need to find current out of this. This is the largest version
# number that doesn't have a dedicated on-disk file present; those
# represent "experimental" versions of faces, which we don't fully
# support yet.
#
# We walk the versions from highest to lowest and take the first version
# that is not defined in an explicitly versioned file on disk as the
# current version.
#
# This constrains us to only ship experimental versions with *one*
# version in the file, not multiple, but given you can't reliably load
# them except by side-effect when you ignore that rule this seems safe
# enough...
#
# Given those constraints, and that we are not going to ship a versioned
# interface that is not :current in this release, we are going to leave
# these thoughts in place, and just punt on the actual versioning.
#
# When we upgrade the core to support multiple versions we can solve the
# problems then; as lazy as possible.
#
# We do support multiple versions in the same file, though, so we sort
# versions here and return the last item in that set.
#
# --daniel 2011-04-06
latest_ver = @faces[name].keys.sort.last
@faces[name][:current] = @faces[name][latest_ver]
end
end
unless version == :current or get_face(name, version)
# Try an obsolete version of the face, if needed, to see if that helps?
safely_require name, version
end
return get_face(name, version)
end
def self.safely_require(name, version = nil)
path = File.join 'puppet' ,'face', version.to_s, name.to_s
require path
true
rescue LoadError => e
raise unless e.message =~ %r{-- #{path}$}
# ...guess we didn't find the file; return a much better problem.
nil
rescue SyntaxError => e
raise unless e.message =~ %r{#{path}\.rb:\d+: }
Puppet.err "Failed to load face #{name}:\n#{e}"
# ...but we just carry on after complaining.
nil
end
def self.register(face)
@faces[underscorize(face.name)][face.version] = face
end
def self.underscorize(name)
unless name.to_s =~ /^[-_a-z]+$/i then
raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid face name"
end
name.to_s.downcase.split(/[-_]/).join('_').to_sym
end
end
|