summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network/handler/master.rb
blob: 963b7841a5af4c284c0e47a8664b2ebfac74fc24 (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
require 'openssl'
require 'puppet'
require 'puppet/parser/interpreter'
require 'puppet/sslcertificates'
require 'xmlrpc/server'
require 'yaml'

class Puppet::Network::Handler
    class MasterError < Puppet::Error; end
    class Master < Handler
        desc "Puppet's configuration interface.  Used for all interactions related to
        generating client configurations."

        include Puppet::Util

        attr_accessor :ast, :local
        attr_reader :ca

        @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface|
                iface.add_method("string getconfig(string)")
                iface.add_method("int freshness()")
        }

        # FIXME At some point, this should be autodocumenting.
        def addfacts(facts)
            # Add our server version to the fact list
            facts["serverversion"] = Puppet.version.to_s

            # And then add the server name and IP
            {"servername" => "fqdn",
                "serverip" => "ipaddress"
            }.each do |var, fact|
                if obj = Facter[fact]
                    facts[var] = obj.value
                else
                    Puppet.warning "Could not retrieve fact %s" % fact
                end
            end

            if facts["servername"].nil?
                host = Facter.value(:hostname)
                if domain = Facter.value(:domain)
                    facts["servername"] = [host, domain].join(".")
                else
                    facts["servername"] = host
                end
            end
        end

        # Manipulate the client name as appropriate.
        def clientname(name, ip, facts)
            # Always use the hostname from Facter.
            client = facts["hostname"]
            clientip = facts["ipaddress"]
            if Puppet[:node_name] == 'cert'
                if name
                    client = name
                end
                if ip
                    clientip = ip
                end
            end

            return client, clientip
        end

        # Tell a client whether there's a fresh config for it
        def freshness(client = nil, clientip = nil)
            if Puppet.features.rails? and Puppet[:storeconfigs]
                Puppet::Rails.connect

                host = Puppet::Rails::Host.find_or_create_by_name(client)
                host.last_freshcheck = Time.now
                if clientip and (! host.ip or host.ip == "")
                    host.ip = clientip
                end
                host.save
            end
            if defined? @interpreter
                return @interpreter.parsedate
            else
                return 0
            end
        end

        def initialize(hash = {})
            args = {}

            # Allow specification of a code snippet or of a file
            if code = hash[:Code]
                args[:Code] = code
            else
                args[:Manifest] = hash[:Manifest] || Puppet[:manifest]
            end

            if hash[:Local]
                @local = hash[:Local]
            else
                @local = false
            end

            args[:Local] = @local

            if hash.include?(:CA) and hash[:CA]
                @ca = Puppet::SSLCertificates::CA.new()
            else
                @ca = nil
            end

            Puppet.debug("Creating interpreter")

            if hash.include?(:UseNodes)
                args[:UseNodes] = hash[:UseNodes]
            elsif @local
                args[:UseNodes] = false
            end

            # This is only used by the cfengine module, or if --loadclasses was
            # specified in +puppet+.
            if hash.include?(:Classes)
                args[:Classes] = hash[:Classes]
            end

            @interpreter = Puppet::Parser::Interpreter.new(args)
        end

        def getconfig(facts, format = "marshal", client = nil, clientip = nil)
            if @local
                # we don't need to do anything, since we should already
                # have raw objects
                Puppet.debug "Our client is local"
            else
                Puppet.debug "Our client is remote"

                # XXX this should definitely be done in the protocol, somehow
                case format
                when "marshal":
                    Puppet.warning "You should upgrade your client.  'Marshal' will not be supported much longer."
                    begin
                        facts = Marshal::load(CGI.unescape(facts))
                    rescue => detail
                        raise XMLRPC::FaultException.new(
                            1, "Could not rebuild facts"
                        )
                    end
                when "yaml":
                    begin
                        facts = YAML.load(CGI.unescape(facts))
                    rescue => detail
                        raise XMLRPC::FaultException.new(
                            1, "Could not rebuild facts"
                        )
                    end
                else
                    raise XMLRPC::FaultException.new(
                        1, "Unavailable config format %s" % format
                    )
                end
            end

            client, clientip = clientname(client, clientip, facts)

            # Add any server-side facts to our server.
            addfacts(facts)

            retobjects = nil

            # This is hackish, but there's no "silence" option for benchmarks
            # right now
            if @local
                #begin
                    retobjects = @interpreter.run(client, facts)
                #rescue Puppet::Error => detail
                #    Puppet.err detail
                #    raise XMLRPC::FaultException.new(
                #        1, detail.to_s
                #    )
                #rescue => detail
                #    Puppet.err detail.to_s
                #    return ""
                #end
            else
                benchmark(:notice, "Compiled configuration for %s" % client) do
                    begin
                        retobjects = @interpreter.run(client, facts)
                    rescue Puppet::Error => detail
                        Puppet.err detail
                        raise XMLRPC::FaultException.new(
                            1, detail.to_s
                        )
                    rescue => detail
                        Puppet.err detail.to_s
                        return ""
                    end
                end
            end

            if @local
                return retobjects
            else
                str = nil
                case format
                when "marshal":
                    str = Marshal::dump(retobjects)
                when "yaml":
                    str = YAML.dump(retobjects)
                else
                    raise XMLRPC::FaultException.new(
                        1, "Unavailable config format %s" % format
                    )
                end
                return CGI.escape(str)
            end
        end

        def local?
            if defined? @local and @local
                return true
            else
                return false
            end
        end
    end
end

# $Id$