~ubuntu-branches/ubuntu/lucid/puppet/lucid-security

« back to all changes in this revision

Viewing changes to bin/puppetrun

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2009-12-23 00:48:10 UTC
  • mfrom: (1.1.10 upstream) (3.1.7 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091223004810-3i4oryds922g5n59
Tags: 0.25.1-3ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/rules:
    + Don't start puppet when first installing puppet.
  - debian/puppet.conf, lib/puppet/defaults.rb:
    + Move templates to /etc/puppet
  - lib/puppet/defaults.rb:
    + Fix /var/lib/puppet/state ownership.
  - man/man8/puppet.conf.8: 
    + Fix broken URL in manpage.
  - debian/control:
    + Update maintainer accordint to spec.
    + Puppetmaster Recommends -> Suggests
    + Created puppet-testsuite as a seperate. Allow the users to run puppet's 
      testsuite.
  - tests/Rakefile: Fix rakefile so that the testsuite can acutally be ran.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env ruby
2
 
 
3
 
#
4
 
# = Synopsis
5
 
#
6
 
# Trigger a puppetd run on a set of hosts.
7
 
#
8
 
# = Usage
9
 
#
10
 
#   puppetrun [-a|--all] [-c|--class <class>] [-d|--debug] [-f|--foreground]
11
 
#       [-h|--help] [--host <host>] [--no-fqdn] [--ignoreschedules]
12
 
#       [-t|--tag <tag>] [--test] [-p|--ping]
13
 
#
14
 
# = Description
15
 
#
16
 
# This script can be used to connect to a set of machines running +puppetd+
17
 
# and trigger them to run their configurations.  The most common usage would
18
 
# be to specify a class of hosts and a set of tags, and +puppetrun+ would
19
 
# look up in LDAP all of the hosts matching that class, then connect to
20
 
# each host and trigger a run of all of the objects with the specified tags.
21
 
#
22
 
# If you are not storing your host configurations in LDAP, you can specify
23
 
# hosts manually.
24
 
#
25
 
# You will most likely have to run +puppetrun+ as root to get access to
26
 
# the SSL certificates.
27
 
#
28
 
# +puppetrun+ reads +puppetmaster+'s configuration file, so that it can copy
29
 
# things like LDAP settings.
30
 
#
31
 
# = Usage Notes
32
 
#
33
 
# +puppetrun+ is useless unless +puppetd+ is listening.  See its documentation
34
 
# for more information, but the gist is that you must enable +listen+ on the
35
 
# +puppetd+ daemon, either using +--listen+ on the command line or adding
36
 
# 'listen: true' in its config file.  In addition, you need to set the daemons
37
 
# up to specifically allow connections by creating the +namespaceauth+ file,
38
 
# normally at '/etc/puppet/namespaceauth.conf'.  This file specifies who has
39
 
# access to each namespace; if you create the file you must add every namespace
40
 
# you want any Puppet daemon to allow -- it is currently global to all Puppet
41
 
# daemons.
42
 
#
43
 
# An example file looks like this::
44
 
#
45
 
#     [fileserver]
46
 
#         allow *.madstop.com
47
 
48
 
#     [puppetmaster]
49
 
#         allow *.madstop.com
50
 
51
 
#     [puppetrunner]
52
 
#         allow culain.madstop.com
53
 
#
54
 
# This is what you would install on your Puppet master; non-master hosts could
55
 
# leave off the 'fileserver' and 'puppetmaster' namespaces.
56
 
#
57
 
# Expect more documentation on this eventually.
58
 
#
59
 
# = Options
60
 
#
61
 
# Note that any configuration parameter that's valid in the configuration file
62
 
# is also a valid long argument.  For example, 'ssldir' is a valid configuration
63
 
# parameter, so you can specify '--ssldir <directory>' as an argument.
64
 
#
65
 
# See the configuration file documentation at
66
 
# http://reductivelabs.com/projects/puppet/reference/configref.html for
67
 
# the full list of acceptable parameters. A commented list of all
68
 
# configuration options can also be generated by running puppetmasterdd with
69
 
# '--genconfig'.
70
 
#
71
 
#
72
 
# all::
73
 
#   Connect to all available hosts.  Requires LDAP support at this point.
74
 
#
75
 
# class::
76
 
#   Specify a class of machines to which to connect.  This only works if you
77
 
#   have LDAP configured, at the moment.
78
 
#
79
 
# debug::
80
 
#   Enable full debugging.
81
 
#
82
 
# foreground::
83
 
#   Run each configuration in the foreground; that is, when connecting to a host,
84
 
#   do not return until the host has finished its run.  The default is false.
85
 
#
86
 
# help::
87
 
#   Print this help message
88
 
#
89
 
# host::
90
 
#   A specific host to which to connect.  This flag can be specified more
91
 
#   than once.
92
 
#
93
 
# ignoreschedules::
94
 
#   Whether the client should ignore schedules when running its configuration.
95
 
#   This can be used to force the client to perform work it would not normally
96
 
#   perform so soon.  The default is false.
97
 
#
98
 
# parallel::
99
 
#   How parallel to make the connections.  Parallelization is provided by forking
100
 
#   for each client to which to connect.  The default is 1, meaning serial execution.
101
 
#
102
 
# tag::
103
 
#   Specify a tag for selecting the objects to apply. Does not work with the
104
 
#   --test option.
105
 
#
106
 
#
107
 
# test::
108
 
#   Print the hosts you would connect to but do not actually connect. This
109
 
#   option requires LDAP support at this point.
110
 
#
111
 
# ping::
112
 
#   
113
 
#   Do a ICMP echo against the target host. Skip hosts that don't respond to ping.
114
 
#
115
 
# = Example
116
 
#
117
 
#   sudo puppetrun -p 10 --host host1 --host host2 -t remotefile -t webserver
118
 
#
119
 
# = Author
120
 
#
121
 
# Luke Kanies
122
 
#
123
 
# = Copyright
124
 
#
125
 
# Copyright (c) 2005 Reductive Labs, LLC
126
 
# Licensed under the GNU Public License
127
 
 
128
 
[:INT, :TERM].each do |signal|
129
 
    trap(signal) do
130
 
        $stderr.puts "Cancelling"
131
 
        exit(1)
132
 
    end
133
 
end
134
 
 
135
 
begin
136
 
    require 'rubygems'
137
 
rescue LoadError
138
 
    # Nothing; we were just doing this just in case
139
 
end
140
 
 
141
 
begin
142
 
    require 'ldap'
143
 
rescue LoadError
144
 
    $stderr.puts "Failed to load ruby LDAP library. LDAP functionality will not be available"
145
 
end
146
 
 
147
 
require 'puppet'
148
 
require 'puppet/network/client'
149
 
require 'puppet/util/ldap/connection'
150
 
require 'getoptlong'
151
 
 
152
 
flags = [
153
 
    [ "--all",      "-a",               GetoptLong::NO_ARGUMENT ],
154
 
    [ "--tag",      "-t",           GetoptLong::REQUIRED_ARGUMENT ],
155
 
    [ "--class",        "-c",                   GetoptLong::REQUIRED_ARGUMENT ],
156
 
    [ "--foreground", "-f",             GetoptLong::NO_ARGUMENT ],
157
 
    [ "--debug",        "-d",                   GetoptLong::NO_ARGUMENT ],
158
 
    [ "--help",         "-h",                   GetoptLong::NO_ARGUMENT ],
159
 
    [ "--host",                                 GetoptLong::REQUIRED_ARGUMENT ],
160
 
    [ "--parallel", "-p",                       GetoptLong::REQUIRED_ARGUMENT ],
161
 
    [ "--ping", "-P",                   GetoptLong::NO_ARGUMENT ],
162
 
    [ "--no-fqdn",  "-n",                       GetoptLong::NO_ARGUMENT ],
163
 
    [ "--test",                     GetoptLong::NO_ARGUMENT ],
164
 
    [ "--version",  "-V",           GetoptLong::NO_ARGUMENT ]
165
 
]
166
 
 
167
 
# Add all of the config parameters as valid options.
168
 
Puppet.settings.addargs(flags)
169
 
 
170
 
result = GetoptLong.new(*flags)
171
 
 
172
 
options = {
173
 
    :ignoreschedules => false,
174
 
    :foreground => false,
175
 
    :parallel => 1,
176
 
    :ping => false,
177
 
    :debug => false,
178
 
    :test => false,
179
 
    :all => false,
180
 
    :verbose => true,
181
 
    :fqdn => true
182
 
}
183
 
 
184
 
hosts = []
185
 
classes = []
186
 
tags = []
187
 
 
188
 
Puppet::Util::Log.newdestination(:console)
189
 
 
190
 
begin
191
 
    result.each { |opt,arg|
192
 
        case opt
193
 
            when "--version"
194
 
                puts "%s" % Puppet.version
195
 
                exit
196
 
            when "--ignoreschedules"
197
 
                options[:ignoreschedules] = true
198
 
            when "--no-fqdn"
199
 
                options[:fqdn] = false
200
 
            when "--all"
201
 
                options[:all] = true
202
 
            when "--test"
203
 
                options[:test] = true
204
 
            when "--tag"
205
 
                tags << arg
206
 
            when "--class"
207
 
                classes << arg
208
 
            when "--host"
209
 
                hosts << arg
210
 
            when "--help"
211
 
                if Puppet.features.usage?
212
 
                    RDoc::usage && exit
213
 
                else
214
 
                    puts "No help available unless you have RDoc::usage installed"
215
 
                    exit
216
 
                end
217
 
            when "--parallel"
218
 
                begin
219
 
                    options[:parallel] = Integer(arg)
220
 
                rescue
221
 
                    $stderr.puts "Could not convert %s to an integer" % arg.inspect
222
 
                    exit(23)
223
 
                end
224
 
            when "--ping"
225
 
                 options[:ping] = true 
226
 
            when "--foreground"
227
 
                options[:foreground] = true
228
 
            when "--debug"
229
 
                options[:debug] = true
230
 
            else
231
 
                Puppet.settings.handlearg(opt, arg)
232
 
        end
233
 
    }
234
 
rescue GetoptLong::InvalidOption => detail
235
 
    $stderr.puts "Try '#{$0} --help'"
236
 
    exit(1)
237
 
end
238
 
 
239
 
if options[:debug]
240
 
    Puppet::Util::Log.level = :debug
241
 
else
242
 
    Puppet::Util::Log.level = :info
243
 
end
244
 
 
245
 
# Now parse the config
246
 
Puppet.parse_config
247
 
 
248
 
if Puppet[:node_terminus] == "ldap" and (options[:all] or classes)
249
 
    if options[:all]
250
 
        hosts = Puppet::Node.search("whatever").collect { |node| node.name }
251
 
        puts "all: %s" % hosts.join(", ")
252
 
    else
253
 
        hosts = []
254
 
        classes.each do |klass|
255
 
            list = Puppet::Node.search("whatever", :class => klass).collect { |node| node.name }
256
 
            puts "%s: %s" % [klass, list.join(", ")]
257
 
 
258
 
            hosts += list
259
 
        end
260
 
    end
261
 
elsif ! classes.empty?
262
 
    $stderr.puts "You must be using LDAP to specify host classes"
263
 
    exit(24)
264
 
end
265
 
 
266
 
if tags.empty?
267
 
    tags = ""
268
 
else
269
 
    tags = tags.join(",")
270
 
end
271
 
 
272
 
children = {}
273
 
 
274
 
# If we get a signal, then kill all of our children and get out.
275
 
[:INT, :TERM].each do |signal|
276
 
    trap(signal) do
277
 
        Puppet.notice "Caught #{signal}; shutting down"
278
 
        children.each do |pid, host|
279
 
            Process.kill("INT", pid)
280
 
        end
281
 
 
282
 
        waitall
283
 
 
284
 
        exit(1)
285
 
    end
286
 
end
287
 
 
288
 
if options[:test]
289
 
    puts "Skipping execution in test mode"
290
 
    exit(0)
291
 
end
292
 
 
293
 
todo = hosts.dup
294
 
 
295
 
failures = []
296
 
 
297
 
# Now do the actual work
298
 
go = true
299
 
while go
300
 
    # If we don't have enough children in process and we still have hosts left to
301
 
    # do, then do the next host.
302
 
    if children.length < options[:parallel] and ! todo.empty?
303
 
        host = todo.shift
304
 
        pid = fork do
305
 
            if options[:ping]
306
 
              out = %x{ping -c 1 #{host}}
307
 
              unless $? == 0
308
 
                  $stderr.print "Could not contact %s\n" % host
309
 
                  next
310
 
              end
311
 
            end
312
 
            client = Puppet::Network::Client.runner.new(
313
 
                :Server => host,
314
 
                :Port => Puppet[:puppetport]
315
 
            )
316
 
 
317
 
            print "Triggering %s\n" % host
318
 
            begin
319
 
                result = client.run(tags, options[:ignoreschedules], options[:foreground])
320
 
            rescue => detail
321
 
                $stderr.puts "Host %s failed: %s\n" % [host, detail]
322
 
                exit(2)
323
 
            end
324
 
            
325
 
            case result
326
 
            when "success": exit(0)
327
 
            when "running":
328
 
                $stderr.puts "Host %s is already running" % host
329
 
                exit(3)
330
 
            else
331
 
                $stderr.puts "Host %s returned unknown answer '%s'" % [host, result]
332
 
                exit(12)
333
 
            end
334
 
        end
335
 
        children[pid] = host
336
 
    else
337
 
        # Else, see if we can reap a process.
338
 
        begin
339
 
            pid = Process.wait
340
 
 
341
 
            if host = children[pid]
342
 
                # Remove our host from the list of children, so the parallelization
343
 
                # continues working.
344
 
                children.delete(pid)
345
 
                if $?.exitstatus != 0
346
 
                    failures << host
347
 
                end
348
 
                print "%s finished with exit code %s\n" % [host, $?.exitstatus]
349
 
            else
350
 
                $stderr.puts "Could not find host for PID %s with status %s" %
351
 
                    [pid, $?.exitstatus]
352
 
            end
353
 
        rescue Errno::ECHILD
354
 
            # There are no children left, so just exit unless there are still
355
 
            # children left to do.
356
 
            next unless todo.empty?
357
 
 
358
 
            if failures.empty?
359
 
                puts "Finished"
360
 
                exit(0)
361
 
            else
362
 
                puts "Failed: %s" % failures.join(", ")
363
 
                exit(3)
364
 
            end
365
 
        end
366
 
    end
367
 
end
368
 
 
369