#!/usr/bin/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 <host>]
# [-p|--param <param>] [-t|--types] type <name>
#
# = 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 <directory>' 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
#
# $ 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'
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 ]
]
# Add all of the config parameters as valid options.
Puppet.config.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.config.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 == "" or value == []
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
comp = Puppet::Type.type(:component).create(:name => "ralsh")
comp.push(obj)
transaction = comp.evaluate
begin
transaction.evaluate
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
# $Id: ralsh 2573 2007-06-13 22:31:52Z luke $
syntax highlighted by Code2HTML, v. 0.9.1