summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/handler/node.rb
blob: 898db7c22771a891fea5324eb4c76c34828c294a (plain)
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
#  Created by Luke A. Kanies on 2007-08-13.
#  Copyright (c) 2007. All rights reserved.

require 'puppet/util'
require 'puppet/util/classgen'
require 'puppet/util/instance_loader'

# Look up a node, along with all the details about it.
class Puppet::Network::Handler::Node < Puppet::Network::Handler
    # A simplistic class for managing the node information itself.
    class SimpleNode
        attr_accessor :name, :classes, :parameters, :environment, :source, :ipaddress

        def initialize(name, options = {})
            @name = name

            if classes = options[:classes]
                if classes.is_a?(String)
                    @classes = [classes]
                else
                    @classes = classes
                end
            else
                @classes = []
            end

            @parameters = options[:parameters] || {}

            unless @environment = options[:environment] 
                if env = Puppet[:environment] and env != ""
                    @environment = env
                end
            end
        end

        # Merge the node facts with parameters from the node source.
        # This is only called if the node source has 'fact_merge' set to true.
        def fact_merge(facts)
            facts.each do |name, value|
                @parameters[name] = value unless @parameters.include?(name)
            end
        end
    end

    desc "Retrieve information about nodes."

    extend Puppet::Util::ClassGen
    extend Puppet::Util::InstanceLoader

    # A simple base module we can use for modifying how our node sources work.
    module SourceBase
        include Puppet::Util::Docs
    end

    @interface = XMLRPC::Service::Interface.new("nodes") { |iface|
        iface.add_method("string details(key)")
        iface.add_method("string parameters(key)")
        iface.add_method("string environment(key)")
        iface.add_method("string classes(key)")
    }

    # Set up autoloading and retrieving of reports.
    autoload :node_source, 'puppet/node_source'

    attr_reader :source

    # Add a new node source.
    def self.newnode_source(name, options = {}, &block)
        name = symbolize(name)

        fact_merge = options[:fact_merge]
        mod = genmodule(name, :extend => SourceBase, :hash => instance_hash(:node_source), :block => block)
        mod.send(:define_method, :fact_merge?) do
            fact_merge
        end
        mod
    end

    # Collect the docs for all of our node sources.
    def self.node_source_docs
        docs = ""

        # Use this method so they all get loaded
        instance_loader(:node_source).loadall
        loaded_instances(:node_source).sort { |a,b| a.to_s <=> b.to_s }.each do |name|
            mod = self.node_source(name)
            docs += "%s\n%s\n" % [name, "-" * name.to_s.length]

            docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n"
        end

        docs
    end

    # List each of the node sources.
    def self.node_sources
        instance_loader(:node_source).loadall
        loaded_instances(:node_source)
    end

    # Remove a defined node source; basically only used for testing.
    def self.rm_node_source(name)
        rmclass(name, :hash => instance_hash(:node_source))
    end

    # Return a given node's classes.
    def classes(key)
        if node = details(key)
            node.classes
        else
            nil
        end
    end

    # Return an entire node configuration.  This uses the 'nodesearch' method
    # defined in the node_source to look for the node.
    def details(key, client = nil, clientip = nil)
        facts = node_facts(key)
        node = nil
        names = node_names(key, facts)
        names.each do |name|
            name = name.to_s if name.is_a?(Symbol)
            if node = nodesearch(name)
                Puppet.info "Found %s in %s" % [name, @source]
                break
            end
        end

        # If they made it this far, we haven't found anything, so look for a
        # default node.
        unless node or names.include?("default")
            if node = nodesearch("default")
                Puppet.notice "Using default node for %s" % key
            end
        end

        if node
            node.source = @source

            # Merge the facts into the parameters.
            if fact_merge?
                node.fact_merge(facts)
            end
            return node
        else
            return nil
        end
    end

    # Return a given node's environment.
    def environment(key, client = nil, clientip = nil)
        if node = details(key)
            node.environment
        else
            nil
        end
    end

    # Create our node lookup tool.
    def initialize(hash = {})
        @source = hash[:Source] || Puppet[:node_source]

        unless mod = self.class.node_source(@source)
            raise ArgumentError, "Unknown node source '%s'" % @source
        end

        extend(mod)

        super
    end

    # Try to retrieve a given node's parameters.
    def parameters(key, client = nil, clientip = nil)
        if node = details(key)
            node.parameters
        else
            nil
        end
    end

    private

    # Create/cache a fact handler.
    def fact_handler
        unless defined?(@fact_handler)
            @fact_handler = Puppet::Network::Handler.handler(:facts).new
        end
        @fact_handler
    end

    # Short-hand for creating a new node, so the node sources don't need to
    # specify the constant.
    def newnode(options)
        SimpleNode.new(options)
    end

    # Look up the node facts from our fact handler.
    def node_facts(key)
        if facts = fact_handler.get(key)
            facts
        else
            {}
        end
    end

    # Calculate the list of node names we should use for looking
    # up our node.
    def node_names(key, facts = nil)
        facts ||= node_facts(key)
        names = []

        if hostname = facts["hostname"]
            unless hostname == key
                names << hostname
            end
        else
            hostname = key
        end

        if fqdn = facts["fqdn"]
            hostname = fqdn
            names << fqdn
        end

        # Make sure both the fqdn and the short name of the
        # host can be used in the manifest
        if hostname =~ /\./
            names << hostname.sub(/\..+/,'')
        elsif domain = facts['domain']
            names << hostname + "." + domain
        end

        # Sort the names inversely by name length.
        names.sort! { |a,b| b.length <=> a.length }

        # And make sure the key is first, since that's the most
        # likely usage.
        ([key] + names).uniq
    end
end