class Puppet::Application::Device

Attributes

agent[RW]
args[RW]
host[RW]

Public Instance Methods

app_defaults() click to toggle source
Calls superclass method Puppet::Application#app_defaults
   # File lib/puppet/application/device.rb
12 def app_defaults
13   super.merge({
14     :catalog_terminus => :rest,
15     :catalog_cache_terminus => :json,
16     :node_terminus => :rest,
17     :facts_terminus => :network_device,
18   })
19 end
find_resources(type, name) click to toggle source
    # File lib/puppet/application/device.rb
397 def find_resources(type, name)
398   key = [type, name].join('/')
399 
400   if name
401     [ Puppet::Resource.indirection.find( key ) ]
402   else
403     Puppet::Resource.indirection.search( key, {} )
404   end
405 end
help() click to toggle source
    # File lib/puppet/application/device.rb
 86   def help
 87       <<-HELP
 88 
 89 puppet-device(8) -- #{summary}
 90 ========
 91 
 92 SYNOPSIS
 93 --------
 94 Retrieves catalogs from the Puppet master and applies them to remote devices.
 95 
 96 This subcommand can be run manually; or periodically using cron,
 97 a scheduled task, or a similar tool.
 98 
 99 
100 USAGE
101 -----
102   puppet device [-h|--help] [-v|--verbose] [-d|--debug]
103                 [-l|--logdest syslog|<file>|console] [--detailed-exitcodes]
104                 [--deviceconfig <file>] [-w|--waitforcert <seconds>]
105                 [--libdir <directory>]
106                 [-a|--apply <file>] [-f|--facts] [-r|--resource <type> [name]]
107                 [-t|--target <device>] [--user=<user>] [-V|--version]
108 
109 
110 DESCRIPTION
111 -----------
112 Devices require a proxy Puppet agent to request certificates, collect facts,
113 retrieve and apply catalogs, and store reports.
114 
115 
116 USAGE NOTES
117 -----------
118 Devices managed by the puppet-device subcommand on a Puppet agent are
119 configured in device.conf, which is located at $confdir/device.conf by default,
120 and is configurable with the $deviceconfig setting.
121 
122 The device.conf file is an INI-like file, with one section per device:
123 
124 [<DEVICE_CERTNAME>]
125 type <TYPE>
126 url <URL>
127 debug
128 
129 The section name specifies the certname of the device.
130 
131 The values for the type and url properties are specific to each type of device.
132 
133 The optional debug property specifies transport-level debugging,
134 and is limited to telnet and ssh transports.
135 
136 See https://puppet.com/docs/puppet/latest/config_file_device.html for details.
137 
138 
139 OPTIONS
140 -------
141 Note that any setting that's valid in the configuration file is also a valid
142 long argument. For example, 'server' is a valid configuration parameter, so
143 you can specify '--server <servername>' as an argument.
144 
145 * --help, -h:
146   Print this help message
147 
148 * --verbose, -v:
149   Turn on verbose reporting.
150 
151 * --debug, -d:
152   Enable full debugging.
153 
154 * --logdest, -l:
155   Where to send log messages. Choose between 'syslog' (the POSIX syslog
156   service), 'console', or the path to a log file. If debugging or verbosity is
157   enabled, this defaults to 'console'. Otherwise, it defaults to 'syslog'.
158   Multiple destinations can be set using a comma separated list
159   (eg: `/path/file1,console,/path/file2`)"
160 
161   A path ending with '.json' will receive structured output in JSON format. The
162   log file will not have an ending ']' automatically written to it due to the
163   appending nature of logging. It must be appended manually to make the content
164   valid JSON.
165 
166 * --detailed-exitcodes:
167   Provide transaction information via exit codes. If this is enabled, an exit
168   code of '1' means at least one device had a compile failure, an exit code of
169   '2' means at least one device had resource changes, and an exit code of '4'
170   means at least one device had resource failures. Exit codes of '3', '5', '6',
171   or '7' means that a bitwise combination of the preceding exit codes happened.
172 
173 * --deviceconfig:
174   Path to the device config file for puppet device.
175   Default: $confdir/device.conf
176 
177 * --waitforcert, -w:
178   This option only matters for targets that do not yet have certificates
179   and it is enabled by default, with a value of 120 (seconds).  This causes
180   +puppet device+ to poll the server every 2 minutes and ask it to sign a
181   certificate request.  This is useful for the initial setup of a target.
182   You can turn off waiting for certificates by specifying a time of 0.
183 
184 * --libdir:
185   Override the per-device libdir with a local directory. Specifying a libdir also
186   disables pluginsync. This is useful for testing.
187 
188   A path ending with '.jsonl' will receive structured output in JSON Lines
189   format.
190 
191 * --apply:
192   Apply a manifest against a remote target. Target must be specified.
193 
194 * --facts:
195   Displays the facts of a remote target. Target must be specified.
196 
197 * --resource:
198   Displays a resource state as Puppet code, roughly equivalent to
199   `puppet resource`.  Can be filtered by title. Requires --target be specified.
200 
201 * --target:
202   Target a specific device/certificate in the device.conf. Doing so will perform a
203   device run against only that device/certificate.
204 
205 * --to_yaml:
206   Output found resources in yaml format, suitable to use with Hiera and
207   create_resources.
208 
209 * --user:
210   The user to run as.
211 
212 
213 EXAMPLE
214 -------
215       $ puppet device --target remotehost --verbose
216 
217 AUTHOR
218 ------
219 Brice Figureau
220 
221 
222 COPYRIGHT
223 ---------
224 Copyright (c) 2011-2018 Puppet Inc., LLC
225 Licensed under the Apache 2.0 License
226       HELP
227   end
main() click to toggle source
    # File lib/puppet/application/device.rb
