summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Pittman <daniel@puppetlabs.com>2011-05-06 11:08:35 -0700
committerDaniel Pittman <daniel@puppetlabs.com>2011-05-06 11:08:35 -0700
commitf80afbe72b848fe4ed81d8116d4eeb494aa6f61e (patch)
tree73c7f27f2ea2fb1668f7067ce05638f5064f540d /lib
parent1b12b55b6a2d3581f9643bf09d55727ba1213580 (diff)
parentb983386ece1b9816e6d3d59a316ad589e35773df (diff)
downloadpuppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.tar.gz
puppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.tar.xz
puppet-f80afbe72b848fe4ed81d8116d4eeb494aa6f61e.zip
Merge branch '2.7.x' into 2.7.next
Conflicts: * spec/unit/node/facts_spec.rb Updates: * spec/unit/interface/action{,_builder}_spec.rb => update for 'when_invoked' block being required.
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/application/face_base.rb130
-rw-r--r--lib/puppet/application/faces.rb50
-rw-r--r--lib/puppet/application/help.rb1
-rw-r--r--lib/puppet/application/plugin.rb3
-rw-r--r--lib/puppet/application/secret_agent.rb (renamed from lib/puppet/application/configurer.rb)4
-rw-r--r--lib/puppet/face/catalog.rb86
-rw-r--r--lib/puppet/face/catalog/select.rb34
-rw-r--r--lib/puppet/face/certificate.rb46
-rw-r--r--lib/puppet/face/certificate_request.rb32
-rw-r--r--lib/puppet/face/certificate_revocation_list.rb30
-rw-r--r--lib/puppet/face/config.rb36
-rw-r--r--lib/puppet/face/configurer.rb12
-rw-r--r--lib/puppet/face/facts.rb37
-rw-r--r--lib/puppet/face/file.rb20
-rw-r--r--lib/puppet/face/file/download.rb36
-rw-r--r--lib/puppet/face/file/store.rb12
-rw-r--r--lib/puppet/face/help.rb38
-rw-r--r--lib/puppet/face/help/action.erb49
-rw-r--r--lib/puppet/face/help/face.erb47
-rw-r--r--lib/puppet/face/help/global.erb3
-rw-r--r--lib/puppet/face/key.rb23
-rw-r--r--lib/puppet/face/node.rb27
-rw-r--r--lib/puppet/face/parser.rb41
-rw-r--r--lib/puppet/face/plugin.rb47
-rw-r--r--lib/puppet/face/report.rb40
-rw-r--r--lib/puppet/face/resource.rb23
-rw-r--r--lib/puppet/face/resource_type.rb17
-rw-r--r--lib/puppet/face/secret_agent.rb39
-rw-r--r--lib/puppet/face/status.rb30
-rw-r--r--lib/puppet/file_serving/fileset.rb2
-rw-r--r--lib/puppet/indirector/face.rb (renamed from lib/puppet/face/indirector.rb)51
-rw-r--r--lib/puppet/indirector/request.rb51
-rw-r--r--lib/puppet/interface.rb60
-rw-r--r--lib/puppet/interface/action.rb101
-rw-r--r--lib/puppet/interface/action_builder.rb31
-rw-r--r--lib/puppet/interface/action_manager.rb4
-rw-r--r--lib/puppet/interface/documentation.rb197
-rw-r--r--lib/puppet/interface/face_collection.rb22
-rw-r--r--lib/puppet/interface/option.rb8
-rw-r--r--lib/puppet/interface/option_manager.rb29
-rw-r--r--lib/puppet/network/formats.rb36
-rw-r--r--lib/puppet/network/http/api/v1.rb2
-rw-r--r--lib/puppet/network/rest_authconfig.rb1
-rw-r--r--lib/puppet/node.rb23
-rwxr-xr-xlib/puppet/node/facts.rb20
-rw-r--r--lib/puppet/parser/templatewrapper.rb2
-rw-r--r--lib/puppet/resource/catalog.rb14
-rw-r--r--lib/puppet/status.rb2
-rw-r--r--lib/puppet/transaction/event_manager.rb17
-rwxr-xr-xlib/puppet/type/user.rb2
-rw-r--r--lib/puppet/type/whit.rb8
-rw-r--r--lib/puppet/util/command_line.rb9
52 files changed, 1319 insertions, 366 deletions
diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb
index 7bebd18bb..7a5ce3400 100644
--- a/lib/puppet/application/face_base.rb
+++ b/lib/puppet/application/face_base.rb
@@ -15,8 +15,8 @@ class Puppet::Application::FaceBase < Puppet::Application
Puppet::Util::Log.level = :info
end
- option("--render-as FORMAT") do |arg|
- @render_as = arg.to_sym
+ option("--render-as FORMAT") do |format|
+ self.render_as = format.to_sym
end
option("--mode RUNMODE", "-r") do |arg|
@@ -27,55 +27,23 @@ class Puppet::Application::FaceBase < Puppet::Application
attr_accessor :face, :action, :type, :arguments, :render_as
- attr_writer :exit_code
- # This allows you to set the exit code if you don't want to just exit
- # immediately but you need to indicate a failure.
- def exit_code
- @exit_code || 0
+ def render_as=(format)
+ if format == :json then
+ @render_as = Puppet::Network::FormatHandler.format(:pson)
+ else
+ @render_as = Puppet::Network::FormatHandler.format(format)
+ end
+ @render_as or raise ArgumentError, "I don't know how to render '#{format}'"
end
def render(result)
- format = render_as || action.render_as || :for_humans
-
# Invoke the rendering hook supplied by the user, if appropriate.
- if hook = action.when_rendering(format) then
+ if hook = action.when_rendering(render_as.name)
result = hook.call(result)
end
- if format == :for_humans then
- render_for_humans(result)
- else
- render_method = Puppet::Network::FormatHandler.format(format).render_method
- if render_method == "to_pson"
- PSON::pretty_generate(result, :allow_nan => true, :max_nesting => false)
- else
- result.send(render_method)
- end
- end
- end
-
- def render_for_humans(result)
- # String to String
- return result if result.is_a? String
- return result if result.is_a? Numeric
-
- # Simple hash to table
- if result.is_a? Hash and result.keys.all? { |x| x.is_a? String or x.is_a? Numeric }
- output = ''
- column_a = result.map do |k,v| k.to_s.length end.max + 2
- column_b = 79 - column_a
- result.sort_by { |k,v| k.to_s } .each do |key, value|
- output << key.to_s.ljust(column_a)
- output << PP.pp(value, '', column_b).
- chomp.gsub(/\n */) { |x| x + (' ' * column_a) }
- output << "\n"
- end
- return output
- end
-
- # ...or pretty-print the inspect outcome.
- return result.pretty_inspect
+ render_as.render(result)
end
def preinit
@@ -133,8 +101,13 @@ class Puppet::Application::FaceBase < Puppet::Application
end
if @action.nil?
- @action = @face.get_default_action()
- @is_default_action = true
+ if @action = @face.get_default_action() then
+ @is_default_action = true
+ else
+ Puppet.err "#{face.name} does not have a default action, and no action was given"
+ Puppet.err Puppet::Face[:help, :current].help(@face.name)
+ exit false
+ end
end
# Now we can interact with the default option code to build behaviour
@@ -142,7 +115,7 @@ class Puppet::Application::FaceBase < Puppet::Application
@action.options.each do |option|
option = @action.get_option(option) # make it the object.
self.class.option(*option.optparse) # ...and make the CLI parse it.
- end if @action
+ end
# ...and invoke our parent to parse all the command line options.
super
@@ -194,23 +167,66 @@ class Puppet::Application::FaceBase < Puppet::Application
# would invoke the action with options set as global state in the
# interface object. --daniel 2011-03-28
@arguments << options
+
+ # If we don't have a rendering format, set one early.
+ self.render_as ||= (@action.render_as || :console)
end
def main
+ status = false
+
# Call the method associated with the provided action (e.g., 'find').
- if @action
- result = @face.send(@action.name, *arguments)
- puts render(result) unless result.nil?
- else
- if arguments.first.is_a? Hash
- puts "#{@face} does not have a default action"
- else
- puts "#{@face} does not respond to action #{arguments.first}"
- end
+ unless @action
+ puts Puppet::Face[:help, :current].help(@face.name)
+ raise "#{face} does not respond to action #{arguments.first}"
+ end
- puts Puppet::Face[:help, :current].help(@face.name, *arguments)
+ # We need to do arity checking here because this is generic code
+ # calling generic methods – that have argument defaulting. We need to
+ # make sure we don't accidentally pass the options as the first
+ # argument to a method that takes one argument. eg:
+ #
+ # puppet facts find
+ # => options => {}
+ # @arguments => [{}]
+ # => @face.send :bar, {}
+ #
+ # def face.bar(argument, options = {})
+ # => bar({}, {}) # oops! we thought the options were the
+ # # positional argument!!
+ #
+ # We could also fix this by making it mandatory to pass the options on
+ # every call, but that would make the Ruby API much more annoying to
+ # work with; having the defaulting is a much nicer convention to have.
+ #
+ # We could also pass the arguments implicitly, by having a magic
+ # 'options' method that was visible in the scope of the action, which
+ # returned the right stuff.
+ #
+ # That sounds attractive, but adds complications to all sorts of
+ # things, especially when you think about how to pass options when you
+ # are writing Ruby code that calls multiple faces. Especially if
+ # faces are involved in that. ;)
+ #
+ # --daniel 2011-04-27
+ if (arity = @action.positional_arg_count) > 0
+ unless (count = arguments.length) == arity then
+ s = arity == 2 ? '' : 's'
+ raise ArgumentError, "puppet #{@face.name} #{@action.name} takes #{arity-1} argument#{s}, but you gave #{count-1}"
+ end
end
- exit(exit_code)
+
+ result = @face.send(@action.name, *arguments)
+ puts render(result) unless result.nil?
+ status = true
+
+ rescue Exception => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err detail.to_s
+ Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage"
+
+ ensure
+ exit status
end
end
diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb
index 3dd3f0312..e7fce66b1 100644
--- a/lib/puppet/application/faces.rb
+++ b/lib/puppet/application/faces.rb
@@ -10,16 +10,54 @@ class Puppet::Application::Faces < Puppet::Application
Puppet::Util::Log.level = :debug
end
- option("--help", "-h") do |arg|
- puts "Usage: puppet faces [actions|terminuses]
-Lists all available faces, and by default includes all available terminuses and actions.
-"
- end
-
option("--verbose", "-v") do
Puppet::Util::Log.level = :info
end
+ def help
+ <<-HELP
+puppet-faces(8) -- List available Faces and actions
+========
+
+SYNOPSIS
+--------
+Lists the available subcommands (with applicable terminuses and/or actions)
+provided by the Puppet Faces API. This information is automatically read
+from the Puppet code present on the system. By default, the output includes
+all terminuses and actions.
+
+USAGE
+-----
+puppet faces [-d|--debug] [-v|--verbose] [actions|terminuses]
+
+OPTIONS
+-------
+Note that any configuration option valid in the configuration file is also
+a valid long argument. See the configuration file documentation at
+http://docs.puppetlabs.com/references/stable/configuration.html for the
+full list of acceptable parameters. A commented list of all
+configuration options can also be generated by running puppet agent with
+'--genconfig'.
+
+* --verbose:
+ Sets the log level to "info." This option has no tangible effect at the time
+ of this writing.
+
+* --debug:
+ Sets the log level to "debug." This option has no tangible effect at the time
+ of this writing.
+
+AUTHOR
+------
+Puppet Labs
+
+COPYRIGHT
+---------
+Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License
+
+ HELP
+ end
+
def list(*arguments)
if arguments.empty?
arguments = %w{terminuses actions}
diff --git a/lib/puppet/application/help.rb b/lib/puppet/application/help.rb
index 0d7767632..4829a2036 100644
--- a/lib/puppet/application/help.rb
+++ b/lib/puppet/application/help.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require 'puppet/application/face_base'
class Puppet::Application::Help < Puppet::Application::FaceBase
diff --git a/lib/puppet/application/plugin.rb b/lib/puppet/application/plugin.rb
new file mode 100644
index 000000000..2d0402e43
--- /dev/null
+++ b/lib/puppet/application/plugin.rb
@@ -0,0 +1,3 @@
+require 'puppet/application/face_base'
+class Puppet::Application::Plugin < Puppet::Application::FaceBase
+end
diff --git a/lib/puppet/application/configurer.rb b/lib/puppet/application/secret_agent.rb
index 6e86cd2d4..d704d14b2 100644
--- a/lib/puppet/application/configurer.rb
+++ b/lib/puppet/application/secret_agent.rb
@@ -1,7 +1,7 @@
require 'puppet/application'
require 'puppet/face'
-class Puppet::Application::Configurer < Puppet::Application
+class Puppet::Application::Secret_agent < Puppet::Application
should_parse_config
run_mode :agent
@@ -17,7 +17,7 @@ class Puppet::Application::Configurer < Puppet::Application
end
def run_command
- report = Puppet::Face[:configurer, '0.0.1'].synchronize(Puppet[:certname])
+ report = Puppet::Face[:secret_agent, '0.0.1'].synchronize(Puppet[:certname])
Puppet::Face[:report, '0.0.1'].submit(report)
end
end
diff --git a/lib/puppet/face/catalog.rb b/lib/puppet/face/catalog.rb
index 0dcde3591..4624313bc 100644
--- a/lib/puppet/face/catalog.rb
+++ b/lib/puppet/face/catalog.rb
@@ -1,8 +1,56 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
+
+Puppet::Indirector::Face.define(:catalog, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Compile, save, view, and convert catalogs."
+ description <<-EOT
+ This face primarily interacts with the compiling subsystem. By default,
+ it compiles a catalog using the default manifest and the hostname from
+ `certname`, but you can choose to retrieve a catalog from the server by
+ specifying `--terminus rest`. You can also choose to print any catalog
+ in 'dot' format (for easy graph viewing with OmniGraffle or Graphviz)
+ with '--render-as dot'.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `active_record`
+ * `compiler`
+ * `queue`
+ * `rest`
+ * `yaml`
+ EOT
-Puppet::Face::Indirector.define(:catalog, '0.0.1') do
action(:apply) do
- when_invoked do |catalog, options|
+ summary "Apply a Puppet::Resource::Catalog object"
+ description <<-EOT
+ Applies a catalog object retrieved with the `download` action. This
+ action cannot consume a serialized catalog, and is not intended for
+ command-line use."
+ EOT
+ notes <<-EOT
+ This action returns a Puppet::Transaction::Report object.
+ EOT
+ examples <<-EOT
+ From `secret_agent.rb`:
+
+ Puppet::Face[:plugin, '0.0.1'].download
+
+ facts = Puppet::Face[:facts, '0.0.1'].find(certname)
+ catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts)
+ report = Puppet::Face[:catalog, '0.0.1'].apply(catalog)
+
+ Puppet::Face[:report, '0.0.1'].submit(report)
+ EOT
+
+ when_invoked do |options|
+ catalog = Puppet::Face[:catalog, "0.0.1"].find(Puppet[:certname]) or raise "Could not find catalog for #{Puppet[:certname]}"
+ catalog = catalog.to_ral
+
report = Puppet::Transaction::Report.new("apply")
report.configuration_version = catalog.version
@@ -23,18 +71,38 @@ Puppet::Face::Indirector.define(:catalog, '0.0.1') do
end
action(:download) do
- when_invoked do |certname, facts, options|
+ summary "Download this node's catalog from the puppet master server"
+ description <<-EOT
+ Retrieves a catalog from the puppet master. Unlike the `find` action,
+ `download` submits facts to the master as part of the request. This
+ action is not intended for command-line use.
+ EOT
+ notes "This action returns a Puppet::Resource::Catalog object."
+ examples <<-EOT
+ From `secret_agent.rb`:
+
+ Puppet::Face[:plugin, '0.0.1'].download
+
+ facts = Puppet::Face[:facts, '0.0.1'].find(certname)
+ catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts)
+ report = Puppet::Face[:catalog, '0.0.1'].apply(catalog)
+
+ Puppet::Face[:report, '0.0.1'].submit(report)
+ EOT
+ when_invoked do |options|
Puppet::Resource::Catalog.indirection.terminus_class = :rest
- facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))}
+ Puppet::Resource::Catalog.indirection.cache_class = nil
catalog = nil
retrieval_duration = thinmark do
- catalog = Puppet::Face[:catalog, '0.0.1'].find(certname, facts_to_upload)
+ catalog = Puppet::Face[:catalog, '0.0.1'].find(Puppet[:certname])
end
- catalog = catalog.to_ral
- catalog.finalize
catalog.retrieval_duration = retrieval_duration
catalog.write_class_file
- catalog
+
+ Puppet::Resource::Catalog.indirection.terminus_class = :yaml
+ Puppet::Face[:catalog, "0.0.1"].save(catalog)
+ Puppet.notice "Saved catalog for #{Puppet[:certname]} to yaml"
+ nil
end
end
end
diff --git a/lib/puppet/face/catalog/select.rb b/lib/puppet/face/catalog/select.rb
index ba27117bc..a6c97a627 100644
--- a/lib/puppet/face/catalog/select.rb
+++ b/lib/puppet/face/catalog/select.rb
@@ -1,10 +1,42 @@
# Select and show a list of resources of a given type.
Puppet::Face.define(:catalog, '0.0.1') do
action :select do
+ summary "Select and show a list of resources of a given type"
+ description <<-EOT
+ Retrieves a catalog for the specified host and returns an array of
+ resources of the given type. This action is not intended for
+ command-line use.
+ EOT
+ notes <<-NOTES
+ The type name for this action must be given in its capitalized form.
+ That is, calling `catalog select mynode file` will return an empty
+ array, whereas calling it with 'File' will return a list of the node's
+ file resources.
+
+ By default, this action will retrieve a catalog from Puppet's compiler
+ subsystem; you must call the action with `--terminus rest` if you wish
+ to retrieve a catalog from the puppet master.
+ NOTES
when_invoked do |host, type, options|
+ # REVISIT: Eventually, type should have a default value that triggers
+ # the non-specific behaviour. For now, though, this will do.
+ # --daniel 2011-05-03
catalog = Puppet::Resource::Catalog.indirection.find(host)
- catalog.resources.reject { |res| res.type != type }.each { |res| puts res }
+ if type == '*'
+ catalog.resources
+ else
+ type = type.downcase
+ catalog.resources.reject { |res| res.type.downcase != type }
+ end
+ end
+
+ when_rendering :console do |value|
+ if value.nil? then
+ "no matching resources found"
+ else
+ value.map {|x| x.to_s }.join("\n")
+ end
end
end
end
diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb
index 4c2950fb3..ee2b2873f 100644
--- a/lib/puppet/face/certificate.rb
+++ b/lib/puppet/face/certificate.rb
@@ -1,15 +1,51 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
require 'puppet/ssl/host'
-Puppet::Face::Indirector.define(:certificate, '0.0.1') do
+Puppet::Indirector::Face.define(:certificate, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Provide access to the CA for certificate management"
+ description <<-EOT
+ This face interacts with a local or remote Puppet certificate
+ authority. Currently, its behavior is not a full superset of puppet
+ cert; specifically, it is unable to mimic puppet cert's "clean" option,
+ and its "generate" action submits a CSR rather than creating a
+ signed certificate.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `ca`
+ * `file`
+ * `rest`
+ EOT
+
option "--ca-location LOCATION" do
+ summary "The certificate authority to query"
+ description <<-EOT
+ Whether to act on the local certificate authority or one provided by a
+ remote puppet master. Allowed values are 'local' and 'remote.'
+ EOT
+
before_action do |action, args, options|
Puppet::SSL::Host.ca_location = options[:ca_location].to_sym
end
end
action :generate do
- summary "Generate a new Certificate Signing Request for HOST"
+ summary "Generate a new certificate signing request for HOST"
+ description <<-EOT
+ Generates and submits a certificate signing request (CSR) for the
+ provided host identifier. This CSR will then have to be signed by a user
+ with the proper authorization on the certificate authority.
+
+ Puppet agent handles CSR submission automatically. This action is
+ primarily useful for requesting certificates for individual users and
+ external applications.
+ EOT
when_invoked do |name, options|
host = Puppet::SSL::Host.new(name)
@@ -19,7 +55,7 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do
end
action :list do
- summary "List all Certificate Signing Requests"
+ summary "List all certificate signing requests"
when_invoked do |options|
Puppet::SSL::Host.indirection.search("*", {
@@ -29,7 +65,7 @@ Puppet::Face::Indirector.define(:certificate, '0.0.1') do
end
action :sign do
- summary "Sign a Certificate Signing Request for HOST"
+ summary "Sign a certificate signing request for HOST"
when_invoked do |name, options|
host = Puppet::SSL::Host.new(name)
diff --git a/lib/puppet/face/certificate_request.rb b/lib/puppet/face/certificate_request.rb
index 1feba25ab..cc6021517 100644
--- a/lib/puppet/face/certificate_request.rb
+++ b/lib/puppet/face/certificate_request.rb
@@ -1,4 +1,32 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
-Puppet::Face::Indirector.define(:certificate_request, '0.0.1') do
+Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Manage certificate requests."
+ description <<-EOT
+ Retrieves and submits certificate signing requests (CSRs). Invoke
+ `search` with an unread key to retrieve all outstanding CSRs, invoke
+ `find` with a node certificate name to retrieve a specific request, and
+ invoke `save` to submit a CSR.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `ca`
+ * `file`
+ * `rest`
+ EOT
+ examples <<-EOT
+ Retrieve all CSRs from the local CA:
+
+ puppet certificate_request search no_key --terminus ca
+
+ Retrieve a single CSR from the puppet master's CA:
+
+ puppet certificate_request find mynode.puppetlabs.lan --terminus rest
+ EOT
end
diff --git a/lib/puppet/face/certificate_revocation_list.rb b/lib/puppet/face/certificate_revocation_list.rb
index 6a75aa578..2722b20f2 100644
--- a/lib/puppet/face/certificate_revocation_list.rb
+++ b/lib/puppet/face/certificate_revocation_list.rb
@@ -1,4 +1,30 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
-Puppet::Face::Indirector.define(:certificate_revocation_list, '0.0.1') do
+Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Manage the list of revoked certificates."
+ description <<-EOT
+ This face is primarily for retrieving the certificate revocation
+ list from the CA. Although it exposes search/save/destroy methods,
+ they shouldn't be used under normal circumstances.
+ EOT
+ notes <<-EOT
+ Although the find action must be given an argument, this argument is
+ never read, and can contain the descriptive text of your choice.
+
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `ca`
+ * `file`
+ * `rest`
+ EOT
+ examples <<-EXAMPLES
+ Retrieve the CRL:
+
+ puppet certificate_revocation_list find crl
+ EXAMPLES
end
diff --git a/lib/puppet/face/config.rb b/lib/puppet/face/config.rb
index 45cb6b156..9ca41085e 100644
--- a/lib/puppet/face/config.rb
+++ b/lib/puppet/face/config.rb
@@ -1,7 +1,43 @@
require 'puppet/face'
Puppet::Face.define(:config, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Interact with Puppet configuration options"
+
action(:print) do
+ summary "Examine Puppet's current configuration options"
+ description <<-EOT
+ Prints the value of a single configuration option or a list of
+ configuration options.
+
+ This action is an alternate interface to the information available with
+ `puppet agent --configprint`.
+ EOT
+ notes <<-EOT
+ The return data of this action varies depending on its arguments. When
+ called with "all," `print` will return a complete list of option names
+ and values. When called with a single configuration option name, it will
+ return the value of that option. When called with a list of
+ configuration option names, it will return the corresponding list of
+ option names and values.
+
+ By default, this action retrieves its configuration information in agent
+ mode. To examine the master's configuration, supply Puppet's global
+ `--mode master` option. To examine configurations from a specific
+ environment, you can use the `--environment` option.
+ EOT
+ examples <<-EOT
+ Get puppet's runfile directory:
+
+ puppet config print rundir
+
+ Get a list of important directories from the master's config:
+
+ puppet config print all --mode master | grep -E "(path|dir)"
+ EOT
+
when_invoked do |*args|
options = args.pop
Puppet.settings[:configprint] = args.join(",")
diff --git a/lib/puppet/face/configurer.rb b/lib/puppet/face/configurer.rb
deleted file mode 100644
index 74dfb854e..000000000
--- a/lib/puppet/face/configurer.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'puppet/face'
-
-Puppet::Face.define(:configurer, '0.0.1') do
- action(:synchronize) do
- when_invoked do |certname, options|
- facts = Puppet::Face[:facts, '0.0.1'].find(certname)
- catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts)
- report = Puppet::Face[:catalog, '0.0.1'].apply(catalog)
- report
- end
- end
-end
diff --git a/lib/puppet/face/facts.rb b/lib/puppet/face/facts.rb
index 04eab93a5..ecf4e371e 100644
--- a/lib/puppet/face/facts.rb
+++ b/lib/puppet/face/facts.rb
@@ -1,9 +1,40 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
require 'puppet/node/facts'
-Puppet::Face::Indirector.define(:facts, '0.0.1') do
- # Upload our facts to the server
+Puppet::Indirector::Face.define(:facts, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Retrieve, store, and view facts."
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `active_record`
+ * `couch`
+ * `facter`
+ * `inventory_active_record`
+ * `memory`
+ * `network_device`
+ * `rest`
+ * `yaml`
+ EOT
+
action(:upload) do
+ summary "Upload our facts to the puppet master."
+ description <<-EOT
+ Retrieves facts for the local system and saves them to the puppet master
+ server. This is essentially a shortcut action: it calls the `find`
+ action with the facter terminus, then passes the returned facts object
+ to the `save` action, which uses the rest terminus.
+ EOT
+ notes <<-EOT
+ This action uses the save action, which requires the puppet master's
+ auth.conf to allow save access to the `facts` REST terminus. See
+ `http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more details.
+ EOT
+
render_as :yaml
when_invoked do |options|
diff --git a/lib/puppet/face/file.rb b/lib/puppet/face/file.rb
index 1aa9462dd..707ceafd4 100644
--- a/lib/puppet/face/file.rb
+++ b/lib/puppet/face/file.rb
@@ -1,5 +1,21 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
+
+Puppet::Indirector::Face.define(:file, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Retrieve and store files in a filebucket"
+ # TK this needs a description of how to find files in a filebucket, and
+ # some good use cases for retrieving/storing them. I can't write either
+ # of these yet.
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `file`
+ * `rest`
+ EOT
-Puppet::Face::Indirector.define(:file, '0.0.1') do
set_indirection_name :file_bucket_file
end
diff --git a/lib/puppet/face/file/download.rb b/lib/puppet/face/file/download.rb
new file mode 100644
index 000000000..f5413d493
--- /dev/null
+++ b/lib/puppet/face/file/download.rb
@@ -0,0 +1,36 @@
+# Download a specified file into the local filebucket.
+Puppet::Face.define(:file, '0.0.1') do
+ action :download do |*args|
+ when_invoked do |sum, options|
+ if sum =~ /^puppet:\/\// # it's a puppet url
+ require 'puppet/file_serving'
+ require 'puppet/file_serving/content'
+ raise "Could not find metadata for #{sum}" unless content = Puppet::FileServing::Content.indirection.find(sum)
+ file = Puppet::FileBucket::File.new(content.content)
+ else
+ tester = Object.new
+ tester.extend(Puppet::Util::Checksums)
+
+ type = tester.sumtype(sum)
+ sumdata = tester.sumdata(sum)
+
+ key = "#{type}/#{sumdata}"
+
+ Puppet::FileBucket::File.indirection.terminus_class = :file
+ if Puppet::FileBucket::File.indirection.find(key)
+ Puppet.info "Content for '#{sum}' already exists"
+ return
+ end
+
+ Puppet::FileBucket::File.indirection.terminus_class = :rest
+ raise "Could not download content for '#{sum}'" unless file = Puppet::FileBucket::File.indirection.find(key)
+ end
+
+
+ Puppet::FileBucket::File.indirection.terminus_class = :file
+ Puppet.notice "Saved #{sum} to filebucket"
+ Puppet::FileBucket::File.indirection.save file
+ return nil
+ end
+ end
+end
diff --git a/lib/puppet/face/file/store.rb b/lib/puppet/face/file/store.rb
new file mode 100644
index 000000000..4c9523b6c
--- /dev/null
+++ b/lib/puppet/face/file/store.rb
@@ -0,0 +1,12 @@
+# Store a specified file in our filebucket.
+Puppet::Face.define(:file, '0.0.1') do
+ action :store do |*args|
+ when_invoked do |path, options|
+ file = Puppet::FileBucket::File.new(File.read(path))
+
+ Puppet::FileBucket::File.indirection.terminus_class = :file
+ Puppet::FileBucket::File.indirection.save file
+ file.checksum
+ end
+ end
+end
diff --git a/lib/puppet/face/help.rb b/lib/puppet/face/help.rb
index a762fb02e..aef917447 100644
--- a/lib/puppet/face/help.rb
+++ b/lib/puppet/face/help.rb
@@ -4,13 +4,16 @@ require 'pathname'
require 'erb'
Puppet::Face.define(:help, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
summary "Displays help about puppet subcommands"
action(:help) do
summary "Display help about faces and their actions."
option "--version VERSION" do
- desc "Which version of the interface to show help for"
+ summary "which version of the interface to show help for"
end
default
@@ -21,29 +24,20 @@ Puppet::Face.define(:help, '0.0.1') do
options = args.pop
if options.nil? or args.length > 2 then
if args.select { |x| x == 'help' }.length > 2 then
- c = "\n !\"'),-./7:;<GIJLST\\_`abcdefhiklmnoprstuwx|}".split('')
+ c = "\n %'(),-./=ADEFHILORSTUXY\\_`gnv|".split('')
i = <<-'EOT'.gsub(/\s*/, '').to_i(36)
- 2s7ytxy5vpj74kbab5xzf1ik2roinzlefaspjrzckiert5xbaxvwlku3a91w7y1rsd
- nenp51gwpulmnrp54nwdil36fjgjarab801y0r5a9nh1hdfgi99arn5c5t3zhxbvzi
- u6wx5r1tb7lun7pro69nrxunqqixsh6qmmv0ms0i0yycqw3pystyzmiita0lpxynqs
- qkbjwadcx82n76wwpzbht8i8rgvqhqick8mk3cs3rvwdjookpgu0rxw4tcezned5sq
- z5x8z9vntyyz0s4h6hjhtwtbytsmmu7ltvdftaixc7fkt276sqm48ab4yv0ot9y26n
- z0xniy4pfl1x300lt6h9c8of49vf799ieuxwnoycsjlmtd4qntzit524j0tdn6n5aj
- mq3z10apjuhkzprvmu53z1gnacymnoforrz5mbqto062kckgw5463pxwzg8liglub4
- ubnr0dln1s6iy3ummxuhim7m5a7yedl3gyy6ow4qqtmsigv27lysooau24zpsccsvx
- ddwygjprqpbwon7i9s1279m1fpinvva8mfh6bgmotrpxsh1c8rc83l3u0utf5i200y
- l7ui0ngcbcjyr4erzdee2tqk3fpjvb82t8xhncruhgn7j5dh2m914qzhb0gkoom47k
- 6et7rp4tqjnrv0y2apk5qdl1x1hnbkkxup5ys6ip2ksmtpd3ipmrdtswxr5xwfiqtm
- 60uyjr1v79irhnkrbbt4fwhgqjby1qflgwt9c1wpayzzucep6npgbn3f1k6cn4pug3
- 1u02wel4tald4hij8m5p49xr8u4ero1ucs5uht42o8nhpmpe7c7xf9t85i85m9m5kk
- tgoqkgbu52gy5aoteyp8jkm3vri9fnkmwa5h60zt8otja72joxjb40p2rz2vp8f8q9
- nnggxt3x90pe5u4048ntyuha78q1oikhhpvw9j083yc3l00hz5ehv9c1au5gvctyap
- zprub289qruve9qsyuh75j04wzkemqw3uhisrfs92u1ahv2qlqxmorgob16c1vbqkx
- ttkoyp2agkt0v5l7lec25p0jqun9y39k41h67aeb5ihiqsftxc9azmg31hc73dk8ur
- lj88vgbmgt8yln9rchw60whgxvnv9zn6cxbr482svctswc5a07atj
+ 3he6737w1aghshs6nwrivl8mz5mu9nywg9tbtlt081uv6fq5kvxse1td3tj1wvccmte806nb
+ cy6de2ogw0fqjymbfwi6a304vd56vlq71atwmqsvz3gpu0hj42200otlycweufh0hylu79t3
+ gmrijm6pgn26ic575qkexyuoncbujv0vcscgzh5us2swklsp5cqnuanlrbnget7rt3956kam
+ j8adhdrzqqt9bor0cv2fqgkloref0ygk3dekiwfj1zxrt13moyhn217yy6w4shwyywik7w0l
+ xtuevmh0m7xp6eoswin70khm5nrggkui6z8vdjnrgdqeojq40fya5qexk97g4d8qgw0hvokr
+ pli1biaz503grqf2ycy0ppkhz1hwhl6ifbpet7xd6jjepq4oe0ofl575lxdzjeg25217zyl4
+ nokn6tj5pq7gcdsjre75rqylydh7iia7s3yrko4f5ud9v8hdtqhu60stcitirvfj6zphppmx
+ 7wfm7i9641d00bhs44n6vh6qvx39pg3urifgr6ihx3e0j1ychzypunyou7iplevitkyg6gbg
+ wm08oy1rvogcjakkqc1f7y1awdfvlb4ego8wrtgu9vzw4vmj59utwifn2ejcs569dh1oaavi
+ sc581n7jjg1dugzdu094fdobtx6rsvk3sfctvqnr36xctold
EOT
- 607.times{i,x=i.divmod(1035);a,b=x.divmod(23);print(c[a]*b)}
- raise ArgumentError, "Such panic is really not required."
+ 353.times{i,x=i.divmod(1184);a,b=x.divmod(37);print(c[a]*b)}
end
raise ArgumentError, "help only takes two (optional) arguments, a face name, and an action"
end
diff --git a/lib/puppet/face/help/action.erb b/lib/puppet/face/help/action.erb
index eaf131464..c26958a35 100644
--- a/lib/puppet/face/help/action.erb
+++ b/lib/puppet/face/help/action.erb
@@ -1,3 +1,48 @@
-Use: puppet <%= face.name %> [options] <%= action.name %> [options]
+puppet <%= face.name %><%= action.default? ? '' : " #{action.name}" %>(1) -- <%= action.summary || face.summary %>
+<%= '=' * (_erbout.length - 1) %>
-Summary: <%= action.summary %>
+% if action.synopsis
+SYNOPSIS
+--------
+
+<%= action.synopsis %>
+
+% end
+% if action.description
+DESCRIPTION
+-----------
+<%= action.description %>
+
+%end
+% unless action.options.empty?
+OPTIONS
+-------
+% action.options.sort.each do |name|
+% option = action.get_option name
+<%= " " + option.optparse.join(" |" ) %>
+<%= option.summary %>
+<%= option.description %>
+
+% end
+% end
+% if action.examples
+EXAMPLES
+--------
+<%= action.examples %>
+% end
+% if action.notes
+NOTES
+-----
+<%= action.notes %>
+
+% end
+% unless action.authors.empty?
+AUTHOR
+------
+<%= action.authors.map {|x| " * " + x } .join("\n") %>
+
+%end
+COPYRIGHT AND LICENSE
+---------------------
+<%= action.copyright %>
+<%= action.license %>
diff --git a/lib/puppet/face/help/face.erb b/lib/puppet/face/help/face.erb
index efe5fd809..b249981de 100644
--- a/lib/puppet/face/help/face.erb
+++ b/lib/puppet/face/help/face.erb
@@ -1,7 +1,48 @@
-Use: puppet <%= face.name %> [options] <action> [options]
+NAME
+ <%= face.name %> -- <%= face.summary || "unknown face..." %>
-Available actions:
+% if face.synopsis
+SYNOPSIS
+<%= face.synopsis.gsub(/^/, ' ') %>
+
+% end
+% if face.description
+DESCRIPTION
+<%= face.description.chomp.gsub(/^/, ' ') %>
+
+%end
+% unless face.options.empty?
+OPTIONS
+% face.options.sort.each do |name|
+% option = face.get_option name
+<%= " " + option.optparse.join(" |" ) %>
+<%= option.summary %>
+<%= option.description %>
+
+% end
+% end
+ACTIONS
+% padding = face.actions.map{|x| x.to_s.length}.max + 2
% face.actions.each do |actionname|
% action = face.get_action(actionname)
- <%= action.name.to_s.ljust(16) %> <%= action.summary %>
+ <%= action.name.to_s.ljust(padding) %> <%= action.summary %>
% end
+
+% if face.examples
+EXAMPLES
+<%= face.examples %>
+% end
+% if face.notes
+NOTES
+<%= face.notes %>
+
+% end
+% unless face.authors.empty?
+AUTHOR
+<%= face.authors.join("\n").gsub(/^/, ' * ') %>
+
+%end
+COPYRIGHT AND LICENSE
+<%= face.copyright.gsub(/^/, ' ') %>
+<%= face.license.gsub(/^/, ' ') %>
+
diff --git a/lib/puppet/face/help/global.erb b/lib/puppet/face/help/global.erb
index f4c761b2b..80c77ad26 100644
--- a/lib/puppet/face/help/global.erb
+++ b/lib/puppet/face/help/global.erb
@@ -1,4 +1,4 @@
-puppet <subcommand> [options] <action> [options]
+Usage: puppet <subcommand> [options] <action> [options]
Available subcommands, from Puppet Faces:
% Puppet::Face.faces.sort.each do |name|
@@ -16,5 +16,4 @@ Available applications, soon to be ported to Faces:
See 'puppet help <subcommand> <action>' for help on a specific subcommand action.
See 'puppet help <subcommand>' for help on a specific subcommand.
-See 'puppet man <subcommand>' for the full man page.
Puppet v<%= Puppet::PUPPETVERSION %>
diff --git a/lib/puppet/face/key.rb b/lib/puppet/face/key.rb
index 3a11ddb03..67d775ca4 100644
--- a/lib/puppet/face/key.rb
+++ b/lib/puppet/face/key.rb
@@ -1,4 +1,23 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
+
+Puppet::Indirector::Face.define(:key, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Create, save, and remove certificate keys"
+
+ description <<-EOT
+ Keys are created for you automatically when certificate requests are
+ generated with 'puppet certificate generate'. You should not have to use
+ this action directly from the command line.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `ca`
+ * `file`
+ EOT
-Puppet::Face::Indirector.define(:key, '0.0.1') do
end
diff --git a/lib/puppet/face/node.rb b/lib/puppet/face/node.rb
index 12336df8f..be38ad388 100644
--- a/lib/puppet/face/node.rb
+++ b/lib/puppet/face/node.rb
@@ -1,3 +1,26 @@
-require 'puppet/face/indirector'
-Puppet::Face::Indirector.define(:node, '0.0.1') do
+require 'puppet/indirector/face'
+Puppet::Indirector::Face.define(:node, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "View and manage node definitions"
+
+ description <<-EOT
+ This face interacts with node objects, which are what Puppet uses to
+ build a catalog. A node object consists of the node's facts,
+ environment, additional top-scope variables, and classes.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `active_record`
+ * `exec`
+ * `ldap`
+ * `memory`
+ * `plain`
+ * `rest`
+ * `yaml`
+ EOT
end
diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb
index d4aaaf043..e6a9503dd 100644
--- a/lib/puppet/face/parser.rb
+++ b/lib/puppet/face/parser.rb
@@ -2,19 +2,30 @@ require 'puppet/face'
require 'puppet/parser'
Puppet::Face.define(:parser, '0.0.1') do
- action :validate do
- when_invoked do |*args|
- args.pop
- files = args
- if files.empty?
- files << Puppet[:manifest]
- Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}"
- end
- files.each do |file|
- Puppet[:manifest] = file
- Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear
- end
- nil
- end
- end
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Interact directly with the parser"
+
+ action :validate do
+ summary "Validate the syntax of one or more Puppet manifests"
+ description <<-EOT
+ This action validates Puppet DSL syntax without compiling a catalog or
+ syncing any resources. If no manifest files are provided, it will
+ validate the default site manifest.
+ EOT
+ when_invoked do |*args|
+ args.pop
+ files = args
+ if files.empty?
+ files << Puppet[:manifest]
+ Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}"
+ end
+ files.each do |file|
+ Puppet[:manifest] = file
+ Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear
+ end
+ nil
+ end
+ end
end
diff --git a/lib/puppet/face/plugin.rb b/lib/puppet/face/plugin.rb
new file mode 100644
index 000000000..969d42389
--- /dev/null
+++ b/lib/puppet/face/plugin.rb
@@ -0,0 +1,47 @@
+require 'puppet/face'
+Puppet::Face.define(:plugin, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Interact with the Puppet plugin system"
+ description <<-EOT
+ This face provides network access to the puppet master's store of
+ plugins. It is intended for use in other faces, rather than for direct
+ command line access.
+ EOT
+ notes <<-EOT
+ The puppet master can serve Ruby code collected from the lib directories
+ of its modules. These plugins can be used on agent nodes to extend
+ Facter and implement custom types and providers.
+ EOT
+
+ action :download do
+ summary "Download plugins from the configured master"
+ returns <<-EOT
+ An array containing the files actually downloaded. If all files
+ were in sync, this array will be empty.
+ EOT
+ notes "This action modifies files on disk without returning any data."
+ examples <<-EOT
+ Retrieve plugins from the puppet master:
+
+ Puppet::Face[:plugin, '0.0.1'].download
+ EOT
+
+ when_invoked do |options|
+ require 'puppet/configurer/downloader'
+ Puppet::Configurer::Downloader.new("plugin",
+ Puppet[:plugindest],
+ Puppet[:pluginsource],
+ Puppet[:pluginsignore]).evaluate
+ end
+
+ when_rendering :console do |value|
+ if value.empty? then
+ "No plugins downloaded."
+ else
+ "Downloaded these plugins: #{value.join(', ')}"
+ end
+ end
+ end
+end
diff --git a/lib/puppet/face/report.rb b/lib/puppet/face/report.rb
index 6e6f0b335..c8549b14f 100644
--- a/lib/puppet/face/report.rb
+++ b/lib/puppet/face/report.rb
@@ -1,11 +1,43 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
+
+Puppet::Indirector::Face.define(:report, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Create, display, and submit reports"
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `processor`
+ * `rest`
+ * `yaml`
+ EOT
-Puppet::Face::Indirector.define(:report, '0.0.1') do
action(:submit) do
+ summary "Submit a report object to the puppet master"
+ description <<-EOT
+ This action is essentially a shortcut and wrapper for the `save` action
+ with a terminus of `rest`. It also can provide additional details in the
+ event of a report submission failure. It is not intended for use from
+ a command line.
+ EOT
+ examples <<-EOT
+ From secret_agent.rb:
+ Puppet::Face[:plugin, '0.0.1'].download
+
+ facts = Puppet::Face[:facts, '0.0.1'].find(certname)
+ catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts)
+ report = Puppet::Face[:catalog, '0.0.1'].apply(catalog)
+
+ Puppet::Face[:report, '0.0.1'].submit(report)
+ EOT
when_invoked do |report, options|
begin
- Puppet::Transaction::Report.terminus_class = :rest
- report.save
+ Puppet::Transaction::Report.indirection.terminus_class = :rest
+ Puppet::Face[:report, "0.0.1"].save(report)
+ Puppet.notice "Uploaded report for #{report.name}"
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
diff --git a/lib/puppet/face/resource.rb b/lib/puppet/face/resource.rb
index d162f728a..ed6360888 100644
--- a/lib/puppet/face/resource.rb
+++ b/lib/puppet/face/resource.rb
@@ -1,4 +1,23 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
-Puppet::Face::Indirector.define(:resource, '0.0.1') do
+Puppet::Indirector::Face.define(:resource, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Interact directly with resources via the RAL, like ralsh"
+ description <<-EOT
+ This face provides a Ruby API with functionality similar to the puppet
+ resource (née ralsh) command line application. It is not intended to be
+ used from the command line.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `ral`
+ * `rest`
+ EOT
+
+ examples "TK we really need some examples for this one."
end
diff --git a/lib/puppet/face/resource_type.rb b/lib/puppet/face/resource_type.rb
index 0cdbd719f..77ccefa8f 100644
--- a/lib/puppet/face/resource_type.rb
+++ b/lib/puppet/face/resource_type.rb
@@ -1,4 +1,17 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
-Puppet::Face::Indirector.define(:resource_type, '0.0.1') do
+Puppet::Indirector::Face.define(:resource_type, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "View resource types, classes, and nodes from all manifests"
+ description "TK I have no idea what this does."
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `parser`
+ * `rest`
+ EOT
end
diff --git a/lib/puppet/face/secret_agent.rb b/lib/puppet/face/secret_agent.rb
new file mode 100644
index 000000000..c8c8e6629
--- /dev/null
+++ b/lib/puppet/face/secret_agent.rb
@@ -0,0 +1,39 @@
+require 'puppet/face'
+
+Puppet::Face.define(:secret_agent, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "Provides agent-like behavior, with no plugin downloading or reporting"
+ description <<-EOT
+ This face currently functions as a proof of concept, demonstrating how
+ Faces allows the separation of application logic from Puppet's internal
+ systems; compare the code for puppet agent. It will eventually replace
+ puppet agent entirely, and can provide a template for users who wish to
+ implement agent-like functionality with drastically different
+ application logic.
+ EOT
+
+ action(:synchronize) do
+ summary "Retrieve and apply a catalog from the puppet master"
+ description <<-EOT
+ This action mimics the behavior of the puppet agent application. It does
+ not currently daemonize, but can download plugins, submit facts,
+ retrieve and apply a catalog, and submit a report to the puppet master.
+ EOT
+
+ when_invoked do |options|
+ Puppet::Face[:plugin, '0.0.1'].download
+
+ Puppet::Face[:facts, '0.0.1'].upload
+
+ Puppet::Face[:catalog, '0.0.1'].download
+
+ report = Puppet::Face[:catalog, '0.0.1'].apply
+
+ Puppet::Face[:report, '0.0.1'].submit(report)
+
+ return report
+ end
+ end
+end
diff --git a/lib/puppet/face/status.rb b/lib/puppet/face/status.rb
index 7085e7cd7..6a29debdd 100644
--- a/lib/puppet/face/status.rb
+++ b/lib/puppet/face/status.rb
@@ -1,4 +1,30 @@
-require 'puppet/face/indirector'
+require 'puppet/indirector/face'
-Puppet::Face::Indirector.define(:status, '0.0.1') do
+Puppet::Indirector::Face.define(:status, '0.0.1') do
+ copyright "Puppet Labs", 2011
+ license "Apache 2 license; see COPYING"
+
+ summary "View puppet server status"
+ description <<-EOT
+ This subcommand is only useful for determining whether a puppet master
+ server (or an agent node, if puppet was started with the `--listen`
+ option) is responding to requests.
+
+ Only the `find` action is valid. If the server is responding to
+ requests, `find` will retrieve a status object; if not, the connection
+ will be refused. When invoked with the `local` terminus, `find` will
+ always return true.
+
+ If you wish to query a server other than the master configured in
+ puppet.conf, you must set the `--server` and `--masterport` options on
+ the command line.
+ EOT
+ notes <<-EOT
+ This is an indirector face, which exposes find, search, save, and
+ destroy actions for an indirected subsystem of Puppet. Valid terminuses
+ for this face include:
+
+ * `local`
+ * `rest`
+ EOT
end
diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb
index c020f036d..f29f70a53 100644
--- a/lib/puppet/file_serving/fileset.rb
+++ b/lib/puppet/file_serving/fileset.rb
@@ -59,7 +59,7 @@ class Puppet::FileServing::Fileset
end
def initialize(path, options = {})
- path = path.chomp(File::SEPARATOR)
+ path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR
raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path
@path = path
diff --git a/lib/puppet/face/indirector.rb b/lib/puppet/indirector/face.rb
index 6c7708b51..1371c647e 100644
--- a/lib/puppet/face/indirector.rb
+++ b/lib/puppet/indirector/face.rb
@@ -1,10 +1,24 @@
-require 'puppet'
require 'puppet/face'
-class Puppet::Face::Indirector < Puppet::Face
+class Puppet::Indirector::Face < Puppet::Face
option "--terminus TERMINUS" do
- desc "REVISIT: You can select a terminus, which has some bigger effect
-that we should describe in this file somehow."
+ summary "The indirector terminus to use for this action"
+ description <<-EOT
+Indirector faces expose indirected subsystems of Puppet. These
+subsystems are each able to retrieve and alter a specific type of data
+(with the familiar actions of `find`, `search`, `save`, and `destroy`)
+from an arbitrary number of pluggable backends. In Puppet parlance,
+these backends are called terminuses.
+
+Almost all indirected subsystems have a `rest` terminus that interacts
+with the puppet master's data. Most of them have additional terminuses
+for various local data models, which are in turn used by the indirected
+subsystem on the puppet master whenever it receives a remote request.
+
+The terminus for an action is often determined by context, but
+occasionally needs to be set explicitly. See the "Notes" section of this
+face's manpage for more details.
+ EOT
before_action do |action, args, options|
set_terminus(options[:terminus])
@@ -23,11 +37,9 @@ that we should describe in this file somehow."
Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort
end
- def call_indirection_method(method, *args)
- options = args.last
-
+ def call_indirection_method(method, key, options)
begin
- result = indirection.__send__(method, *args)
+ result = indirection.__send__(method, key, options)
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise "Could not call '#{method}' on '#{indirection_name}': #{detail}"
@@ -37,23 +49,38 @@ that we should describe in this file somehow."
end
action :destroy do
- when_invoked { |*args| call_indirection_method(:destroy, *args) }
+ summary "Delete an object"
+ when_invoked { |key, options| call_indirection_method(:destroy, key, options) }
end
action :find do
- when_invoked { |*args| call_indirection_method(:find, *args) }
+ summary "Retrieve an object by name"
+ when_invoked { |key, options| call_indirection_method(:find, key, options) }
end
action :save do
- when_invoked { |*args| call_indirection_method(:save, *args) }
+ summary "Create or modify an object"
+ notes <<-EOT
+ Save actions cannot currently be invoked from the command line, and are
+ for API use only.
+ EOT
+ when_invoked { |key, options| call_indirection_method(:save, key, options) }
end
action :search do
- when_invoked { |*args| call_indirection_method(:search, *args) }
+ summary "Search for an object"
+ when_invoked { |key, options| call_indirection_method(:search, key, options) }
end
# Print the configuration for the current terminus class
action :info do
+ summary "Print the default terminus class for this face"
+ description <<-EOT
+ TK So this is per-face, right? No way to tell what the default terminus
+ is per-action, for subsystems that switch to REST for save but query
+ locally for find?
+ EOT
+
when_invoked do |*args|
if t = indirection.terminus_class
puts "Run mode '#{Puppet.run_mode.name}': #{t}"
diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb
index 1918a3fb5..fd8d654dd 100644
--- a/lib/puppet/indirector/request.rb
+++ b/lib/puppet/indirector/request.rb
@@ -14,51 +14,6 @@ class Puppet::Indirector::Request
OPTION_ATTRIBUTES = [:ip, :node, :authenticated, :ignore_terminus, :ignore_cache, :instance, :environment]
- def self.from_pson(json)
- raise ArgumentError, "No indirection name provided in json data" unless indirection_name = json['type']
- raise ArgumentError, "No method name provided in json data" unless method = json['method']
- raise ArgumentError, "No key provided in json data" unless key = json['key']
-
- request = new(indirection_name, method, key, json['attributes'])
-
- if instance = json['instance']
- klass = Puppet::Indirector::Indirection.instance(request.indirection_name).model
- if instance.is_a?(klass)
- request.instance = instance
- else
- request.instance = klass.from_pson(instance)
- end
- end
-
- request
- end
-
- def to_pson(*args)
- result = {
- 'document_type' => 'Puppet::Indirector::Request',
- 'data' => {
- 'type' => indirection_name,
- 'method' => method,
- 'key' => key
- }
- }
- data = result['data']
- attributes = {}
- OPTION_ATTRIBUTES.each do |key|
- next unless value = send(key)
- attributes[key] = value
- end
-
- options.each do |opt, value|
- attributes[opt] = value
- end
-
- data['attributes'] = attributes unless attributes.empty?
- data['instance'] = instance if instance
-
- result.to_pson(*args)
- end
-
# Is this an authenticated request?
def authenticated?
# Double negative, so we just get true or false
@@ -106,11 +61,9 @@ class Puppet::Indirector::Request
self.indirection_name = indirection_name
self.method = method
- options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash }
-
set_attributes(options)
- @options = options
+ @options = options.inject({}) { |hash, ary| hash[ary[0].to_sym] = ary[1]; hash }
if key_or_instance.is_a?(String) || key_or_instance.is_a?(Symbol)
key = key_or_instance
@@ -200,7 +153,7 @@ class Puppet::Indirector::Request
def set_attributes(options)
OPTION_ATTRIBUTES.each do |attribute|
- if options.include?(attribute.to_sym)
+ if options.include?(attribute)
send(attribute.to_s + "=", options[attribute])
options.delete(attribute)
end
diff --git a/lib/puppet/interface.rb b/lib/puppet/interface.rb
index ced00863d..10e2ec8d7 100644
--- a/lib/puppet/interface.rb
+++ b/lib/puppet/interface.rb
@@ -1,7 +1,11 @@
require 'puppet'
require 'puppet/util/autoload'
+require 'puppet/interface/documentation'
+require 'prettyprint'
class Puppet::Interface
+ include FullDocs
+
require 'puppet/interface/face_collection'
require 'puppet/interface/action_manager'
@@ -65,27 +69,33 @@ class Puppet::Interface
Puppet.warning("set_default_format is deprecated (and ineffective); use render_as on your actions instead.")
end
+
########################################################################
# Documentation. We currently have to rewrite both getters because we share
# the same instance between build-time and the runtime instance. When that
# splits out this should merge into a module that both the action and face
# include. --daniel 2011-04-17
- attr_accessor :summary, :description
- def summary(value = nil)
- self.summary = value unless value.nil?
- @summary
- end
- def summary=(value)
- value = value.to_s
- value =~ /\n/ and
- raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead."
-
- @summary = value
- end
-
- def description(value = nil)
- self.description = value unless value.nil?
- @description
+ def synopsis
+ output = PrettyPrint.format do |s|
+ s.text("puppet #{name} <action>")
+ s.breakable
+
+ options.each do |option|
+ option = get_option(option)
+ wrap = option.required? ? %w{ < > } : %w{ [ ] }
+
+ s.group(0, *wrap) do
+ option.optparse.each do |item|
+ unless s.current_group.first?
+ s.breakable
+ s.text '|'
+ s.breakable
+ end
+ s.text item
+ end
+ end
+ end
+ end
end
@@ -97,9 +107,15 @@ class Puppet::Interface
raise ArgumentError, "Cannot create face #{name.inspect} with invalid version number '#{version}'!"
end
- @name = Puppet::Interface::FaceCollection.underscorize(name)
+ @name = Puppet::Interface::FaceCollection.underscorize(name)
@version = version
+ # The few bits of documentation we actually demand. The default license
+ # is a favour to our end users; if you happen to get that in a core face
+ # report it as a bug, please. --daniel 2011-04-26
+ @authors = []
+ @license = 'All Rights Reserved'
+
instance_eval(&block) if block_given?
end
@@ -139,12 +155,12 @@ class Puppet::Interface
action.get_option(name).__decoration_name(type)
end
+ methods.reverse! if type == :after
+
+ # Exceptions here should propagate up; this implements a hook we can use
+ # reasonably for option validation.
methods.each do |hook|
- begin
- respond_to? hook and self.__send__(hook, action, passed_args, passed_options)
- rescue => e
- Puppet.warning("invoking #{action} #{type} hook: #{e}")
- end
+ respond_to? hook and self.__send__(hook, action, passed_args, passed_options)
end
end
diff --git a/lib/puppet/interface/action.rb b/lib/puppet/interface/action.rb
index f8eef69b1..114e5341b 100644
--- a/lib/puppet/interface/action.rb
+++ b/lib/puppet/interface/action.rb
@@ -1,15 +1,28 @@
-# -*- coding: utf-8 -*-
require 'puppet/interface'
-require 'puppet/interface/option'
+require 'puppet/interface/documentation'
+require 'prettyprint'
class Puppet::Interface::Action
+ extend Puppet::Interface::DocGen
+ include Puppet::Interface::FullDocs
+
def initialize(face, name, attrs = {})
raise "#{name.inspect} is an invalid action name" unless name.to_s =~ /^[a-z]\w*$/
@face = face
@name = name.to_sym
+
+ # The few bits of documentation we actually demand. The default license
+ # is a favour to our end users; if you happen to get that in a core face
+ # report it as a bug, please. --daniel 2011-04-26
+ @authors = []
+ @license = 'All Rights Reserved'
+
attrs.each do |k, v| send("#{k}=", v) end
- @options = {}
+ # @options collects the added options in the order they're declared.
+ # @options_hash collects the options keyed by alias for quick lookups.
+ @options = []
+ @options_hash = {}
@when_rendering = {}
end
@@ -30,8 +43,32 @@ class Puppet::Interface::Action
!!@default
end
- attr_accessor :summary
-
+ ########################################################################
+ # Documentation...
+ attr_doc :returns
+ def synopsis
+ output = PrettyPrint.format do |s|
+ s.text("puppet #{@face.name}")
+ s.text(" #{name}") unless default?
+ s.breakable
+
+ options.each do |option|
+ option = get_option(option)
+ wrap = option.required? ? %w{ < > } : %w{ [ ] }
+
+ s.group(0, *wrap) do
+ option.optparse.each do |item|
+ unless s.current_group.first?
+ s.breakable
+ s.text '|'
+ s.breakable
+ end
+ s.text item
+ end
+ end
+ end
+ end
+ end
########################################################################
# Support for rendering formats and all.
@@ -39,8 +76,15 @@ class Puppet::Interface::Action
unless type.is_a? Symbol
raise ArgumentError, "The rendering format must be a symbol, not #{type.class.name}"
end
- return unless @when_rendering.has_key? type
- return @when_rendering[type].bind(@face)
+ # Do we have a rendering hook for this name?
+ return @when_rendering[type].bind(@face) if @when_rendering.has_key? type
+
+ # How about by another name?
+ alt = type.to_s.sub(/^to_/, '').to_sym
+ return @when_rendering[alt].bind(@face) if @when_rendering.has_key? alt
+
+ # Guess not, nothing to run.
+ return nil
end
def set_rendering_method_for(type, proc)
unless proc.is_a? Proc
@@ -83,18 +127,6 @@ class Puppet::Interface::Action
########################################################################
- # Documentation stuff, whee!
- attr_accessor :summary, :description
- def summary=(value)
- value = value.to_s
- value =~ /\n/ and
- raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead."
-
- @summary = value
- end
-
-
- ########################################################################
# Initially, this was defined to allow the @action.invoke pattern, which is
# a very natural way to invoke behaviour given our introspection
# capabilities. Heck, our initial plan was to have the faces delegate to
@@ -148,11 +180,13 @@ class Puppet::Interface::Action
# this stuff work, because it would have been cleaner. Which gives you an
# idea how motivated we were to make this cleaner. Sorry.
# --daniel 2011-03-31
+ attr_reader :positional_arg_count
+ attr_accessor :when_invoked
def when_invoked=(block)
internal_name = "#{@name} implementation, required on Ruby 1.8".to_sym
- arity = block.arity
+ arity = @positional_arg_count = block.arity
if arity == 0 then
# This will never fire on 1.8.7, which treats no arguments as "*args",
# but will on 1.9.2, which treats it as "no arguments". Which bites,
@@ -195,9 +229,11 @@ WRAPPER
if @face.is_a?(Class)
@face.class_eval do eval wrapper, nil, file, line end
@face.define_method(internal_name, &block)
+ @when_invoked = @face.instance_method(name)
else
@face.instance_eval do eval wrapper, nil, file, line end
@face.meta_def(internal_name, &block)
+ @when_invoked = @face.method(name).unbind
end
end
@@ -211,7 +247,8 @@ WRAPPER
end
option.aliases.each do |name|
- @options[name] = option
+ @options << name
+ @options_hash[name] = option
end
option
@@ -223,15 +260,15 @@ WRAPPER
end
def option?(name)
- @options.include? name.to_sym
+ @options_hash.include? name.to_sym
end
def options
- (@options.keys + @face.options).sort
+ @face.options + @options
end
def get_option(name, with_inherited_options = true)
- option = @options[name.to_sym]
+ option = @options_hash[name.to_sym]
if option.nil? and with_inherited_options
option = @face.get_option(name)
end
@@ -239,12 +276,26 @@ WRAPPER
end
def validate_args(args)
+ # Check for multiple aliases for the same option...
+ args.last.keys.each do |name|
+ # #7290: If this isn't actually an option, ignore it for now. We should
+ # probably fail, but that wasn't our API, and I don't want to perturb
+ # behaviour this late in the RC cycle. --daniel 2011-04-29
+ if option = get_option(name) then
+ overlap = (option.aliases & args.last.keys)
+ unless overlap.length == 1 then
+ raise ArgumentError, "Multiple aliases for the same option passed: #{overlap.join(', ')}"
+ end
+ end
+ end
+
+ # Check for missing mandatory options.
required = options.map do |name|
get_option(name)
end.select(&:required?).collect(&:name) - args.last.keys
return if required.empty?
- raise ArgumentError, "missing required options (#{required.join(', ')})"
+ raise ArgumentError, "The following options are required: #{required.join(', ')}"
end
########################################################################
diff --git a/lib/puppet/interface/action_builder.rb b/lib/puppet/interface/action_builder.rb
index fd8b0856f..ba5531f1d 100644
--- a/lib/puppet/interface/action_builder.rb
+++ b/lib/puppet/interface/action_builder.rb
@@ -9,13 +9,6 @@ class Puppet::Interface::ActionBuilder
new(face, name, &block).action
end
- private
- def initialize(face, name, &block)
- @face = face
- @action = Puppet::Interface::Action.new(face, name)
- instance_eval(&block)
- end
-
# Ideally the method we're defining here would be added to the action, and a
# method on the face would defer to it, but we can't get scope correct, so
# we stick with this. --daniel 2011-03-24
@@ -55,7 +48,7 @@ class Puppet::Interface::ActionBuilder
def render_as(value = nil)
value.nil? and raise ArgumentError, "You must give a rendering format to render_as"
- formats = Puppet::Network::FormatHandler.formats << :for_humans
+ formats = Puppet::Network::FormatHandler.formats
unless formats.include? value
raise ArgumentError, "#{value.inspect} is not a valid rendering format: #{formats.sort.join(", ")}"
end
@@ -66,18 +59,26 @@ class Puppet::Interface::ActionBuilder
# Metaprogram the simple DSL from the target class.
Puppet::Interface::Action.instance_methods.grep(/=$/).each do |setter|
next if setter =~ /^=/
- dsl = setter.sub(/=$/, '')
+ property = setter.sub(/=$/, '')
- unless private_instance_methods.include? dsl
+ unless public_instance_methods.include? property
# Using eval because the argument handling semantics are less awful than
# when we use the define_method/block version. The later warns on older
# Ruby versions if you pass the wrong number of arguments, but carries
# on, which is totally not what we want. --daniel 2011-04-18
- eval <<METHOD
-def #{dsl}(value)
- @action.#{dsl} = value
-end
-METHOD
+ eval <<-METHOD
+ def #{property}(value)
+ @action.#{property} = value
+ end
+ METHOD
end
end
+
+ private
+ def initialize(face, name, &block)
+ @face = face
+ @action = Puppet::Interface::Action.new(face, name)
+ instance_eval(&block)
+ @action.when_invoked or raise ArgumentError, "actions need to know what to do when_invoked; please add the block"
+ end
end
diff --git a/lib/puppet/interface/action_manager.rb b/lib/puppet/interface/action_manager.rb
index 440be2d1b..c5eb8e08a 100644
--- a/lib/puppet/interface/action_manager.rb
+++ b/lib/puppet/interface/action_manager.rb
@@ -1,9 +1,11 @@
-require 'puppet/interface/action_builder'
+require 'puppet/interface/action'
module Puppet::Interface::ActionManager
# Declare that this app can take a specific action, and provide
# the code to do so.
def action(name, &block)
+ require 'puppet/interface/action_builder'
+
@actions ||= {}
@default_action ||= nil
raise "Action #{name} already defined for #{self}" if action?(name)
diff --git a/lib/puppet/interface/documentation.rb b/lib/puppet/interface/documentation.rb
new file mode 100644
index 000000000..48e9a8b1a
--- /dev/null
+++ b/lib/puppet/interface/documentation.rb
@@ -0,0 +1,197 @@
+# This isn't usable outside Puppet::Interface; don't load it alone.
+class Puppet::Interface
+ module DocGen
+ def self.strip_whitespace(text)
+ text.gsub!(/[ \t\f]+$/, '')
+
+ # We need to identify an indent: the minimum number of whitespace
+ # characters at the start of any line in the text.
+ #
+ # Using split rather than each_line is because the later only takes a
+ # block on Ruby 1.8.5 / Centos, and we support that. --daniel 2011-05-03
+ indent = text.split(/\n/).map {|x| x.index(/[^\s]/) }.compact.min
+
+ if indent > 0 then
+ text.gsub!(/^[ \t\f]{0,#{indent}}/, '')
+ end
+
+ return text
+ end
+
+ # The documentation attributes all have some common behaviours; previously
+ # we open-coded them across the set of six things, but that seemed
+ # wasteful - especially given that they were literally the same, and had
+ # the same bug hidden in them.
+ #
+ # This feels a bit like overkill, but at least the common code is common
+ # now. --daniel 2011-04-29
+ def attr_doc(name, &validate)
+ # Now, which form of the setter do we want, validated or not?
+ get_arg = "value.to_s"
+ if validate
+ define_method(:"_validate_#{name}", validate)
+ get_arg = "_validate_#{name}(#{get_arg})"
+ end
+
+ # We use module_eval, which I don't like much, because we can't have an
+ # argument to a block with a default value in Ruby 1.8, and I don't like
+ # the side-effects (eg: no argument count validation) of using blocks
+ # without as metheds. When we are 1.9 only (hah!) you can totally
+ # replace this with some up-and-up define_method. --daniel 2011-04-29
+ module_eval(<<-EOT, __FILE__, __LINE__ + 1)
+ def #{name}(value = nil)
+ self.#{name} = value unless value.nil?
+ @#{name}
+ end
+
+ def #{name}=(value)
+ @#{name} = Puppet::Interface::DocGen.strip_whitespace(#{get_arg})
+ end
+ EOT
+ end
+ end
+
+ module TinyDocs
+ extend Puppet::Interface::DocGen
+
+ attr_doc :summary do |value|
+ value =~ /\n/ and
+ raise ArgumentError, "Face summary should be a single line; put the long text in 'description' instead."
+ value
+ end
+
+ attr_doc :description
+ end
+
+ module FullDocs
+ extend Puppet::Interface::DocGen
+ include TinyDocs
+
+ attr_doc :examples
+ attr_doc :notes
+ attr_doc :license
+
+ attr_doc :short_description
+ def short_description(value = nil)
+ self.short_description = value unless value.nil?
+ if @short_description.nil? then
+ return nil if @description.nil?
+ lines = @description.split("\n")
+ grab = [5, lines.index('') || 5].min
+ @short_description = lines[0, grab].join("\n")
+ end
+ @short_description
+ end
+
+ def author(value = nil)
+ unless value.nil? then
+ unless value.is_a? String
+ raise ArgumentError, 'author must be a string; use multiple statements for multiple authors'
+ end
+
+ if value =~ /\n/ then
+ raise ArgumentError, 'author should be a single line; use multiple statements for multiple authors'
+ end
+ @authors.push(Puppet::Interface::DocGen.strip_whitespace(value))
+ end
+ @authors.empty? ? nil : @authors.join("\n")
+ end
+ def authors
+ @authors
+ end
+ def author=(value)
+ if Array(value).any? {|x| x =~ /\n/ } then
+ raise ArgumentError, 'author should be a single line; use multiple statements'
+ end
+ @authors = Array(value).map{|x| Puppet::Interface::DocGen.strip_whitespace(x) }
+ end
+ alias :authors= :author=
+
+ def copyright(owner = nil, years = nil)
+ if years.nil? and not owner.nil? then
+ raise ArgumentError, 'copyright takes the owners names, then the years covered'
+ end
+ self.copyright_owner = owner unless owner.nil?
+ self.copyright_years = years unless years.nil?
+
+ if self.copyright_years or self.copyright_owner then
+ "Copyright #{self.copyright_years} by #{self.copyright_owner}"
+ else
+ "Unknown copyright owner and years."
+ end
+ end
+
+ attr_accessor :copyright_owner
+ def copyright_owner=(value)
+ case value
+ when String then @copyright_owner = value
+ when Array then @copyright_owner = value.join(", ")
+ else
+ raise ArgumentError, "copyright owner must be a string or an array of strings"
+ end
+ @copyright_owner
+ end
+
+ attr_accessor :copyright_years
+ def copyright_years=(value)
+ years = munge_copyright_year value
+ years = (years.is_a?(Array) ? years : [years]).
+ sort_by do |x| x.is_a?(Range) ? x.first : x end
+
+ @copyright_years = years.map do |year|
+ if year.is_a? Range then
+ "#{year.first}-#{year.last}"
+ else
+ year
+ end
+ end.join(", ")
+ end
+
+ def munge_copyright_year(input)
+ case input
+ when Range then input
+ when Integer then
+ if input < 1970 then
+ fault = "before 1970"
+ elsif input > (future = Time.now.year + 2) then
+ fault = "after #{future}"
+ end
+ if fault then
+ raise ArgumentError, "copyright with a year #{fault} is very strange; did you accidentally add or subtract two years?"
+ end
+
+ input
+
+ when String then
+ input.strip.split(/,/).map do |part|
+ part = part.strip
+ if part =~ /^\d+$/ then
+ part.to_i
+ elsif found = part.split(/-/) then
+ unless found.length == 2 and found.all? {|x| x.strip =~ /^\d+$/ }
+ raise ArgumentError, "#{part.inspect} is not a good copyright year or range"
+ end
+ Range.new(found[0].to_i, found[1].to_i)
+ else
+ raise ArgumentError, "#{part.inspect} is not a good copyright year or range"
+ end
+ end
+
+ when Array then
+ result = []
+ input.each do |item|
+ item = munge_copyright_year item
+ if item.is_a? Array
+ result.concat item
+ else
+ result << item
+ end
+ end
+ result
+
+ else
+ raise ArgumentError, "#{input.inspect} is not a good copyright year, set, or range"
+ end
+ end
+ end
+end
diff --git a/lib/puppet/interface/face_collection.rb b/lib/puppet/interface/face_collection.rb
index 6e6afc545..12d3c56b1 100644
--- a/lib/puppet/interface/face_collection.rb
+++ b/lib/puppet/interface/face_collection.rb
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
require 'puppet/interface'
module Puppet::Interface::FaceCollection
@@ -10,21 +9,12 @@ module Puppet::Interface::FaceCollection
unless @loaded
@loaded = true
$LOAD_PATH.each do |dir|
- next unless FileTest.directory?(dir)
- Dir.chdir(dir) do
- Dir.glob("puppet/face/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file|
- iname = file.sub(/\.rb/, '')
- begin
- require iname
- rescue Exception => detail
- puts detail.backtrace if Puppet[:trace]
- raise "Could not load #{iname} from #{dir}/#{file}: #{detail}"
- end
- end
- end
+ Dir.glob("#{dir}/puppet/face/*.rb").
+ collect {|f| File.basename(f, '.rb') }.
+ each {|name| self[name, :current] }
end
end
- return @faces.keys.select {|name| @faces[name].length > 0 }
+ @faces.keys.select {|name| @faces[name].length > 0 }
end
def self.validate_version(version)
@@ -124,6 +114,10 @@ module Puppet::Interface::FaceCollection
rescue LoadError => e
raise unless e.message =~ %r{-- puppet/face/#{name}$}
# ...guess we didn't find the file; return a much better problem.
+ rescue SyntaxError => e
+ raise unless e.message =~ %r{puppet/face/#{name}\.rb:\d+: }
+ Puppet.err "Failed to load face #{name}:\n#{e}"
+ # ...but we just carry on after complaining.
end
return get_face(name, version)
diff --git a/lib/puppet/interface/option.rb b/lib/puppet/interface/option.rb
index f4c56cb2c..b68bdeb12 100644
--- a/lib/puppet/interface/option.rb
+++ b/lib/puppet/interface/option.rb
@@ -1,4 +1,10 @@
+require 'puppet/interface'
+
class Puppet::Interface::Option
+ include Puppet::Interface::TinyDocs
+ # For compatibility, deprecated, and should go fairly soon...
+ ['', '='].each { |x| alias :"desc#{x}" :"description#{x}" }
+
def initialize(parent, *declaration, &block)
@parent = parent
@optparse = []
@@ -78,7 +84,7 @@ class Puppet::Interface::Option
end
attr_reader :parent, :name, :aliases, :optparse
- attr_accessor :required, :desc
+ attr_accessor :required
attr_accessor :before_action
def before_action=(proc)
diff --git a/lib/puppet/interface/option_manager.rb b/lib/puppet/interface/option_manager.rb
index d42359c07..326a91d92 100644
--- a/lib/puppet/interface/option_manager.rb
+++ b/lib/puppet/interface/option_manager.rb
@@ -8,6 +8,11 @@ module Puppet::Interface::OptionManager
end
def add_option(option)
+ # @options collects the added options in the order they're declared.
+ # @options_hash collects the options keyed by alias for quick lookups.
+ @options ||= []
+ @options_hash ||= {}
+
option.aliases.each do |name|
if conflict = get_option(name) then
raise ArgumentError, "Option #{option} conflicts with existing option #{conflict}"
@@ -21,25 +26,30 @@ module Puppet::Interface::OptionManager
end
end
- option.aliases.each { |name| @options[name] = option }
- option
+ option.aliases.each do |name|
+ @options << name
+ @options_hash[name] = option
+ end
+
+ return option
end
def options
- @options ||= {}
- result = @options.keys
+ result = (@options ||= [])
if self.is_a?(Class) and superclass.respond_to?(:options)
- result += superclass.options
+ result = superclass.options + result
elsif self.class.respond_to?(:options)
- result += self.class.options
+ result = self.class.options + result
end
- result.sort
+
+ return result
end
def get_option(name, with_inherited_options = true)
- @options ||= {}
- result = @options[name.to_sym]
+ @options_hash ||= {}
+
+ result = @options_hash[name.to_sym]
if result.nil? and with_inherited_options then
if self.is_a?(Class) and superclass.respond_to?(:get_option)
result = superclass.get_option(name)
@@ -47,6 +57,7 @@ module Puppet::Interface::OptionManager
result = self.class.get_option(name)
end
end
+
return result
end
diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb
index 4ca3240d4..082c83ee3 100644
--- a/lib/puppet/network/formats.rb
+++ b/lib/puppet/network/formats.rb
@@ -160,3 +160,39 @@ end
# This is really only ever going to be used for Catalogs.
Puppet::Network::FormatHandler.create_serialized_formats(:dot, :required_methods => [:render_method])
+
+
+Puppet::Network::FormatHandler.create(:console,
+ :mime => 'text/x-console-text',
+ :weight => 0) do
+ def json
+ @json ||= Puppet::Network::FormatHandler.format(:pson)
+ end
+
+ def render(datum)
+ # String to String
+ return datum if datum.is_a? String
+ return datum if datum.is_a? Numeric
+
+ # Simple hash to table
+ if datum.is_a? Hash and datum.keys.all? { |x| x.is_a? String or x.is_a? Numeric }
+ output = ''
+ column_a = datum.map do |k,v| k.to_s.length end.max + 2
+ column_b = 79 - column_a
+ datum.sort_by { |k,v| k.to_s } .each do |key, value|
+ output << key.to_s.ljust(column_a)
+ output << json.render(value).
+ chomp.gsub(/\n */) { |x| x + (' ' * column_a) }
+ output << "\n"
+ end
+ return output
+ end
+
+ # ...or pretty-print the inspect outcome.
+ return json.render(datum)
+ end
+
+ def render_multiple(data)
+ data.collect(&:render).join("\n")
+ end
+end
diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb
index 61307f01e..388d54961 100644
--- a/lib/puppet/network/http/api/v1.rb
+++ b/lib/puppet/network/http/api/v1.rb
@@ -30,7 +30,7 @@ module Puppet::Network::HTTP::API::V1
method = indirection_method(http_method, indirection)
- params[:environment] = environment
+ params[:environment] = Puppet::Node::Environment.new(environment)
raise ArgumentError, "No request key specified in #{uri}" if key == "" or key.nil?
diff --git a/lib/puppet/network/rest_authconfig.rb b/lib/puppet/network/rest_authconfig.rb
index cf76978fe..dfe8f85c4 100644
--- a/lib/puppet/network/rest_authconfig.rb
+++ b/lib/puppet/network/rest_authconfig.rb
@@ -8,6 +8,7 @@ module Puppet
DEFAULT_ACL = [
{ :acl => "~ ^\/catalog\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true },
+ { :acl => "~ ^\/node\/([^\/]+)$", :method => :find, :allow => '$1', :authenticated => true },
# this one will allow all file access, and thus delegate
# to fileserver.conf
{ :acl => "/file" },
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 4bd4d1de6..5b0a98615 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -20,29 +20,6 @@ class Puppet::Node
attr_accessor :name, :classes, :source, :ipaddress, :parameters
attr_reader :time
- def self.from_pson(pson)
- raise ArgumentError, "No name provided in pson data" unless name = pson['name']
-
- node = new(name)
- node.classes = pson['classes']
- node.parameters = pson['parameters']
- node.environment = pson['environment']
- node
- end
-
- def to_pson(*args)
- result = {
- 'document_type' => "Puppet::Node",
- 'data' => {}
- }
- result['data']['name'] = name
- result['data']['classes'] = classes unless classes.empty?
- result['data']['parameters'] = parameters unless parameters.empty?
- result['data']['environment'] = environment.name
-
- result.to_pson(*args)
- end
-
def environment
return super if @environment
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
index 2ff7156c8..577b62b62 100755
--- a/lib/puppet/node/facts.rb
+++ b/lib/puppet/node/facts.rb
@@ -61,22 +61,18 @@ class Puppet::Node::Facts
def self.from_pson(data)
result = new(data['name'], data['values'])
- result.timestamp = Time.parse(data['timestamp']) if data['timestamp']
- result.expiration = Time.parse(data['expiration']) if data['expiration']
+ result.timestamp = Time.parse(data['timestamp'])
+ result.expiration = Time.parse(data['expiration'])
result
end
def to_pson(*args)
- result = {
- 'document_type' => "Puppet::Node::Facts",
- 'data' => {}
- }
-
- result['data']['name'] = name
- result['data']['expiration'] = expiration if expiration
- result['data']['timestamp'] = timestamp if timestamp
- result['data']['values'] = strip_internal
- result.to_pson(*args)
+ {
+ 'expiration' => expiration,
+ 'name' => name,
+ 'timestamp' => timestamp,
+ 'values' => strip_internal,
+ }.to_pson(*args)
end
# Add internal data to the facts for storage.
diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb
index 180a03dc9..27d75bf92 100644
--- a/lib/puppet/parser/templatewrapper.rb
+++ b/lib/puppet/parser/templatewrapper.rb
@@ -20,7 +20,7 @@ class Puppet::Parser::TemplateWrapper
def script_line
# find which line in the template (if any) we were called from
- caller.find { |l| l =~ /#{file}:/ }.first[/:(\d+):/,1]
+ (caller.find { |l| l =~ /#{file}:/ }||"")[/:(\d+):/,1]
end
# Should return true if a variable is defined, false if it is not
diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb
index a6cff9bdc..b742d283f 100644
--- a/lib/puppet/resource/catalog.rb
+++ b/lib/puppet/resource/catalog.rb
@@ -74,7 +74,7 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
raise ArgumentError, "Can only add objects that respond to :ref, not instances of #{resource.class}" unless resource.respond_to?(:ref)
fail_on_duplicate_type_and_title(resource)
title_key = title_key_for_ref(resource.ref)
-
+
@transient_resources << resource if applying?
@resource_table[title_key] = resource
@@ -339,8 +339,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
@relationship_graph
end
- # Impose our container information on another graph by using it
- # to replace any container vertices X with a pair of verticies
+ # Impose our container information on another graph by using it
+ # to replace any container vertices X with a pair of verticies
# { admissible_X and completed_X } such that that
#
# 0) completed_X depends on admissible_X
@@ -353,8 +353,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
# Note that this requires attention to the possible case of containers
# which contain or depend on other containers, but has the advantage
# that the number of new edges created scales linearly with the number
- # of contained verticies regardless of how containers are related;
- # alternatives such as replacing container-edges with content-edges
+ # of contained verticies regardless of how containers are related;
+ # alternatives such as replacing container-edges with content-edges
# scale as the product of the number of external dependences, which is
# to say geometrically in the case of nested / chained containers.
#
@@ -374,8 +374,8 @@ class Puppet::Resource::Catalog < Puppet::SimpleGraph
admissible = Hash.new { |h,k| k }
completed = Hash.new { |h,k| k }
containers.each { |x|
- admissible[x] = whit_class.new(:name => "admissible_#{x.name}", :catalog => self)
- completed[x] = whit_class.new(:name => "completed_#{x.name}", :catalog => self)
+ admissible[x] = whit_class.new(:name => "admissible_#{x.ref}", :catalog => self)
+ completed[x] = whit_class.new(:name => "completed_#{x.ref}", :catalog => self)
}
#
# Implement the six requierments listed above
diff --git a/lib/puppet/status.rb b/lib/puppet/status.rb
index eecd0e18c..ea6a601f3 100644
--- a/lib/puppet/status.rb
+++ b/lib/puppet/status.rb
@@ -10,7 +10,7 @@ class Puppet::Status
@status = status || {"is_alive" => true}
end
- def to_pson
+ def to_pson(*args)
@status.to_pson
end
diff --git a/lib/puppet/transaction/event_manager.rb b/lib/puppet/transaction/event_manager.rb
index f5da870ed..8f1a695af 100644
--- a/lib/puppet/transaction/event_manager.rb
+++ b/lib/puppet/transaction/event_manager.rb
@@ -62,7 +62,18 @@ class Puppet::Transaction::EventManager
end
def queue_events_for_resource(source, target, callback, events)
- source.info "Scheduling #{callback} of #{target}"
+ whit = Puppet::Type.type(:whit)
+
+ # The message that a resource is refreshing the completed-whit for its own class
+ # is extremely counter-intuitive. Basically everything else is easy to understand,
+ # if you suppress the whit-lookingness of the whit resources
+ refreshing_c_whit = target.is_a?(whit) && target.name =~ /^completed_/
+
+ if refreshing_c_whit
+ source.debug "The container #{target} will propagate my #{callback} event"
+ else
+ source.info "Scheduling #{callback} of #{target}"
+ end
@event_queues[target] ||= {}
@event_queues[target][callback] ||= []
@@ -82,7 +93,9 @@ class Puppet::Transaction::EventManager
process_noop_events(resource, callback, events) and return false unless events.detect { |e| e.status != "noop" }
resource.send(callback)
- resource.notice "Triggered '#{callback}' from #{events.length} events"
+ if not resource.is_a?(Puppet::Type.type(:whit))
+ resource.notice "Triggered '#{callback}' from #{events.length} events"
+ end
return true
rescue => detail
resource.err "Failed to call #{callback}: #{detail}"
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index 767959308..572d5796d 100755
--- a/lib/puppet/type/user.rb
+++ b/lib/puppet/type/user.rb
@@ -451,8 +451,6 @@ module Puppet
newparam(:ia_load_module, :required_features => :manages_aix_lam) do
desc "The name of the I&A module to use to manage this user"
-
- defaultto "compat"
end
newproperty(:attributes, :parent => Puppet::Property::KeyValue, :required_features => :manages_aix_lam) do
diff --git a/lib/puppet/type/whit.rb b/lib/puppet/type/whit.rb
index 55ed0386e..4c77915b3 100644
--- a/lib/puppet/type/whit.rb
+++ b/lib/puppet/type/whit.rb
@@ -5,8 +5,14 @@ Puppet::Type.newtype(:whit) do
desc "The name of the whit, because it must have one."
end
+
+ # Hide the fact that we're a whit from logs
def to_s
- "(#{name})"
+ name.sub(/^completed_|^admissible_/, "")
+ end
+
+ def path
+ to_s
end
def refresh
diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb
index 714d03f74..8190f8ac1 100644
--- a/lib/puppet/util/command_line.rb
+++ b/lib/puppet/util/command_line.rb
@@ -65,14 +65,9 @@ module Puppet
# return to the caller. How strange we are. --daniel 2011-04-11
else
unless subcommand_name.nil? then
- puts "Error: Unknown Puppet subcommand #{subcommand_name}.\n"
+ puts "Error: Unknown Puppet subcommand '#{subcommand_name}'"
end
-
- # Doing this at the top of the file is natural, but causes puppet.rb
- # to load too early, which causes things to break. This is a nasty
- # thing, found in #7065. --daniel 2011-04-11
- require 'puppet/face'
- puts Puppet::Face[:help, :current].help
+ puts "See 'puppet help' for help on available puppet subcommands"
end
end