diff options
| author | Daniel Pittman <daniel@puppetlabs.com> | 2011-05-06 11:08:35 -0700 |
|---|---|---|
| committer | Daniel Pittman <daniel@puppetlabs.com> | 2011-05-06 11:08:35 -0700 |
| commit | f80afbe72b848fe4ed81d8116d4eeb494aa6f61e (patch) | |
| tree | 73c7f27f2ea2fb1668f7067ce05638f5064f540d /lib/puppet | |
| parent | 1b12b55b6a2d3581f9643bf09d55727ba1213580 (diff) | |
| parent | b983386ece1b9816e6d3d59a316ad589e35773df (diff) | |
| download | puppet-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/puppet')
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 |
