summaryrefslogtreecommitdiffstats
path: root/lib/puppet/server/report.rb
blob: 3d02f5f3d2e9c39c5ae8dcaec797b92bc292ce22 (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
module Puppet
class Server
    # A simple server for triggering a new run on a Puppet client.
    class Report < Handler
        @interface = XMLRPC::Service::Interface.new("puppetreports") { |iface|
            iface.add_method("string report(array)")
        }

        Puppet.setdefaults(:reporting,
            :reportdirectory => {:default => "$vardir/reports",
                    :mode => 0750,
                    :owner => "$user",
                    :group => "$group",
                    :desc => "The directory in which to store reports received from the
                client.  Each client gets a separate subdirectory."},
            :reports => ["none",
                "The list of reports to generate.  All reports are looked for
                in puppet/reports/<name>.rb, and multiple report names should be
                comma-separated (whitespace is okay)."
            ]
        )

        @reports = {}
        @reportloader = Puppet::Autoload.new(self, "puppet/reports")

        class << self
            attr_reader :hooks
        end

        def self.reportmethod(report)
            "report_" + report.to_s
        end

        # Add a hook for processing reports.
        def self.newreport(name, &block)
            name = name.intern if name.is_a? String
            method = reportmethod(name)

            # We want to define a method so that reports can use 'return'.
            define_method(method, &block)

            @reports[name] = method
        end

        # Load a report.
        def self.report(name)
            name = name.intern if name.is_a? String
            unless @reports.include? reportmethod(name)
                @reportloader.load(name)
                unless @reports.include? name
                    Puppet.warning(
                        "Loaded report file for %s but report was not defined" %
                        name
                    )
                    return nil
                end
            end

            @reports[name]
        end

        def initialize(*args)
            super
            Puppet.config.use(:reporting)
            Puppet.config.use(:metrics)
        end

        # Dynamically create the report methods as necessary.
        def method_missing(name, *args)
            if name.to_s =~ /^report_(.+)$/
                if self.class.report($1)
                    send(name, *args)
                else
                    super
                end
            else
                super
            end
        end

        # Accept a report from a client.
        def report(report, client = nil, clientip = nil)
            # We need the client name for storing files.
            client ||= Facter["hostname"].value

            # Unescape the report
            unless @local
                report = CGI.unescape(report)
            end

            process(report)

            # We don't want any tracking back in the fs.  Unlikely, but there
            # you go.
            client.gsub("..",".")

            dir = File.join(Puppet[:reportdirectory], client)

            unless FileTest.exists?(dir)
                mkclientdir(client, dir)
            end

            # Now store the report.
            now = Time.now.gmtime
            name = %w{year month day hour min}.collect do |method|
                # Make sure we're at least two digits everywhere
                "%02d" % now.send(method).to_s
            end.join("") + ".yaml"

            file = File.join(dir, name)

            begin
                File.open(file, "w", 0640) do |f|
                    f.puts report
                end
            rescue => detail
                if Puppet[:debug]
                    puts detail.backtrace
                end
                Puppet.warning "Could not write report for %s at %s: %s" %
                    [client, file, detail]
            end


            # Our report is in YAML
            return file
        end

        private

        def mkclientdir(client, dir)
            Puppet.config.setdefaults("reportclient-#{client}",
                "clientdir-#{client}" => { :default => dir,
                    :mode => 0750,
                    :owner => "$user",
                    :group => "$group"
                }
            )

            Puppet.config.use("reportclient-#{client}")
        end

        # Process the report using all of the existing hooks.
        def process(report)
            return if Puppet[:reports] == "none"

            # First convert the report to real objects
            begin
                report = YAML.load(report)
            rescue => detail
                Puppet.warning "Could not load report: %s" % detail
                return
            end

            Puppet[:reports].split(/\s*,\s*/).each do |name|
                method = self.class.report(name)

                if respond_to? method
                    Puppet.info "Processing report %s" % name
                    begin
                        send(method, report)
                    rescue NoMethodError => detail
                        Puppet.warning "No report named '%s'" % name
                    rescue => detail
                        if Puppet[:debug]
                            puts detail.backtrace
                        end
                        Puppet.err "Report %s failed: %s" %
                            [name, detail]
                    end
                else
                    Puppet.warning "No report named '%s'" % name
                end
            end
        end
    end
end
end

# $Id$