├── LICENSE ├── README.markdown ├── lib └── puppet │ ├── application │ ├── catalog.rb │ ├── certificate.rb │ ├── certificate_request.rb │ ├── certificate_revocation_list.rb │ ├── config.rb │ ├── configurer.rb │ ├── facts.rb │ ├── file.rb │ ├── indirection_base.rb │ ├── interface.rb │ ├── interface_base.rb │ ├── key.rb │ ├── node.rb │ ├── report.rb │ ├── resource_type.rb │ └── status.rb │ ├── interface.rb │ └── interface │ ├── action.rb │ ├── action_builder.rb │ ├── action_manager.rb │ ├── catalog.rb │ ├── catalog │ └── select.rb │ ├── certificate.rb │ ├── certificate_request.rb │ ├── certificate_revocation_list.rb │ ├── config.rb │ ├── configurer.rb │ ├── facts.rb │ ├── file.rb │ ├── indirector.rb │ ├── interface_collection.rb │ ├── key.rb │ ├── node.rb │ ├── report.rb │ ├── resource.rb │ ├── resource_type.rb │ └── status.rb └── spec ├── README.markdown ├── spec.opts ├── spec_helper.rb ├── unit ├── application │ ├── certificate.rb │ ├── config_spec.rb │ ├── configurer_spec.rb │ ├── indirection_base_spec.rb │ ├── interface_base_spec.rb │ └── interface_spec.rb ├── interface │ ├── action_builder_spec.rb │ ├── action_manager_spec.rb │ ├── action_spec.rb │ ├── catalog_spec.rb │ ├── certificate_request_spec.rb │ ├── certificate_revocation_list_spec.rb │ ├── certificate_spec.rb │ ├── config_spec.rb │ ├── configurer_spec.rb │ ├── facts_spec.rb │ ├── file_spec.rb │ ├── indirector_spec.rb │ ├── interface_collection_spec.rb │ ├── key_spec.rb │ ├── node_spec.rb │ ├── report_spec.rb │ ├── resource_spec.rb │ └── resource_type_spec.rb ├── interface_spec.rb └── puppet │ ├── provider │ └── README.markdown │ └── type │ └── README.markdown └── watchr.rb /LICENSE: -------------------------------------------------------------------------------- 1 | This program and entire repository is free software; you can 2 | redistribute it and/or modify it under the terms of the GNU 3 | General Public License as published by the Free Software 4 | Foundation, version 2 of the License. 5 | 6 | This program is distributed in the hope that it will be useful, 7 | but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | GNU General Public License for more details. 10 | 11 | You should have received a copy of the GNU General Public License 12 | along with this program; if not, write to the Free Software 13 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 14 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Puppet Interfaces 2 | ================= 3 | A set of executables that provide complete CLI access to Puppet's 4 | core data types. They also provide Interface classes for 5 | each of the core data types, which are extensible via plugins. 6 | 7 | For instance, you can create a new action for catalogs at 8 | lib/puppet/interface/catalog/$action.rb. 9 | 10 | This is a Puppet module and should work fine if you install it 11 | in Puppet's module path. 12 | 13 | **Note that this only works with Puppet 2.6.next (and thus will work 14 | with 2.6.5), because there is otherwise a bug in finding Puppet applications. 15 | You also have to either install the lib files into your Puppet libdir, or 16 | you need to add this lib directory to your RUBYLIB.** 17 | 18 | This is meant to be tested and iterated upon, with the plan that it will be 19 | merged into Puppet core once we're satisfied with it. 20 | 21 | Usage 22 | ----- 23 | The general usage is: 24 | 25 | $ puppet 26 | 27 | So, e.g.: 28 | 29 | $ puppet facts find myhost.domain.com 30 | $ puppet node destroy myhost 31 | 32 | You can use it to list all known data types and the available terminus classes: 33 | 34 | $ puppet interface list 35 | catalog : active_record, compiler, queue, rest, yaml 36 | certificate : ca, file, rest 37 | certificate_request : ca, file, rest 38 | certificate_revocation_list : ca, file, rest 39 | file_bucket_file : file, rest 40 | inventory : yaml 41 | key : ca, file 42 | node : active_record, exec, ldap, memory, plain, rest, yaml 43 | report : processor, rest, yaml 44 | resource : ral, rest 45 | resource_type : parser, rest 46 | status : local, rest 47 | 48 | But most interestingly, you can use it for two main purposes: 49 | 50 | * As a client for any Puppet REST server, such as catalogs, facts, reports, etc. 51 | * As a local CLI for any local Puppet data 52 | 53 | A simple case is looking at the local facts: 54 | 55 | $ puppet facts find localhost 56 | 57 | If you're on the server, you can look in that server's fact collection: 58 | 59 | $ puppet facts --mode master --vardir /tmp/foo --terminus yaml find localhost 60 | 61 | Note that we're setting both the vardir and the 'mode', which switches from the default 'agent' mode to server mode (requires a patch in my branch). 62 | 63 | If you'd prefer the data be outputted in json instead of yaml, well, you can do that, too: 64 | 65 | $ puppet find --mode master facts --vardir /tmp/foo --terminus yaml --format pson localhost 66 | 67 | To test using it as an endpoint for compiling and retrieving catalogs from a remote server, (from my commit), try this: 68 | 69 | # Terminal 1 70 | $ sbin/puppetmasterd --trace --confdir /tmp/foo --vardir /tmp/foo --debug --manifest ~/bin/test.pp --certname localhost --no-daemonize 71 | 72 | # Terminal 2 73 | $ sbin/puppetd --trace --debug --confdir /tmp/foo --vardir /tmp/foo --certname localhost --server localhost --test --report 74 | 75 | # Terminal 3, actual testing 76 | $ puppet catalog find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest 77 | 78 | This compiles a test catalog (assuming that ~/bin/test.pp exists) and returns it. With the right auth setup, you can also get facts: 79 | 80 | $ puppet facts find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest 81 | 82 | Or use IRB to do the same thing: 83 | 84 | $ irb 85 | >> require 'puppet/interface' 86 | => true 87 | >> interface = Puppet::Interface.interface(:facts).new 88 | => # 89 | >> facts = interface.find("myhost"); nil 90 | 91 | Like I said, a prototype, but I'd love it if people would play it with some and make some recommendations. 92 | 93 | Extending 94 | --------- 95 | Like most parts of Puppet, these are easy to extend. Just drop a new action into a given interface's directory. E.g.: 96 | 97 | $ cat lib/puppet/interface/catalog/select.rb 98 | # Select and show a list of resources of a given type. 99 | Puppet::Interface.interface(:catalog) do 100 | action :select do 101 | invoke do |host,type| 102 | catalog = Puppet::Resource::Catalog.indirection.find(host) 103 | 104 | catalog.resources.reject { |res| res.type != type }.each { |res| puts res } 105 | end 106 | end 107 | end 108 | $ puppet catalog select localhost Class 109 | Class[main] 110 | Class[Settings] 111 | $ 112 | 113 | Notice that this gets loaded automatically when you try to use it. So, if you have a simple command you've written, such as for cleaning up nodes or diffing catalogs, you an port it to this framework and it should fit cleanly. 114 | -------------------------------------------------------------------------------- /lib/puppet/application/catalog.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Catalog < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/certificate.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Certificate < Puppet::Application::IndirectionBase 4 | 5 | # Luke used to call this --ca but that's taken by the global boolean --ca. 6 | # Since these options map CA terminology to indirector terminology, it's 7 | # now called --ca-location. 8 | option "--ca-location CA_LOCATION" do |arg| 9 | Puppet::SSL::Host.ca_location = arg.to_sym 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /lib/puppet/application/certificate_request.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Certificate_request < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/certificate_revocation_list.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Certificate_revocation_list < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/config.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/interface_base' 2 | 3 | class Puppet::Application::Config < Puppet::Application::InterfaceBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/configurer.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application' 2 | require 'puppet/interface' 3 | 4 | class Puppet::Application::Configurer < Puppet::Application 5 | should_parse_config 6 | run_mode :agent 7 | 8 | option("--debug","-d") 9 | option("--verbose","-v") 10 | 11 | def setup 12 | if options[:debug] or options[:verbose] 13 | Puppet::Util::Log.level = options[:debug] ? :debug : :info 14 | end 15 | 16 | Puppet::Util::Log.newdestination(:console) 17 | end 18 | 19 | def run_command 20 | report = Puppet::Interface.interface(:configurer).synchronize(Puppet[:certname]) 21 | Puppet::Interface.interface(:report).submit(report) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/puppet/application/facts.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Facts < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/file.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::File < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/indirection_base.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/interface_base' 2 | 3 | class Puppet::Application::IndirectionBase < Puppet::Application::InterfaceBase 4 | option("--terminus TERMINUS") do |arg| 5 | @terminus = arg 6 | end 7 | 8 | attr_accessor :terminus, :indirection 9 | 10 | def setup 11 | super 12 | 13 | if interface.respond_to?(:indirection) 14 | raise "Could not find data type #{type} for application #{self.class.name}" unless interface.indirection 15 | 16 | interface.set_terminus(terminus) if terminus 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/puppet/application/interface.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application' 2 | require 'puppet/interface' 3 | 4 | class Puppet::Application::Interface < Puppet::Application 5 | 6 | should_parse_config 7 | run_mode :agent 8 | 9 | option("--debug", "-d") do |arg| 10 | Puppet::Util::Log.level = :debug 11 | end 12 | 13 | option("--verbose", "-v") do 14 | Puppet::Util::Log.level = :info 15 | end 16 | 17 | def list(*arguments) 18 | if arguments.empty? 19 | arguments = %w{terminuses actions} 20 | end 21 | interfaces.each do |name| 22 | str = "#{name}:\n" 23 | if arguments.include?("terminuses") 24 | begin 25 | terms = terminus_classes(name.to_sym) 26 | str << "\tTerminuses: #{terms.join(", ")}\n" 27 | rescue => detail 28 | puts detail.backtrace if Puppet[:trace] 29 | $stderr.puts "Could not load terminuses for #{name}: #{detail}" 30 | end 31 | end 32 | 33 | if arguments.include?("actions") 34 | begin 35 | actions = actions(name.to_sym) 36 | str << "\tActions: #{actions.join(", ")}\n" 37 | rescue => detail 38 | puts detail.backtrace if Puppet[:trace] 39 | $stderr.puts "Could not load actions for #{name}: #{detail}" 40 | end 41 | end 42 | 43 | print str 44 | end 45 | end 46 | 47 | attr_accessor :verb, :name, :arguments 48 | 49 | def main 50 | # Call the method associated with the provided action (e.g., 'find'). 51 | send(verb, *arguments) 52 | end 53 | 54 | def setup 55 | Puppet::Util::Log.newdestination :console 56 | 57 | load_applications # Call this to load all of the apps 58 | 59 | @verb, @arguments = command_line.args 60 | @arguments ||= [] 61 | 62 | validate 63 | end 64 | 65 | def validate 66 | unless verb 67 | raise "You must specify 'find', 'search', 'save', or 'destroy' as a verb; 'save' probably does not work right now" 68 | end 69 | 70 | unless respond_to?(verb) 71 | raise "Command '#{verb}' not found for 'interface'" 72 | end 73 | end 74 | 75 | def interfaces 76 | Puppet::Interface.interfaces 77 | end 78 | 79 | def terminus_classes(indirection) 80 | Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort 81 | end 82 | 83 | def actions(indirection) 84 | return [] unless interface = Puppet::Interface.interface(indirection) 85 | interface.load_actions 86 | return interface.actions.sort { |a,b| a.to_s <=> b.to_s } 87 | end 88 | 89 | def load_applications 90 | command_line.available_subcommands.each do |app| 91 | command_line.require_application app 92 | end 93 | end 94 | end 95 | 96 | -------------------------------------------------------------------------------- /lib/puppet/application/interface_base.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application' 2 | require 'puppet/interface' 3 | 4 | class Puppet::Application::InterfaceBase < Puppet::Application 5 | should_parse_config 6 | run_mode :agent 7 | 8 | def preinit 9 | super 10 | trap(:INT) do 11 | $stderr.puts "Cancelling Interface" 12 | exit(0) 13 | end 14 | end 15 | 16 | option("--debug", "-d") do |arg| 17 | Puppet::Util::Log.level = :debug 18 | end 19 | 20 | option("--verbose", "-v") do 21 | Puppet::Util::Log.level = :info 22 | end 23 | 24 | option("--format FORMAT") do |arg| 25 | @format = arg.to_sym 26 | end 27 | 28 | option("--mode RUNMODE", "-r") do |arg| 29 | raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) 30 | self.class.run_mode(arg.to_sym) 31 | set_run_mode self.class.run_mode 32 | end 33 | 34 | 35 | attr_accessor :interface, :type, :verb, :arguments, :format 36 | attr_writer :exit_code 37 | 38 | # This allows you to set the exit code if you don't want to just exit 39 | # immediately but you need to indicate a failure. 40 | def exit_code 41 | @exit_code || 0 42 | end 43 | 44 | def main 45 | # Call the method associated with the provided action (e.g., 'find'). 46 | if result = interface.send(verb, *arguments) 47 | puts render(result) 48 | end 49 | exit(exit_code) 50 | end 51 | 52 | # Override this if you need custom rendering. 53 | def render(result) 54 | render_method = Puppet::Network::FormatHandler.format(format).render_method 55 | if render_method == "to_pson" 56 | jj result 57 | exit(0) 58 | else 59 | result.send(render_method) 60 | end 61 | end 62 | 63 | def setup 64 | Puppet::Util::Log.newdestination :console 65 | 66 | @verb = command_line.args.shift 67 | @arguments = command_line.args 68 | @arguments ||= [] 69 | 70 | @arguments = Array(@arguments) 71 | 72 | @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym 73 | 74 | unless Puppet::Interface.interface?(@type) 75 | raise "Could not find interface '#{@type}'" 76 | end 77 | @interface = Puppet::Interface.interface(@type) 78 | @format ||= @interface.default_format 79 | 80 | # We copy all of the app options to the interface. 81 | # This allows each action to read in the options. 82 | @interface.options = options 83 | 84 | validate 85 | end 86 | 87 | def validate 88 | unless verb 89 | raise "You must specify #{interface.actions.join(", ")} as a verb; 'save' probably does not work right now" 90 | end 91 | 92 | unless interface.action?(verb) 93 | raise "Command '#{verb}' not found for #{type}" 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/puppet/application/key.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Key < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/node.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Node < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/report.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Report < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/resource_type.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Resource_type < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/application/status.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/application/indirection_base' 2 | 3 | class Puppet::Application::Status < Puppet::Application::IndirectionBase 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'puppet/util/autoload' 3 | 4 | class Puppet::Interface 5 | require 'puppet/interface/action_manager' 6 | require 'puppet/interface/interface_collection' 7 | 8 | include Puppet::Interface::ActionManager 9 | extend Puppet::Interface::ActionManager 10 | 11 | include Puppet::Util 12 | 13 | @interfaces = {} 14 | 15 | # This is just so we can search for actions. We only use its 16 | # list of directories to search. 17 | # Can't we utilize an external autoloader, or simply use the $LOAD_PATH? -pvb 18 | def self.autoloader 19 | @autoloader ||= Puppet::Util::Autoload.new(:application, "puppet/interface") 20 | end 21 | 22 | def self.interfaces 23 | Puppet::Interface::InterfaceCollection.interfaces 24 | end 25 | 26 | def self.interface?(name) 27 | Puppet::Interface::InterfaceCollection.interface?(name) 28 | end 29 | 30 | def self.register(instance) 31 | Puppet::Interface::InterfaceCollection.register(instance) 32 | end 33 | 34 | def self.interface(name, &blk) 35 | if interface?(name) 36 | interface = Puppet::Interface::InterfaceCollection[name] 37 | interface.instance_eval(&blk) if blk 38 | else 39 | interface = new(name, &blk) 40 | Puppet::Interface::InterfaceCollection.register(interface) 41 | interface.load_actions 42 | end 43 | return interface 44 | end 45 | 46 | attr_accessor :default_format 47 | 48 | def set_default_format(format) 49 | self.default_format = format.to_sym 50 | end 51 | 52 | attr_accessor :type, :verb, :arguments, :options 53 | attr_reader :name 54 | 55 | def initialize(name, options = {}, &block) 56 | @name = Puppet::Interface::InterfaceCollection.underscorize(name) 57 | 58 | @default_format = :pson 59 | options.each { |opt, val| send(opt.to_s + "=", val) } 60 | 61 | instance_eval(&block) if block 62 | end 63 | 64 | # Try to find actions defined in other files. 65 | def load_actions 66 | path = "puppet/interface/#{name}" 67 | 68 | loaded = [] 69 | Puppet::Interface.autoloader.search_directories.each do |dir| 70 | fdir = ::File.join(dir, path) 71 | next unless FileTest.directory?(fdir) 72 | 73 | Dir.chdir(fdir) do 74 | Dir.glob("*.rb").each do |file| 75 | aname = file.sub(/\.rb/, '') 76 | if loaded.include?(aname) 77 | Puppet.debug "Not loading duplicate action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" 78 | next 79 | end 80 | loaded << aname 81 | Puppet.debug "Loading action '#{aname}' for '#{name}' from '#{fdir}/#{file}'" 82 | require "#{path}/#{aname}" 83 | end 84 | end 85 | end 86 | end 87 | 88 | def to_s 89 | "Puppet::Interface(#{name})" 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/puppet/interface/action.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface' 2 | 3 | class Puppet::Interface::Action 4 | attr_reader :name 5 | 6 | def initialize(interface, name) 7 | name = name.to_s 8 | raise "'#{name}' is an invalid action name" unless name =~ /^[a-z]\w*$/ 9 | @interface = interface 10 | @name = name 11 | end 12 | 13 | def invoke(*args, &block) 14 | @interface.method(name).call(*args,&block) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/puppet/interface/action_builder.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface' 2 | require 'puppet/interface/action' 3 | 4 | class Puppet::Interface::ActionBuilder 5 | attr_reader :action 6 | 7 | def self.build(interface, name, &block) 8 | name = name.to_s 9 | raise "Action '#{name}' must specify a block" unless block 10 | builder = new(interface, name, &block) 11 | builder.action 12 | end 13 | 14 | def initialize(interface, name, &block) 15 | @interface = interface 16 | @action = Puppet::Interface::Action.new(interface, name) 17 | instance_eval(&block) 18 | end 19 | 20 | # Ideally the method we're defining here would be added to the action, and a 21 | # method on the interface would defer to it 22 | def invoke(&block) 23 | raise "Invoke called on an ActionBuilder with no corresponding Action" unless @action 24 | if @interface.is_a?(Class) 25 | @interface.define_method(@action.name, &block) 26 | else 27 | @interface.meta_def(@action.name, &block) 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/puppet/interface/action_manager.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/action_builder' 2 | 3 | module Puppet::Interface::ActionManager 4 | # Declare that this app can take a specific action, and provide 5 | # the code to do so. 6 | def action(name, &block) 7 | @actions ||= {} 8 | name = name.to_s.downcase.to_sym 9 | 10 | raise "Action #{name} already defined for #{self}" if action?(name) 11 | 12 | action = Puppet::Interface::ActionBuilder.build(self, name, &block) 13 | 14 | @actions[name] = action 15 | end 16 | 17 | def actions 18 | @actions ||= {} 19 | result = @actions.keys 20 | 21 | if self.is_a?(Class) and superclass.respond_to?(:actions) 22 | result += superclass.actions 23 | elsif self.class.respond_to?(:actions) 24 | result += self.class.actions 25 | end 26 | result.sort 27 | end 28 | 29 | def get_action(name) 30 | @actions[name].dup 31 | end 32 | 33 | def action?(name) 34 | actions.include?(name) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/puppet/interface/catalog.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:catalog) do 4 | action(:apply) do 5 | invoke do |catalog| 6 | report = Puppet::Transaction::Report.new("apply") 7 | report.configuration_version = catalog.version 8 | 9 | Puppet::Util::Log.newdestination(report) 10 | 11 | begin 12 | benchmark(:notice, "Finished catalog run") do 13 | catalog.apply(:report => report) 14 | end 15 | rescue => detail 16 | puts detail.backtrace if Puppet[:trace] 17 | Puppet.err "Failed to apply catalog: #{detail}" 18 | end 19 | 20 | report.finalize_report 21 | report 22 | end 23 | end 24 | 25 | action(:download) do 26 | invoke do |certname,facts| 27 | Puppet::Resource::Catalog.terminus_class = :rest 28 | facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))} 29 | catalog = nil 30 | retrieval_duration = thinmark do 31 | catalog = Puppet::Interface.interface(:catalog).find(certname, facts_to_upload) 32 | end 33 | catalog = catalog.to_ral 34 | catalog.finalize 35 | catalog.retrieval_duration = retrieval_duration 36 | catalog.write_class_file 37 | catalog 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/puppet/interface/catalog/select.rb: -------------------------------------------------------------------------------- 1 | # Select and show a list of resources of a given type. 2 | Puppet::Interface.interface(:catalog) do 3 | action :select do 4 | invoke do |host,type| 5 | catalog = Puppet::Resource::Catalog.indirection.find(host) 6 | 7 | catalog.resources.reject { |res| res.type != type }.each { |res| puts res } 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/puppet/interface/certificate.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:certificate) do 4 | 5 | action :sign do |name| 6 | unless indirection.terminus 7 | raise ArgumentError, "You must have a CA specified; use --ca-location to specify the location (remote, local, only)" 8 | end 9 | 10 | location = Puppet::SSL::Host.ca_location 11 | if location == :local && !Puppet::SSL::CertificateAuthority.ca? 12 | Puppet::Application[:certificate].class.run_mode("master") 13 | set_run_mode Puppet::Application[:certificate].class.run_mode 14 | end 15 | 16 | Puppet::SSL::Host.indirection.save(Puppet::SSL::Host.new(name)) 17 | 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet/interface/certificate_request.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:certificate_request) do 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface/certificate_revocation_list.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:certificate_revocation_list) do 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface/config.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface' 2 | 3 | Puppet::Interface.interface(:config) do 4 | action(:print) do 5 | invoke do |*args| 6 | Puppet.settings[:configprint] = args.join(",") 7 | Puppet.settings.print_config_options 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/puppet/interface/configurer.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface' 2 | 3 | Puppet::Interface.interface(:configurer) do 4 | action(:synchronize) do 5 | invoke do |certname| 6 | facts = Puppet::Interface.interface(:facts).find(certname) 7 | catalog = Puppet::Interface.interface(:catalog).download(certname, facts) 8 | report = Puppet::Interface.interface(:catalog).apply(catalog) 9 | report 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/puppet/interface/facts.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | require 'puppet/node/facts' 3 | 4 | Puppet::Interface::Indirector.interface(:facts) do 5 | set_default_format :yaml 6 | 7 | # Upload our facts to the server 8 | action(:upload) do 9 | invoke do |*args| 10 | Puppet::Node::Facts.indirection.terminus_class = :facter 11 | facts = Puppet::Node::Facts.indirection.find(Puppet[:certname]) 12 | Puppet::Node::Facts.indirection.terminus_class = :rest 13 | Puppet::Node::Facts.indirection.save(facts) 14 | Puppet.notice "Uploaded facts for '#{Puppet[:certname]}'" 15 | nil 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/puppet/interface/file.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:file) do 4 | set_indirection_name :file_bucket_file 5 | end 6 | -------------------------------------------------------------------------------- /lib/puppet/interface/indirector.rb: -------------------------------------------------------------------------------- 1 | require 'puppet' 2 | require 'puppet/interface' 3 | 4 | class Puppet::Interface::Indirector < Puppet::Interface 5 | def self.indirections 6 | Puppet::Indirector::Indirection.instances.collect { |t| t.to_s }.sort 7 | end 8 | 9 | def self.terminus_classes(indirection) 10 | Puppet::Indirector::Terminus.terminus_classes(indirection.to_sym).collect { |t| t.to_s }.sort 11 | end 12 | 13 | action :destroy do 14 | invoke { |*args| call_indirection_method(:destroy, *args) } 15 | end 16 | 17 | action :find do 18 | invoke { |*args| call_indirection_method(:find, *args) } 19 | end 20 | 21 | action :save do 22 | invoke { |*args| call_indirection_method(:save, *args) } 23 | end 24 | 25 | action :search do 26 | invoke { |*args| call_indirection_method(:search, *args) } 27 | end 28 | 29 | # Print the configuration for the current terminus class 30 | action :info do 31 | invoke do |*args| 32 | if t = indirection.terminus_class 33 | puts "Run mode '#{Puppet.run_mode.name}': #{t}" 34 | else 35 | $stderr.puts "No default terminus class for run mode '#{Puppet.run_mode.name}'" 36 | end 37 | end 38 | end 39 | 40 | attr_accessor :from 41 | 42 | def indirection_name 43 | @indirection_name || name.to_sym 44 | end 45 | 46 | # Here's your opportunity to override the indirection name. By default 47 | # it will be the same name as the interface. 48 | def set_indirection_name(name) 49 | @indirection_name = name 50 | end 51 | 52 | # Return an indirection associated with an interface, if one exists 53 | # One usually does. 54 | def indirection 55 | unless @indirection 56 | Puppet.info("Could not find terminus for #{indirection_name}") unless @indirection = Puppet::Indirector::Indirection.instance(indirection_name) 57 | end 58 | @indirection 59 | end 60 | 61 | def set_terminus(from) 62 | begin 63 | indirection.terminus_class = from 64 | rescue => detail 65 | raise "Could not set '#{indirection.name}' terminus to '#{from}' (#{detail}); valid terminus types are #{terminus_classes(indirection.name).join(", ") }" 66 | end 67 | end 68 | 69 | def call_indirection_method(method, *args) 70 | begin 71 | result = indirection.send(method, *args) 72 | rescue => detail 73 | puts detail.backtrace if Puppet[:trace] 74 | raise "Could not call '#{method}' on '#{indirection_name}': #{detail}" 75 | end 76 | 77 | result 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/puppet/interface/interface_collection.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface' 2 | 3 | module Puppet::Interface::InterfaceCollection 4 | @interfaces = {} 5 | 6 | def self.interfaces 7 | unless @loaded 8 | @loaded = true 9 | $LOAD_PATH.each do |dir| 10 | next unless FileTest.directory?(dir) 11 | Dir.chdir(dir) do 12 | Dir.glob("puppet/interface/*.rb").collect { |f| f.sub(/\.rb/, '') }.each do |file| 13 | iname = file.sub(/\.rb/, '') 14 | begin 15 | require iname 16 | rescue Exception => detail 17 | puts detail.backtrace if Puppet[:trace] 18 | raise "Could not load #{iname} from #{dir}/#{file}: #{detail}" 19 | end 20 | end 21 | end 22 | end 23 | end 24 | return @interfaces.keys 25 | end 26 | 27 | def self.[](name) 28 | @interfaces[underscorize(name)] if interface?(name) 29 | end 30 | 31 | def self.interface?(name) 32 | name = underscorize(name) 33 | require "puppet/interface/#{name}" unless @interfaces.has_key? name 34 | return @interfaces.has_key? name 35 | rescue LoadError 36 | return false 37 | end 38 | 39 | def self.register(interface) 40 | @interfaces[underscorize(interface.name)] = interface 41 | end 42 | 43 | def self.underscorize(name) 44 | unless name.to_s =~ /^[-_a-z]+$/i then 45 | raise ArgumentError, "#{name.inspect} (#{name.class}) is not a valid interface name" 46 | end 47 | 48 | name.to_s.downcase.split(/[-_]/).join('_').to_sym 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/puppet/interface/key.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:key) do 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface/node.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:node) do 4 | set_default_format :yaml 5 | end 6 | -------------------------------------------------------------------------------- /lib/puppet/interface/report.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:report) do 4 | action(:submit) do 5 | invoke do |report| 6 | begin 7 | Puppet::Transaction::Report.terminus_class = :rest 8 | report.save 9 | rescue => detail 10 | puts detail.backtrace if Puppet[:trace] 11 | Puppet.err "Could not send report: #{detail}" 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/puppet/interface/resource.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:resource) do 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface/resource_type.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:resource_type) do 4 | end 5 | -------------------------------------------------------------------------------- /lib/puppet/interface/status.rb: -------------------------------------------------------------------------------- 1 | require 'puppet/interface/indirector' 2 | 3 | Puppet::Interface::Indirector.interface(:status) do 4 | end 5 | -------------------------------------------------------------------------------- /spec/README.markdown: -------------------------------------------------------------------------------- 1 | Specs 2 | ===== 3 | 4 | The Puppet project uses RSpec for testing. 5 | 6 | For more information on RSpec, see http://rspec.info/ 7 | 8 | -------------------------------------------------------------------------------- /spec/spec.opts: -------------------------------------------------------------------------------- 1 | --format 2 | s 3 | --colour 4 | --loadby 5 | mtime 6 | --backtrace 7 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | dir = Pathname.new(__FILE__).parent 3 | $LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib') 4 | 5 | require 'mocha' 6 | require 'puppet' 7 | require 'rspec' 8 | 9 | RSpec.configure do |config| 10 | config.mock_with :mocha 11 | 12 | config.before :each do 13 | # Set the confdir and vardir to gibberish so that tests 14 | # have to be correctly mocked. 15 | Puppet[:confdir] = "/dev/null" 16 | Puppet[:vardir] = "/dev/null" 17 | 18 | # Avoid opening ports to the outside world 19 | Puppet.settings[:bindaddress] = "127.0.0.1" 20 | 21 | @logs = [] 22 | Puppet::Util::Log.newdestination(@logs) 23 | end 24 | 25 | config.after :each do 26 | Puppet.settings.clear 27 | 28 | @logs.clear 29 | Puppet::Util::Log.close_all 30 | end 31 | end 32 | 33 | # We need this because the RAL uses 'should' as a method. This 34 | # allows us the same behaviour but with a different method name. 35 | class Object 36 | alias :must :should 37 | end 38 | -------------------------------------------------------------------------------- /spec/unit/application/certificate.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/certificate' 5 | 6 | describe Puppet::Application::Certificate do 7 | it "should be a subclass of Puppet::Application::IndirectionBase" do 8 | Puppet::Application::Certificate.superclass.should equal( 9 | Puppet::Application::IndirectionBase 10 | ) 11 | end 12 | 13 | it "should have a 'ca' option" do 14 | Puppet::Application::Certificate.new.should respond_to(:handle_ca_location) 15 | end 16 | 17 | it "should set the CA location using the 'ca' option" do 18 | Puppet::Application::Certificate.new.handle_ca_location("local") 19 | Puppet::SSL::Host.indirection.terminus_class.should == :file 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/application/config_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/config' 5 | 6 | describe Puppet::Application::Config do 7 | it "should be a subclass of Puppet::Application::InterfaceBase" do 8 | Puppet::Application::Config.superclass.should equal(Puppet::Application::InterfaceBase) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/application/configurer_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/configurer' 5 | require 'puppet/indirector/catalog/rest' 6 | require 'puppet/indirector/report/rest' 7 | require 'tempfile' 8 | 9 | describe "Puppet::Application::Configurer" do 10 | it "should retrieve and apply a catalog and submit a report" do 11 | dirname = Dir.mktmpdir("puppetdir") 12 | Puppet[:vardir] = dirname 13 | Puppet[:confdir] = dirname 14 | Puppet[:certname] = "foo" 15 | @catalog = Puppet::Resource::Catalog.new 16 | @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) 17 | @catalog.add_resource(@file) 18 | 19 | @report = Puppet::Transaction::Report.new("apply") 20 | Puppet::Transaction::Report.stubs(:new).returns(@report) 21 | 22 | Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) 23 | @report.expects(:save) 24 | 25 | Puppet::Util::Log.stubs(:newdestination) 26 | 27 | Puppet::Application::Configurer.new.run 28 | 29 | @report.status.should == "changed" 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/unit/application/indirection_base_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/indirection_base' 5 | 6 | describe Puppet::Application::IndirectionBase do 7 | it "should support a 'from' terminus" 8 | 9 | describe "setup" do 10 | it "should fail if its interface does not support an indirection" 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/unit/application/interface_base_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/interface_base' 5 | 6 | describe Puppet::Application::InterfaceBase do 7 | base_interface = Puppet::Interface.interface(:basetest) 8 | class Puppet::Application::InterfaceBase::Basetest < Puppet::Application::InterfaceBase 9 | end 10 | 11 | before do 12 | @app = Puppet::Application::InterfaceBase::Basetest.new 13 | @app.stubs(:interface).returns base_interface 14 | @app.stubs(:exit) 15 | @app.stubs(:puts) 16 | Puppet::Util::Log.stubs(:newdestination) 17 | end 18 | 19 | describe "when calling main" do 20 | before do 21 | @app.verb = :find 22 | @app.arguments = ["myname", "myarg"] 23 | @app.interface.stubs(:find) 24 | end 25 | 26 | it "should send the specified verb and name to the interface" do 27 | @app.interface.expects(:find).with("myname", "myarg") 28 | 29 | @app.main 30 | end 31 | 32 | it "should use its render method to render any result" 33 | 34 | it "should exit with the current exit code" 35 | end 36 | 37 | describe "during setup" do 38 | before do 39 | @app.command_line.stubs(:args).returns(["find", "myname", "myarg"]) 40 | @app.stubs(:validate) 41 | end 42 | 43 | it "should set the verb from the command line arguments" do 44 | @app.setup 45 | @app.verb.should == "find" 46 | end 47 | 48 | it "should make sure arguments are an array" do 49 | @app.command_line.stubs(:args).returns(["find", "myname", "myarg"]) 50 | @app.setup 51 | @app.arguments.should == ["myname", "myarg"] 52 | end 53 | 54 | it "should set the options on the interface" do 55 | @app.options[:foo] = "bar" 56 | @app.setup 57 | 58 | @app.interface.options.should == @app.options 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/unit/application/interface_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/application/interface' 5 | 6 | describe Puppet::Application::Interface do 7 | it "should be an application" do 8 | Puppet::Application::Interface.superclass.should equal(Puppet::Application) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/interface/action_builder_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/action_builder' 5 | 6 | describe Puppet::Interface::ActionBuilder do 7 | describe "::build" do 8 | it "should build an action" do 9 | action = Puppet::Interface::ActionBuilder.build(nil,:foo) do 10 | end 11 | action.should be_a(Puppet::Interface::Action) 12 | action.name.should == "foo" 13 | end 14 | 15 | it "should define a method on the interface which invokes the action" do 16 | interface = Puppet::Interface.new(:action_builder_test_interface) 17 | action = Puppet::Interface::ActionBuilder.build(interface, :foo) do 18 | invoke do 19 | "invoked the method" 20 | end 21 | end 22 | 23 | interface.foo.should == "invoked the method" 24 | end 25 | 26 | it "should require a block" do 27 | lambda { Puppet::Interface::ActionBuilder.build(nil,:foo) }.should raise_error("Action 'foo' must specify a block") 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/unit/interface/action_manager_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | 5 | # This is entirely an internal class for Interface, so we have to load it instead of our class. 6 | require 'puppet/interface' 7 | 8 | class ActionManagerTester 9 | include Puppet::Interface::ActionManager 10 | end 11 | 12 | describe Puppet::Interface::ActionManager do 13 | subject { ActionManagerTester.new } 14 | 15 | describe "when included in a class" do 16 | it "should be able to define an action" do 17 | subject.action(:foo) do 18 | invoke { "something "} 19 | end 20 | end 21 | 22 | it "should be able to list defined actions" do 23 | subject.action(:foo) do 24 | invoke { "something" } 25 | end 26 | subject.action(:bar) do 27 | invoke { "something" } 28 | end 29 | 30 | subject.actions.should include(:bar) 31 | subject.actions.should include(:foo) 32 | end 33 | 34 | it "should be able to indicate when an action is defined" do 35 | subject.action(:foo) do 36 | invoke { "something" } 37 | end 38 | 39 | subject.should be_action(:foo) 40 | end 41 | end 42 | 43 | describe "when used to extend a class" do 44 | subject { Class.new.extend(Puppet::Interface::ActionManager) } 45 | 46 | it "should be able to define an action" do 47 | subject.action(:foo) do 48 | invoke { "something "} 49 | end 50 | end 51 | 52 | it "should be able to list defined actions" do 53 | subject.action(:foo) do 54 | invoke { "something" } 55 | end 56 | subject.action(:bar) do 57 | invoke { "something" } 58 | end 59 | 60 | subject.actions.should include(:bar) 61 | subject.actions.should include(:foo) 62 | end 63 | 64 | it "should be able to indicate when an action is defined" do 65 | subject.action(:foo) { "something" } 66 | subject.should be_action(:foo) 67 | end 68 | end 69 | 70 | describe "when used both at the class and instance level" do 71 | before do 72 | @klass = Class.new do 73 | include Puppet::Interface::ActionManager 74 | extend Puppet::Interface::ActionManager 75 | end 76 | @instance = @klass.new 77 | end 78 | 79 | it "should be able to define an action at the class level" do 80 | @klass.action(:foo) do 81 | invoke { "something "} 82 | end 83 | end 84 | 85 | it "should create an instance method when an action is defined at the class level" do 86 | @klass.action(:foo) do 87 | invoke { "something" } 88 | end 89 | @instance.foo.should == "something" 90 | end 91 | 92 | it "should be able to define an action at the instance level" do 93 | @instance.action(:foo) do 94 | invoke { "something "} 95 | end 96 | end 97 | 98 | it "should create an instance method when an action is defined at the instance level" do 99 | @instance.action(:foo) do 100 | invoke { "something" } 101 | end 102 | @instance.foo.should == "something" 103 | end 104 | 105 | it "should be able to list actions defined at the class level" do 106 | @klass.action(:foo) do 107 | invoke { "something" } 108 | end 109 | @klass.action(:bar) do 110 | invoke { "something" } 111 | end 112 | 113 | @klass.actions.should include(:bar) 114 | @klass.actions.should include(:foo) 115 | end 116 | 117 | it "should be able to list actions defined at the instance level" do 118 | @instance.action(:foo) do 119 | invoke { "something" } 120 | end 121 | @instance.action(:bar) do 122 | invoke { "something" } 123 | end 124 | 125 | @instance.actions.should include(:bar) 126 | @instance.actions.should include(:foo) 127 | end 128 | 129 | it "should be able to list actions defined at both instance and class level" do 130 | @klass.action(:foo) do 131 | invoke { "something" } 132 | end 133 | @instance.action(:bar) do 134 | invoke { "something" } 135 | end 136 | 137 | @instance.actions.should include(:bar) 138 | @instance.actions.should include(:foo) 139 | end 140 | 141 | it "should be able to indicate when an action is defined at the class level" do 142 | @klass.action(:foo) do 143 | invoke { "something" } 144 | end 145 | @instance.should be_action(:foo) 146 | end 147 | 148 | it "should be able to indicate when an action is defined at the instance level" do 149 | @klass.action(:foo) do 150 | invoke { "something" } 151 | end 152 | @instance.should be_action(:foo) 153 | end 154 | 155 | it "should list actions defined in superclasses" do 156 | @subclass = Class.new(@klass) 157 | @instance = @subclass.new 158 | 159 | @klass.action(:parent) do 160 | invoke { "a" } 161 | end 162 | @subclass.action(:sub) do 163 | invoke { "a" } 164 | end 165 | @instance.action(:instance) do 166 | invoke { "a" } 167 | end 168 | 169 | @instance.should be_action(:parent) 170 | @instance.should be_action(:sub) 171 | @instance.should be_action(:instance) 172 | end 173 | 174 | it "should create an instance method when an action is defined in a superclass" do 175 | @subclass = Class.new(@klass) 176 | @instance = @subclass.new 177 | 178 | @klass.action(:foo) do 179 | invoke { "something" } 180 | end 181 | @instance.foo.should == "something" 182 | end 183 | end 184 | end 185 | -------------------------------------------------------------------------------- /spec/unit/interface/action_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/action' 5 | 6 | describe Puppet::Interface::Action do 7 | describe "when validating the action name" do 8 | it "should require a name" do 9 | lambda { Puppet::Interface::Action.new(nil,nil) }.should raise_error("'' is an invalid action name") 10 | end 11 | 12 | it "should not allow empty names" do 13 | lambda { Puppet::Interface::Action.new(nil,'') }.should raise_error("'' is an invalid action name") 14 | end 15 | 16 | it "should not allow names with whitespace" do 17 | lambda { Puppet::Interface::Action.new(nil,'foo bar') }.should raise_error("'foo bar' is an invalid action name") 18 | end 19 | 20 | it "should not allow names beginning with dashes" do 21 | lambda { Puppet::Interface::Action.new(nil,'-foobar') }.should raise_error("'-foobar' is an invalid action name") 22 | end 23 | end 24 | 25 | describe "when invoking" do 26 | it "should be able to call other actions on the same object" do 27 | interface = Puppet::Interface.new(:my_interface) do 28 | action(:foo) do 29 | invoke { 25 } 30 | end 31 | 32 | action(:bar) do 33 | invoke { "the value of foo is '#{foo}'" } 34 | end 35 | end 36 | interface.foo.should == 25 37 | interface.bar.should == "the value of foo is '25'" 38 | end 39 | 40 | # bar is a class action calling a class action 41 | # quux is a class action calling an instance action 42 | # baz is an instance action calling a class action 43 | # qux is an instance action calling an instance action 44 | it "should be able to call other actions on the same object when defined on a class" do 45 | class Puppet::Interface::MyInterfaceBaseClass < Puppet::Interface 46 | action(:foo) do 47 | invoke { 25 } 48 | end 49 | 50 | action(:bar) do 51 | invoke { "the value of foo is '#{foo}'" } 52 | end 53 | 54 | action(:quux) do 55 | invoke { "qux told me #{qux}" } 56 | end 57 | end 58 | 59 | interface = Puppet::Interface::MyInterfaceBaseClass.new(:my_inherited_interface) do 60 | action(:baz) do 61 | invoke { "the value of foo in baz is '#{foo}'" } 62 | end 63 | 64 | action(:qux) do 65 | invoke { baz } 66 | end 67 | end 68 | interface.foo.should == 25 69 | interface.bar.should == "the value of foo is '25'" 70 | interface.quux.should == "qux told me the value of foo in baz is '25'" 71 | interface.baz.should == "the value of foo in baz is '25'" 72 | interface.qux.should == "the value of foo in baz is '25'" 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/unit/interface/catalog_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/catalog' 5 | 6 | describe Puppet::Interface::Indirector.interface(:catalog) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/certificate_request_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/certificate_request' 5 | 6 | describe Puppet::Interface::Indirector.interface(:certificate_request) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/certificate_revocation_list_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/certificate_revocation_list' 5 | 6 | describe Puppet::Interface::Indirector.interface(:certificate_revocation_list) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/certificate' 5 | 6 | describe Puppet::Interface::Indirector.interface(:certificate) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/config_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/config' 5 | 6 | describe Puppet::Interface.interface(:config) do 7 | it "should use Settings#print_config_options when asked to print" do 8 | Puppet.settings.stubs(:puts) 9 | Puppet.settings.expects(:print_config_options) 10 | subject.print 11 | end 12 | 13 | it "should set 'configprint' to all desired values and call print_config_options when a specific value is provided" do 14 | Puppet.settings.stubs(:puts) 15 | Puppet.settings.expects(:print_config_options) 16 | subject.print("libdir", "ssldir") 17 | Puppet.settings[:configprint].should == "libdir,ssldir" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/interface/configurer_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/configurer' 5 | require 'puppet/indirector/catalog/rest' 6 | require 'tempfile' 7 | 8 | describe Puppet::Interface.interface(:configurer) do 9 | describe "#synchronize" do 10 | it "should retrieve and apply a catalog and return a report" do 11 | dirname = Dir.mktmpdir("puppetdir") 12 | Puppet[:vardir] = dirname 13 | Puppet[:confdir] = dirname 14 | @catalog = Puppet::Resource::Catalog.new 15 | @file = Puppet::Resource.new(:file, File.join(dirname, "tmp_dir_resource"), :parameters => {:ensure => :present}) 16 | @catalog.add_resource(@file) 17 | Puppet::Resource::Catalog::Rest.any_instance.stubs(:find).returns(@catalog) 18 | 19 | report = Puppet::Interface.interface(:configurer).synchronize("foo") 20 | 21 | report.kind.should == "apply" 22 | report.status.should == "changed" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/unit/interface/facts_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/facts' 5 | 6 | describe Puppet::Interface::Indirector.interface(:facts) do 7 | it "should define an 'upload' fact" do 8 | subject.should be_action(:upload) 9 | end 10 | 11 | it "should set its default format to :yaml" do 12 | subject.default_format.should == :yaml 13 | end 14 | 15 | describe "when uploading" do 16 | it "should set the terminus_class to :facter" 17 | 18 | it "should set the cach_eclass to :rest" 19 | 20 | it "should find the current certname" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/interface/file_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/file' 5 | 6 | describe Puppet::Interface::Indirector.interface(:file) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/indirector_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/indirector' 5 | 6 | describe Puppet::Interface::Indirector do 7 | before do 8 | @instance = Puppet::Interface::Indirector.new(:test) 9 | 10 | @indirection = stub 'indirection', :name => :stub_indirection 11 | 12 | @instance.stubs(:indirection).returns @indirection 13 | end 14 | 15 | it "should be able to return a list of indirections" do 16 | Puppet::Interface::Indirector.indirections.should be_include("catalog") 17 | end 18 | 19 | it "should be able to return a list of terminuses for a given indirection" do 20 | Puppet::Interface::Indirector.terminus_classes(:catalog).should be_include("compiler") 21 | end 22 | 23 | describe "as an instance" do 24 | it "should be able to determine its indirection" do 25 | # Loading actions here an get, um, complicated 26 | Puppet::Interface.stubs(:load_actions) 27 | Puppet::Interface::Indirector.new(:catalog).indirection.should equal(Puppet::Resource::Catalog.indirection) 28 | end 29 | end 30 | 31 | [:find, :search, :save, :destroy].each do |method| 32 | it "should define a '#{method}' action" do 33 | Puppet::Interface::Indirector.should be_action(method) 34 | end 35 | 36 | it "should just call the indirection method when the '#{method}' action is invoked" do 37 | @instance.indirection.expects(method).with(:test, "myargs") 38 | @instance.send(method, :test, "myargs") 39 | end 40 | end 41 | 42 | it "should be able to override its indirection name" do 43 | @instance.set_indirection_name :foo 44 | @instance.indirection_name.should == :foo 45 | end 46 | 47 | it "should be able to set its terminus class" do 48 | @instance.indirection.expects(:terminus_class=).with(:myterm) 49 | @instance.set_terminus(:myterm) 50 | end 51 | 52 | it "should define a class-level 'info' action" do 53 | Puppet::Interface::Indirector.should be_action(:info) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/unit/interface/interface_collection_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 2 | 3 | require 'puppet/interface/interface_collection' 4 | 5 | describe Puppet::Interface::InterfaceCollection do 6 | # This is global state that other tests depend on, so we have to save and 7 | # restore it 8 | before :all do 9 | @saved_interfaces = subject.instance_variable_get("@interfaces").dup 10 | end 11 | 12 | before :each do 13 | subject.instance_variable_set("@interfaces", {}) 14 | end 15 | 16 | after :all do 17 | subject.instance_variable_set("@interfaces", @saved_interfaces) 18 | end 19 | 20 | describe "::interfaces" do 21 | end 22 | 23 | describe "::[]" do 24 | before :each do 25 | subject.instance_variable_set("@interfaces", {:foo => 10}) 26 | end 27 | 28 | it "should return the interface with the given name" do 29 | subject["foo"].should == 10 30 | end 31 | 32 | it "should attempt to load the interface if it isn't found" do 33 | subject.expects(:require).with('puppet/interface/bar') 34 | subject["bar"] 35 | end 36 | end 37 | 38 | describe "::interface?" do 39 | before :each do 40 | subject.instance_variable_set("@interfaces", {:foo => 10}) 41 | end 42 | 43 | it "should return true if the interface specified is registered" do 44 | subject.interface?("foo").should == true 45 | end 46 | 47 | it "should attempt to require the interface if it is not registered" do 48 | subject.expects(:require).with('puppet/interface/bar') 49 | subject.interface?("bar") 50 | end 51 | 52 | it "should return true if requiring the interface registered it" do 53 | subject.stubs(:require).with do 54 | subject.instance_variable_set("@interfaces", {:bar => 20}) 55 | end 56 | subject.interface?("bar").should == true 57 | end 58 | 59 | it "should return false if the interface is not registered" do 60 | subject.stubs(:require).returns(true) 61 | subject.interface?("bar").should == false 62 | end 63 | 64 | it "should return false if there is a LoadError requiring the interface" do 65 | subject.stubs(:require).raises(LoadError) 66 | subject.interface?("bar").should == false 67 | end 68 | end 69 | 70 | describe "::register" do 71 | it "should store the interface by name" do 72 | interface = Puppet::Interface.new(:my_interface) 73 | subject.register(interface) 74 | subject.instance_variable_get("@interfaces").should == {:my_interface => interface} 75 | end 76 | end 77 | 78 | describe "::underscorize" do 79 | faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"] 80 | valid = { 81 | "Foo" => :foo, 82 | :Foo => :foo, 83 | "foo_bar" => :foo_bar, 84 | :foo_bar => :foo_bar, 85 | "foo-bar" => :foo_bar, 86 | :"foo-bar" => :foo_bar, 87 | } 88 | 89 | valid.each do |input, expect| 90 | it "should map #{input.inspect} to #{expect.inspect}" do 91 | result = subject.underscorize(input) 92 | result.should == expect 93 | end 94 | end 95 | 96 | faulty.each do |input| 97 | it "should fail when presented with #{input.inspect} (#{input.class})" do 98 | expect { subject.underscorize(input) }. 99 | should raise_error ArgumentError, /not a valid interface name/ 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/unit/interface/key_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/key' 5 | 6 | describe Puppet::Interface::Indirector.interface(:key) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/node_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/node' 5 | 6 | describe Puppet::Interface::Indirector.interface(:node) do 7 | it "should set its default format to :yaml" do 8 | subject.default_format.should == :yaml 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/unit/interface/report_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/report' 5 | 6 | describe Puppet::Interface::Indirector.interface(:report) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/resource_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/resource' 5 | 6 | describe Puppet::Interface::Indirector.interface(:resource) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface/resource_type_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb') 4 | require 'puppet/interface/resource_type' 5 | 6 | describe Puppet::Interface::Indirector.interface(:resource_type) do 7 | end 8 | -------------------------------------------------------------------------------- /spec/unit/interface_spec.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper.rb') 4 | require 'puppet/interface' 5 | 6 | describe Puppet::Interface do 7 | describe "#interface" do 8 | it "should register the interface" do 9 | interface = Puppet::Interface.interface(:interface_test_register) 10 | interface.should == Puppet::Interface.interface(:interface_test_register) 11 | end 12 | 13 | it "should load actions" do 14 | Puppet::Interface.any_instance.expects(:load_actions) 15 | Puppet::Interface.interface(:interface_test_load_actions) 16 | end 17 | 18 | it "should instance-eval any provided block" do 19 | face = Puppet::Interface.new(:interface_test_block) do 20 | action(:something) do 21 | invoke { "foo" } 22 | end 23 | end 24 | 25 | face.something.should == "foo" 26 | end 27 | end 28 | 29 | it "should have a name" do 30 | Puppet::Interface.new(:me).name.should == :me 31 | end 32 | 33 | it "should stringify with its own name" do 34 | Puppet::Interface.new(:me).to_s.should =~ /\bme\b/ 35 | end 36 | 37 | it "should allow overriding of the default format" do 38 | face = Puppet::Interface.new(:me) 39 | face.set_default_format :foo 40 | face.default_format.should == :foo 41 | end 42 | 43 | it "should default to :pson for its format" do 44 | Puppet::Interface.new(:me).default_format.should == :pson 45 | end 46 | 47 | # Why? 48 | it "should create a class-level autoloader" do 49 | Puppet::Interface.autoloader.should be_instance_of(Puppet::Util::Autoload) 50 | end 51 | 52 | it "should set any provided options" do 53 | Puppet::Interface.new(:me, :verb => "foo").verb.should == "foo" 54 | end 55 | 56 | it "should try to require interfaces that are not known" do 57 | Puppet::Interface::InterfaceCollection.expects(:require).with "puppet/interface/foo" 58 | Puppet::Interface.interface(:foo) 59 | end 60 | 61 | it "should be able to load all actions in all search paths" 62 | end 63 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/README.markdown: -------------------------------------------------------------------------------- 1 | Provider Specs 2 | ============== 3 | 4 | Define specs for your providers under this directory. 5 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/README.markdown: -------------------------------------------------------------------------------- 1 | Resource Type Specs 2 | =================== 3 | 4 | Define specs for your resource types in this directory. 5 | -------------------------------------------------------------------------------- /spec/watchr.rb: -------------------------------------------------------------------------------- 1 | ENV["WATCHR"] = "1" 2 | ENV['AUTOTEST'] = 'true' 3 | 4 | def run_comp(cmd) 5 | puts cmd 6 | results = [] 7 | old_sync = $stdout.sync 8 | $stdout.sync = true 9 | line = [] 10 | begin 11 | open("| #{cmd}", "r") do |f| 12 | until f.eof? do 13 | c = f.getc 14 | putc c 15 | line << c 16 | if c == ?\n 17 | results << if RUBY_VERSION >= "1.9" then 18 | line.join 19 | else 20 | line.pack "c*" 21 | end 22 | line.clear 23 | end 24 | end 25 | end 26 | ensure 27 | $stdout.sync = old_sync 28 | end 29 | results.join 30 | end 31 | 32 | def clear 33 | #system("clear") 34 | end 35 | 36 | def growl(message, status) 37 | # Strip the color codes 38 | message.gsub!(/\[\d+m/, '') 39 | 40 | growlnotify = `which growlnotify`.chomp 41 | return if growlnotify.empty? 42 | title = "Watchr Test Results" 43 | image = status == :pass ? "autotest/images/pass.png" : "autotest/images/fail.png" 44 | options = "-w -n Watchr --image '#{File.expand_path(image)}' -m '#{message}' '#{title}'" 45 | system %(#{growlnotify} #{options} &) 46 | end 47 | 48 | def file2specs(file) 49 | %w{spec/unit spec/integration}.collect { |d| 50 | file.sub('lib/puppet', d).sub('.rb', '_spec.rb') 51 | }.find_all { |f| 52 | FileTest.exist?(f) 53 | } 54 | end 55 | 56 | def run_spec(command) 57 | clear 58 | result = run_comp(command).split("\n").last 59 | status = result.include?('0 failures') ? :pass : :fail 60 | growl result, status 61 | end 62 | 63 | def run_spec_files(files) 64 | files = Array(files) 65 | return if files.empty? 66 | opts = File.readlines('spec/spec.opts').collect { |l| l.chomp }.join(" ") 67 | begin 68 | run_spec("rspec #{files.join(' ')}") 69 | rescue => detail 70 | puts detail.backtrace 71 | warn "Failed to run #{files.join(', ')}: #{detail}" 72 | end 73 | end 74 | 75 | def run_suite 76 | files = files("unit") + files("integration") 77 | run_spec("rspec #{files.join(' ')}") 78 | end 79 | 80 | def files(dir) 81 | require 'find' 82 | 83 | result = [] 84 | Find.find(File.join("spec", dir)) do |path| 85 | result << path if path =~ /\.rb/ 86 | end 87 | 88 | result 89 | end 90 | 91 | watch('spec/spec_helper.rb') { run_suite } 92 | watch(%r{^spec/(unit|integration)/.*\.rb$}) { |md| run_spec_files(md[0]) } 93 | watch(%r{^lib/puppet/(.*)\.rb$}) { |md| 94 | run_spec_files(file2specs(md[0])) 95 | } 96 | watch(%r{^spec/lib/spec.*}) { |md| run_suite } 97 | watch(%r{^spec/lib/monkey_patches/.*}) { |md| run_suite } 98 | 99 | # Ctrl-\ 100 | Signal.trap 'QUIT' do 101 | puts " --- Running all tests ---\n\n" 102 | run_suite 103 | end 104 | 105 | @interrupted = false 106 | 107 | # Ctrl-C 108 | Signal.trap 'INT' do 109 | if @interrupted 110 | @wants_to_quit = true 111 | abort("\n") 112 | else 113 | puts "Interrupt a second time to quit; wait for rerun of tests" 114 | @interrupted = true 115 | Kernel.sleep 1.5 116 | # raise Interrupt, nil # let the run loop catch it 117 | begin 118 | run_suite 119 | rescue => detail 120 | puts detail.backtrace 121 | puts "Could not run suite: #{detail}" 122 | end 123 | end 124 | end 125 | --------------------------------------------------------------------------------