#!/usr/bin/env ruby # vim: softtabstop=4 shiftwidth=4 expandtab # # = Synopsis # # Use the Puppet RAL to directly interact with the system. # # = Usage # # ralsh [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit] [-H|--host ] # [-p|--param ] [-t|--types] type # # = Description # # This command provides simple facilities for converting current system state # into Puppet code, along with some ability to use Puppet to affect the current # state. # # By default, you must at least provide a type to list, which case ralsh # will tell you everything it knows about all instances of that type. You can # optionally specify an instance name, and ralsh will only describe that single # instance. # # You can also add +--edit+ as an argument, and ralsh will write its output # to a file, open that file in an editor, and then apply the file as a Puppet # transaction. You can easily use this to use Puppet to make simple changes to # a system. # # = Options # # Note that any configuration parameter that's valid in the configuration file # is also a valid long argument. For example, 'ssldir' is a valid configuration # parameter, so you can specify '--ssldir ' as an argument. # # See the configuration file documentation at # http://reductivelabs.com/projects/puppet/reference/configref.html for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppet with # '--genconfig'. # # debug:: # Enable full debugging. # # edit: # Write the results of the query to a file, open the file in an editor, # and read the file back in as an executable Puppet manifest. # # host: # When specified, connect to the resource server on the named host # and retrieve the list of resouces of the type specified. # # help: # Print this help message. # # param: # Add more parameters to be outputted from queries. # # types: # List all available types. # # verbose:: # Print extra information. # # = Example # # This example uses ``ralsh`` to return Puppet configuration for the user ``luke``:: # # $ ralsh user luke # user { 'luke': # home => '/home/luke', # uid => '100', # ensure => 'present', # comment => 'Luke Kanies,,,', # gid => '1000', # shell => '/bin/bash', # groups => ['sysadmin','audio','video','puppet'] # } # # = Author # # Luke Kanies # # = Copyright # # Copyright (c) 2005-2007 Reductive Labs, LLC # Licensed under the GNU Public License require 'getoptlong' require 'puppet' require 'facter' options = [ [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--types", "-t", GetoptLong::NO_ARGUMENT ], [ "--param", "-p", GetoptLong::REQUIRED_ARGUMENT ], [ "--host", "-H", GetoptLong::REQUIRED_ARGUMENT ], [ "--edit", "-e", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ] # Load facts from Facter Facter.loadfacts # Add all of the config parameters as valid options. Puppet.settings.addargs(options) result = GetoptLong.new(*options) debug = false verbose = false edit = false extra_params = [] host = nil result.each { |opt,arg| case opt when "--host" host = arg when "--types" types = [] Puppet::Type.loadall Puppet::Type.eachtype do |t| next if t.name == :component types << t.name.to_s end puts types.sort exit when "--param" extra_params << arg.to_sym when "--edit" edit = true when "--help" if Puppet.features.usage? RDoc::usage else puts "install RDoc:usage for help" end exit when "--verbose" verbose = true when "--debug" debug = true else # Anything else is handled by the config stuff Puppet.settings.handlearg(opt, arg) end } Puppet::Util::Log.newdestination(:console) # Now parse the config Puppet.parse_config if debug Puppet::Util::Log.level = :debug elsif verbose Puppet::Util::Log.level = :info end if ARGV.length > 0 type = ARGV.shift else raise "You must specify the type to display" end name = nil params = {} if ARGV.length > 0 name = ARGV.shift end if ARGV.length > 0 ARGV.each do |setting| if setting =~ /^(\w+)=(.+)$/ params[$1] = $2 else raise "Invalid parameter setting %s" % setting end end end if edit and host raise "You cannot edit a remote host" end typeobj = nil unless typeobj = Puppet::Type.type(type) raise "Could not find type %s" % type end properties = typeobj.properties.collect { |s| s.name } format = proc {|trans| trans.dup.collect do |param, value| if value.nil? or value.to_s.empty? trans.delete(param) elsif value.to_s == "absent" and param.to_s != "ensure" trans.delete(param) end unless properties.include?(param) or extra_params.include?(param) trans.delete(param) end end trans.to_manifest } text = if host client = Puppet::Network::Client.resource.new(:Server => host, :Port => Puppet[:puppetport]) unless client.read_cert raise "client.read_cert failed" end begin # They asked for a single resource. if name transbucket = [client.describe(type, name)] else # Else, list the whole thing out. transbucket = client.instances(type) end rescue Puppet::Network::XMLRPCClientError => exc raise "client.list(#{type}) failed: #{exc.message}" end transbucket.sort { |a,b| a.name <=> b.name }.collect(&format) else if name obj = typeobj.create(:name => name, :check => properties) vals = obj.retrieve unless params.empty? params.each do |param, value| obj[param] = value end catalog = Puppet::Node::Catalog.new catalog.add_resource obj begin catalog.apply rescue => detail if Puppet[:trace] puts detail.backtrace end end end [format.call(obj.to_trans(true))] else typeobj.instances.collect do |obj| next if ARGV.length > 0 and ! ARGV.include? obj.name trans = obj.to_trans(true) format.call(trans) end end end.compact.join("\n") if edit file = "/tmp/x2puppet-#{Process.pid}.pp" begin File.open(file, "w") do |f| f.puts text end ENV["EDITOR"] ||= "vi" system(ENV["EDITOR"], file) system("puppet -v " + file) ensure #if FileTest.exists? file # File.unlink(file) #end end else puts text end