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