#!/usr/bin/env ruby # # = Synopsis # # Generate a reference for all Puppet types. Largely meant for internal Reductive # Labs use. # # = Usage # # puppetdoc [-a|--all] [-h|--help] [-m|--mode [-s|--section <[type]|configuration|report|function>] # # = Description # # This command generates a restructured-text document describing all installed # Puppet types or all allowable arguments to puppet executables. It is largely # meant for internal use and is used to generate the reference document # available on the Reductive Labs web site. # # = Options # # all:: # Output the docs for all of the reference types. # # help:: # Print this help message # # mode:: # Determine the output mode. Valid modes are 'text', 'trac', and 'pdf'. Note that 'trac' mode only works on Reductive Labs servers. The default mode is 'text'. # # section:: # Handle a particular section. Available sections are 'type', 'configuration', 'report', and 'function'. The default section is 'type'. # # = Example # # $ puppetdoc > /tmp/type_reference.rst # # = Author # # Luke Kanies # # = Copyright # # Copyright (c) 2005-2007 Reductive Labs, LLC # Licensed under the GNU Public License require 'puppet' class PuppetDoc include Puppet::Util::Docs @@sections = {} attr_accessor :page, :depth, :header, :title def self.[](name) @@sections[name] end def self.each @@sections.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name, instance| yield instance } end def self.footer "\n\n----------------\n\n*This page autogenerated on %s*\n" % Time.now end def self.page(*sections) depth = 4 # Use the minimum depth sections.each do |name| section = @@sections[name] or raise "Could not find section %s" % name depth = section.depth if section.depth < depth end text = ".. contents:: :depth: 2\n\n" end def self.pdf(text) puts "creating pdf" File.open("/tmp/puppetdoc.txt", "w") do |f| f.puts text end rst2latex = %x{which rst2latex} if $? != 0 or rst2latex =~ /no / rst2latex = %x{which rst2latex.py} end if $? != 0 or rst2latex =~ /no / raise "Could not find rst2latex" end rst2latex.chomp! cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex} output = %x{#{cmd}} unless $? == 0 $stderr.puts "rst2latex failed" $stderr.puts output exit(1) end $stderr.puts output # Now convert to pdf puts "handling pdf" Dir.chdir("/tmp") do %x{texi2pdf puppetdoc.tex >/dev/null 2>/dev/null} end #if FileTest.exists?("/tmp/puppetdoc.pdf") # FileUtils.mv("/tmp/puppetdoc.pdf", "/export/apache/docroots/reductivelabs.com/htdocs/downloads/puppet/reference.pdf") #end end def self.sections @@sections.keys.sort { |a,b| a.to_s <=> b.to_s } end HEADER_LEVELS = [nil, "=", "-", "+", "'", "~"] def h(name, level) return "%s\n%s\n" % [name, HEADER_LEVELS[level] * name.to_s.length] end def initialize(name, options = {}, &block) @name = name options.each do |option, value| send(option.to_s + "=", value) end meta_def(:generate, &block) @@sections[name] = self # Now handle the defaults @title ||= "%s Reference" % @name.to_s.capitalize @page ||= @title.gsub(/\s+/, '') @depth ||= 2 @header ||= "" end # Indent every line in the chunk except those which begin with '..'. def indent(text, tab) return text.gsub(/(^|\A)/, tab).gsub(/^ +\.\./, "..") end def paramwrap(name, text, options = {}) options[:level] ||= 5 #str = "%s : " % name str = h(name, options[:level]) if options[:namevar] str += "- **namevar**\n\n" end str += text #str += text.gsub(/\n/, "\n ") str += "\n\n" return str end def output(withcontents = true) # First the header text = h(@title, 1) text += "\n\n*This page is autogenerated; any changes will get overwritten*\n\n" if withcontents text += ".. contents:: :depth: %s\n\n" % @depth end text += @header text += generate() if withcontents text += self.class.footer end return text end def text puts output end def trac File.open("/tmp/puppetdoc.txt", "w") do |f| f.puts "{{{ #!rst\n #{self.output} }}}" end puts "Writing %s reference to trac as %s" % [@name, @page] cmd = %{sudo trac-admin /export/svn/trac/puppet wiki import %s /tmp/puppetdoc.txt} % self.page output = %x{#{cmd}} unless $? == 0 $stderr.puts "trac-admin failed" $stderr.puts output exit(1) end unless output =~ /^\s+/ $stderr.puts output end end end require 'puppet' require 'puppet/network/handler' require 'getoptlong' result = GetoptLong.new( [ "--all", "-a", GetoptLong::NO_ARGUMENT ], [ "--mode", "-m", GetoptLong::REQUIRED_ARGUMENT ], [ "--section", "-s", GetoptLong::REQUIRED_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ) debug = false $tab = " " options = {:sections => [], :mode => :text} begin result.each { |opt,arg| case opt when "--all" options[:all] = true when "--mode" case arg when "text", "pdf", "trac": options[:mode] = arg.intern else raise "Invalid output mode %s" % arg end when "--section" options[:sections] << arg.intern when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end end } rescue GetoptLong::InvalidOption => detail $stderr.puts "Try '#{$0} --help'" exit(1) end if options[:sections].empty? options[:sections] << :type end config = PuppetDoc.new :configuration, :depth => 1 do docs = {} Puppet.config.each do |name, object| docs[name] = object end str = "" docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, object| # Make each name an anchor header = name.to_s str += h(header, 3) # Print the doc string itself begin str += object.desc.gsub(/\n/, " ") rescue => detail puts detail.backtrace puts detail end str += "\n\n" # Now print the data about the item. str += "" val = object.default if name.to_s == "vardir" val = "/var/puppet" elsif name.to_s == "confdir" val = "/etc/puppet" end str += "- **Section**: %s\n" % object.section unless val == "" str += "- **Default**: %s\n" % val end str += "\n" end return str end config.header = " Specifying Configuration Parameters ----------------------------------- Every Puppet executable (with the exception of ``puppetdoc``) accepts all of the arguments below, but not all of the arguments make sense for every executable. Each argument has a section listed with it in parentheses; often, that section will map to an executable (e.g., ``puppetd``), in which case it probably only makes sense for that one executable. If ``puppet`` is listed as the section, it is most likely an option that is valid for everyone. This will not always be the case. I have tried to be as thorough as possible in the descriptions of the arguments, so it should be obvious whether an argument is appropriate or not. These arguments can be supplied to the executables either as command-line arugments or in the configuration file for the appropriate executable. For instance, the command-line invocation below would set the configuration directory to ``/private/puppet``:: $ puppetd --confdir=/private/puppet Note that boolean options are turned on and off with a slightly different syntax on the command line:: $ puppetd --storeconfigs $ puppetd --no-storeconfigs The invocations above will enable and disable, respectively, the storage of the client configuration. As mentioned above, the configuration parameters can also be stored in a configuration file located in the configuration directory (`/etc/puppet` by default). The file is named for the executable it is intended for, for example `/etc/puppetd.conf` is the configuration file for `puppetd`. The file, which follows INI-style formatting, should contain a bracketed heading named for the executable, followed by pairs of parameters with their values. Here is an example of a very simple ``puppetd.conf`` file:: [puppetd] confdir = /private/puppet storeconfigs = true Note that boolean parameters must be explicitly specified as `true` or `false` as seen above. If you're starting out with a fresh configuration, you may wish to let the executable generate a template configuration file for you by invoking the executable in question with the `--genconfig` command. The executable will print a template configuration to standard output, which can be redirected to a file like so:: $ puppetd --genconfig > /etc/puppet/puppetd.conf Note that this invocation will \"clobber\" (throw away) the contents of any pre-existing `puppetd.conf` file, so make a backup of your present config if it contains valuable information. Like the `--genconfig` argument, the executables also accept a `--genmanifest` argument, which will generate a manifest that can be used to manage all of Puppet's directories and files and prints it to standard output. This can likewise be redirected to a file:: $ puppetd --genmanifest > /etc/puppet/manifests/site.pp Puppet can also create user and group accounts for itself (one `puppet` group and one `puppet` user) if it is invoked as `root` with the `--mkusers` argument:: $ puppetd --mkusers Signals ------- The ``puppetd`` and ``puppetmasterd`` executables catch some signals for special handling. Both daemons catch (``SIGHUP``), which forces the server to restart tself. Predictably, interrupt and terminate (``SIGINT`` and ``SIGHUP``) will shut down the server, whether it be an instance of ``puppetd`` or ``puppetmasterd``. Sending the ``SIGUSR1`` signal to an instance of ``puppetd`` will cause it to immediately begin a new configuration transaction with the server. This signal has no effect on ``puppetmasterd``. Configuration Parameter Reference --------------------------------- Below is a list of all documented parameters. Not all of them are valid with all Puppet executables, but the executables will ignore any inappropriate values. " report = PuppetDoc.new :report do Puppet::Network::Handler.report.reportdocs end report.header = " Puppet clients can report back to the server after each transaction. This transaction report is sent as a YAML dump of the ``Puppet::Transaction::Report`` class and includes every log message that was generated during the transaction along with as many metrics as Puppet knows how to collect. See `ReportsAndReporting Reports and Reporting`:trac: for more information on how to use reports. Currently, clients default to not sending in reports; you can enable reporting by setting the ``report`` parameter to true. To use a report, set the ``reports`` parameter on the server; multiple reports must be comma-separated. You can also specify ``none`` to disable reports entirely. Puppet provides multiple report handlers that will process client reports: " function = PuppetDoc.new :function do Puppet::Parser::Functions.functiondocs end function.header = " There are two types of functions in Puppet: Statements and rvalues. Statements stand on their own and do not return arguments; they are used for performing stand-alone work like importing. Rvalues return values and can only be used in a statement requiring a value, such as an assignment or a case statement. Here are the functions available in Puppet: " type = PuppetDoc.new :type do types = {} Puppet::Type.loadall Puppet::Type.eachtype { |type| next if type.name == :puppet next if type.name == :component types[type.name] = type } str = %{ Metaparameters -------------- Metaparameters are parameters that work with any element; they are part of the Puppet framework itself rather than being part of the implementation of any given instance. Thus, any defined metaparameter can be used with any instance in your manifest, including defined components. Available Metaparameters ++++++++++++++++++++++++ } begin params = [] Puppet::Type.eachmetaparam { |param| params << param } params.sort { |a,b| a.to_s <=> b.to_s }.each { |param| str += paramwrap(param.to_s, scrub(Puppet::Type.metaparamdoc(param)), :level => 4) #puts "
" + param.to_s + "
" #puts tab(1) + Puppet::Type.metaparamdoc(param).scrub.indent($tab)gsub(/\n\s*/,' ') #puts "
" #puts indent(scrub(Puppet::Type.metaparamdoc(param)), $tab) #puts scrub(Puppet::Type.metaparamdoc(param)) #puts "
" #puts "" } rescue => detail puts detail.backtrace puts "incorrect metaparams: %s" % detail exit(1) end str += %{ Resource Types -------------- - The *namevar* is the parameter used to uniquely identify a type instance. This is the parameter that gets assigned when a string is provided before the colon in a type declaration. In general, only developers will need to worry about which parameter is the ``namevar``. In the following code:: file { "/etc/passwd": owner => root, group => root, mode => 644 } ``/etc/passwd`` is considered the title of the file object (used for things like dependency handling), and because ``path`` is the namevar for ``file``, that string is assigned to the ``path`` parameter. - *Features* are abilities that some providers might not support. You can use the list of supported features to determine how a given provider can be used. - *Parameters* determine the specific configuration of the instance. They either directly modify the system (internally, these are called properties) or they affect how the instance behaves (e.g., adding a search path for ``exec`` instances or determining recursion on ``file`` instances). - *Providers* provide low-level functionality for a given resource type. This is usually in the form of calling out to external commands. When required binaries are specified for providers, fully qualifed paths indicate that the binary must exist at that specific path and unqualified binaries indicate that Puppet will search for the binary using the shell path. Resource types define features they can use, and providers can be tested to see which features they provide. } types.sort { |a,b| a.to_s <=> b.to_s }.each { |name,type| str += " ---------------- " str += h(name, 3) str += scrub(type.doc) + "\n\n" # Handle the feature docs. if featuredocs = type.featuredocs str += h("Features", 4) str += featuredocs end docs = {} type.validproperties.sort { |a,b| a.to_s <=> b.to_s }.reject { |sname| property = type.propertybyname(sname) property.nodoc }.each { |sname| property = type.propertybyname(sname) unless property raise "Could not retrieve property %s on type %s" % [sname, type.name] end doc = nil unless doc = property.doc $stderr.puts "No docs for %s[%s]" % [type, sname] next end doc = doc.dup tmp = doc tmp = scrub(tmp) docs[sname] = tmp } str += h("Parameters", 4) + "\n" type.parameters.sort { |a,b| a.to_s <=> b.to_s }.each { |name,param| #docs[name] = indent(scrub(type.paramdoc(name)), $tab) docs[name] = scrub(type.paramdoc(name)) } docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each { |name, doc| namevar = type.namevar == name and name != :name str += paramwrap(name, doc, :namevar => namevar) } str += "\n" } str end if options[:all] options[:sections] = PuppetDoc.sections end case options[:mode] when :trac options[:sections].each do |name| section = PuppetDoc[name] or raise "Could not find section %s" % name unless options[:mode] == :pdf section.trac end end else text = ".. contents:: :depth: 1\n\n" options[:sections].sort { |a,b| a.to_s <=> b.to_s }.each do |name| section = PuppetDoc[name] # Add the per-section text, but with no ToC text += section.output(false) end text += PuppetDoc.footer # Replace the trac links, since they're invalid everywhere else text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 } if options[:mode] == :pdf PuppetDoc.pdf(text) else puts text end end # $Id: puppetdoc 2412 2007-04-24 20:47:08Z luke $