6
# Trigger a puppetd run on a set of hosts.
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]
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.
22
# If you are not storing your host configurations in LDAP, you can specify
25
# You will most likely have to run +puppetrun+ as root to get access to
26
# the SSL certificates.
28
# +puppetrun+ reads +puppetmaster+'s configuration file, so that it can copy
29
# things like LDAP settings.
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
43
# An example file looks like this::
52
# allow culain.madstop.com
54
# This is what you would install on your Puppet master; non-master hosts could
55
# leave off the 'fileserver' and 'puppetmaster' namespaces.
57
# Expect more documentation on this eventually.
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.
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
73
# Connect to all available hosts. Requires LDAP support at this point.
76
# Specify a class of machines to which to connect. This only works if you
77
# have LDAP configured, at the moment.
80
# Enable full debugging.
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.
87
# Print this help message
90
# A specific host to which to connect. This flag can be specified more
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.
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.
103
# Specify a tag for selecting the objects to apply. Does not work with the
108
# Print the hosts you would connect to but do not actually connect. This
109
# option requires LDAP support at this point.
113
# Do a ICMP echo against the target host. Skip hosts that don't respond to ping.
117
# sudo puppetrun -p 10 --host host1 --host host2 -t remotefile -t webserver
125
# Copyright (c) 2005 Reductive Labs, LLC
126
# Licensed under the GNU Public License
128
[:INT, :TERM].each do |signal|
130
$stderr.puts "Cancelling"
138
# Nothing; we were just doing this just in case
144
$stderr.puts "Failed to load ruby LDAP library. LDAP functionality will not be available"
148
require 'puppet/network/client'
149
require 'puppet/util/ldap/connection'
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 ]
167
# Add all of the config parameters as valid options.
168
Puppet.settings.addargs(flags)
170
result = GetoptLong.new(*flags)
173
:ignoreschedules => false,
174
:foreground => false,
188
Puppet::Util::Log.newdestination(:console)
191
result.each { |opt,arg|
194
puts "%s" % Puppet.version
196
when "--ignoreschedules"
197
options[:ignoreschedules] = true
199
options[:fqdn] = false
203
options[:test] = true
211
if Puppet.features.usage?
214
puts "No help available unless you have RDoc::usage installed"
219
options[:parallel] = Integer(arg)
221
$stderr.puts "Could not convert %s to an integer" % arg.inspect
225
options[:ping] = true
227
options[:foreground] = true
229
options[:debug] = true
231
Puppet.settings.handlearg(opt, arg)
234
rescue GetoptLong::InvalidOption => detail
235
$stderr.puts "Try '#{$0} --help'"
240
Puppet::Util::Log.level = :debug
242
Puppet::Util::Log.level = :info
245
# Now parse the config
248
if Puppet[:node_terminus] == "ldap" and (options[:all] or classes)
250
hosts = Puppet::Node.search("whatever").collect { |node| node.name }
251
puts "all: %s" % hosts.join(", ")
254
classes.each do |klass|
255
list = Puppet::Node.search("whatever", :class => klass).collect { |node| node.name }
256
puts "%s: %s" % [klass, list.join(", ")]
261
elsif ! classes.empty?
262
$stderr.puts "You must be using LDAP to specify host classes"
269
tags = tags.join(",")
274
# If we get a signal, then kill all of our children and get out.
275
[:INT, :TERM].each do |signal|
277
Puppet.notice "Caught #{signal}; shutting down"
278
children.each do |pid, host|
279
Process.kill("INT", pid)
289
puts "Skipping execution in test mode"
297
# Now do the actual work
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?
306
out = %x{ping -c 1 #{host}}
308
$stderr.print "Could not contact %s\n" % host
312
client = Puppet::Network::Client.runner.new(
314
:Port => Puppet[:puppetport]
317
print "Triggering %s\n" % host
319
result = client.run(tags, options[:ignoreschedules], options[:foreground])
321
$stderr.puts "Host %s failed: %s\n" % [host, detail]
326
when "success": exit(0)
328
$stderr.puts "Host %s is already running" % host
331
$stderr.puts "Host %s returned unknown answer '%s'" % [host, result]
337
# Else, see if we can reap a process.
341
if host = children[pid]
342
# Remove our host from the list of children, so the parallelization
345
if $?.exitstatus != 0
348
print "%s finished with exit code %s\n" % [host, $?.exitstatus]
350
$stderr.puts "Could not find host for PID %s with status %s" %
354
# There are no children left, so just exit unless there are still
355
# children left to do.
356
next unless todo.empty?
362
puts "Failed: %s" % failures.join(", ")