230 def main
231   if options[:resource] and !options[:target]
232     raise _("resource command requires target")
233   end
234   if options[:facts] and !options[:target]
235     raise _("facts command requires target")
236   end
237   unless options[:apply].nil?
238     raise _("missing argument: --target is required when using --apply") if options[:target].nil?
239     raise _("%{file} does not exist, cannot apply") % { file: options[:apply] } unless File.file?(options[:apply])
240   end
241   libdir = Puppet[:libdir]
242   vardir = Puppet[:vardir]
243   confdir = Puppet[:confdir]
244   ssldir = Puppet[:ssldir]
245   certname = Puppet[:certname]
246 
247   env = Puppet::Node::Environment.remote(Puppet[:environment])
248   returns = Puppet.override(:current_environment => env, :loaders => Puppet::Pops::Loaders.new(env)) do
249     # find device list
250     require_relative '../../puppet/util/network_device/config'
251     devices = Puppet::Util::NetworkDevice::Config.devices.dup
252     if options[:target]
253       devices.select! { |key, value| key == options[:target] }
254     end
255     if devices.empty?
256       if options[:target]
257         raise _("Target device / certificate '%{target}' not found in %{config}") % { target: options[:target], config: Puppet[:deviceconfig] }
258       else
259         Puppet.err _("No device found in %{config}") % { config: Puppet[:deviceconfig] }
260         exit(1)
261       end
262     end
263     devices.collect do |devicename,device|
264       # TODO when we drop support for ruby < 2.5 we can remove the extra block here
265       begin
266         device_url = URI.parse(device.url)
267         # Handle nil scheme & port
268         scheme = "#{device_url.scheme}://" if device_url.scheme
269         port = ":#{device_url.port}" if device_url.port
270 
271         # override local $vardir and $certname
272         Puppet[:ssldir] = ::File.join(Puppet[:deviceconfdir], device.name, 'ssl')
273         Puppet[:confdir] = ::File.join(Puppet[:devicedir], device.name)
274         Puppet[:libdir] = options[:libdir] || ::File.join(Puppet[:devicedir], device.name, 'lib')
275         Puppet[:vardir] = ::File.join(Puppet[:devicedir], device.name)
276         Puppet[:certname] = device.name
277         ssl_context = nil
278 
279         # create device directory under $deviceconfdir
280         Puppet::FileSystem.dir_mkpath(Puppet[:ssldir]) unless Puppet::FileSystem.dir_exist?(Puppet[:ssldir])
281 
282         # this will reload and recompute default settings and create device-specific sub vardir
283         Puppet.settings.use :main, :agent, :ssl
284 
285         # Workaround for PUP-8736: store ssl certs outside the cache directory to prevent accidental removal and keep the old path as symlink
286         optssldir = File.join(Puppet[:confdir], 'ssl')
287         Puppet::FileSystem.symlink(Puppet[:ssldir], optssldir) unless Puppet::FileSystem.exist?(optssldir)
288 
289         unless options[:resource] || options[:facts] || options[:apply]
290           # Since it's too complicated to fix properly in the default settings, we workaround for PUP-9642 here.
291           # See https://github.com/puppetlabs/puppet/pull/7483#issuecomment-483455997 for details.
292           # This has to happen after `settings.use` above, so the directory is created and before `setup_host` below, where the SSL
293           # routines would fail with access errors
294           if Puppet.features.root? && !Puppet::Util::Platform.windows?
295             user = Puppet::Type.type(:user).new(name: Puppet[:user]).exists? ? Puppet[:user] : nil
296             group = Puppet::Type.type(:group).new(name: Puppet[:group]).exists? ? Puppet[:group] : nil
297             Puppet.debug("Fixing perms for #{user}:#{group} on #{Puppet[:confdir]}")
298             FileUtils.chown(user, group, Puppet[:confdir]) if user || group
299           end
300 
301           ssl_context = setup_context
302 
303           unless options[:libdir]
304             Puppet.override(ssl_context: ssl_context) do
305               Puppet::Configurer::PluginHandler.new.download_plugins(env) if Puppet::Configurer.should_pluginsync?
306             end
307           end
308         end
309 
310         # this inits the device singleton, so that the facts terminus
311         # and the various network_device provider can use it
312         Puppet::Util::NetworkDevice.init(device)
313 
314         if options[:resource]
315           type, name = parse_args(command_line.args)
316           Puppet.info _("retrieving resource: %{resource} from %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { resource: type, target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path }
317           resources = find_resources(type, name)
318           if options[:to_yaml]
319             data = resources.map do |resource|
320               resource.prune_parameters(:parameters_to_include => @extra_params).to_hiera_hash
321             end.inject(:merge!)
322             text = YAML.dump(type.downcase => data)
323           else
324             text = resources.map do |resource|
325               resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest.force_encoding(Encoding.default_external)
326             end.join("\n")
327           end
328           (puts text)
329           0
330         elsif options[:facts]
331           Puppet.info _("retrieving facts from %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { resource: type, target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path }
332           remote_facts = Puppet::Node::Facts.indirection.find(name, :environment => env)
333           # Give a proper name to the facts
334           remote_facts.name = remote_facts.values['clientcert']
335           renderer = Puppet::Network::FormatHandler.format(:console)
336           puts renderer.render(remote_facts)
337           0
338         elsif options[:apply]
339           # avoid reporting to server
340           Puppet::Transaction::Report.indirection.terminus_class = :yaml
341           Puppet::Resource::Catalog.indirection.cache_class = nil
342 
343           require_relative '../../puppet/application/apply'
344           begin
345             Puppet[:node_terminus] = :plain
346             Puppet[:catalog_terminus] = :compiler
347             Puppet[:catalog_cache_terminus] = nil
348             Puppet[:facts_terminus] = :network_device
349             Puppet.override(:network_device => true) do
350               Puppet::Application::Apply.new(Puppet::Util::CommandLine.new('puppet', ["apply", options[:apply]])).run_command
351             end
352           end
353         else
354           Puppet.info _("starting applying configuration to %{target} at %{scheme}%{url_host}%{port}%{url_path}") % { target: device.name, scheme: scheme, url_host: device_url.host, port: port, url_path: device_url.path }
355 
356           overrides = {}
357           overrides[:ssl_context] = ssl_context if ssl_context
358           Puppet.override(overrides) do
359             configurer = Puppet::Configurer.new
360             configurer.run(:network_device => true, :pluginsync => false)
361           end
362         end
363       rescue => detail
364         Puppet.log_exception(detail)
365         # If we rescued an error, then we return 1 as the exit code
366         1
367       ensure
368         Puppet[:libdir] = libdir
369         Puppet[:vardir] = vardir
370         Puppet[:confdir] = confdir
371         Puppet[:ssldir] = ssldir
372         Puppet[:certname] = certname
373       end
374     end
375   end
376 
377   if ! returns or returns.compact.empty?
378     exit(1)
379   elsif options[:detailed_exitcodes]
380     # Bitwise OR the return codes together, puppet style
381     exit(returns.compact.reduce(:|))
382   elsif returns.include? 1
383     exit(1)
384   else
385     exit(0)
386   end
387 end
parse_args(args) click to toggle source
    # File lib/puppet/application/device.rb
389 def parse_args(args)
390   type = args.shift or raise _("You must specify the type to display")
391   Puppet::Type.type(type) or raise _("Could not find type %{type}") % { type: type }
392   name = args.shift
393 
394   [type, name]
395 end
preinit() click to toggle source
   # File lib/puppet/application/device.rb
21 def preinit
22   # Do an initial trap, so that cancels don't get a stack trace.
23   Signal.trap(:INT) do
24     $stderr.puts _("Cancelling startup")
25     exit(0)
26   end
27 
28   {
29     :apply => nil,
30     :waitforcert => nil,
31     :detailed_exitcodes => false,
32     :verbose => false,
33     :debug => false,
34     :centrallogs => false,
35     :setdest => false,
36     :resource => false,
37     :facts => false,
38     :target => nil,
39     :to_yaml => false,
40   }.each do |opt,val|
41     options[opt] = val
42   end
43 
44   @args = {}
45 end
setup() click to toggle source
    # File lib/puppet/application/device.rb
413 def setup
414   setup_logs
415 
416   Puppet::SSL::Oids.register_puppet_oids
417 
418   # setup global device-specific defaults; creates all necessary directories, etc
419   Puppet.settings.use :main, :agent, :device, :ssl
420 
421   if options[:apply] || options[:facts] || options[:resource]
422     Puppet::Util::Log.newdestination(:console)
423   else
424     args[:Server] = Puppet[:server]
425     if options[:centrallogs]
426       logdest = args[:Server]
427 
428       logdest += ":" + args[:Port] if args.include?(:Port)
429       Puppet::Util::Log.newdestination(logdest)
430     end
431 
432     Puppet::Transaction::Report.indirection.terminus_class = :rest
433 
434     if Puppet[:catalog_cache_terminus]
435       Puppet::Resource::Catalog.indirection.cache_class = Puppet[:catalog_cache_terminus].intern
436     end
437   end
438 end
setup_context() click to toggle source
    # File lib/puppet/application/device.rb
407 def setup_context
408   waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
409   sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert)
410   sm.ensure_client_certificate
411 end
summary() click to toggle source
   # File lib/puppet/application/device.rb
82 def summary
83   _("Manage remote network devices")
84 end