Revision: 278
http://etch.svn.sourceforge.net/etch/?rev=278&view=rev
Author: jheiss
Date: 2011-05-04 17:43:47 +0000 (Wed, 04 May 2011)
Log Message:
-----------
Clean up the client directory structure to look like a real ruby project
Modified Paths:
--------------
trunk/client/Rakefile
Added Paths:
-----------
trunk/client/bin/
trunk/client/bin/etch
trunk/client/bin/etch_cron_wrapper
trunk/client/bin/etch_to_trunk
trunk/client/etc/
trunk/client/etc/cron.d/
trunk/client/etc/cron.d/etch
trunk/client/etc/etch/
trunk/client/etc/etch/ca.pem
trunk/client/etc/etch/dhparams
trunk/client/etc/etch.conf
trunk/client/lib/
trunk/client/lib/etch/
trunk/client/lib/etch/client.rb
trunk/client/man/
trunk/client/man/man8/
trunk/client/man/man8/etch.8
trunk/client/packages/
trunk/client/packages/deb/
trunk/client/packages/deb/control
trunk/client/packages/gem/
trunk/client/packages/gem/gemspec
trunk/client/packages/macports/
trunk/client/packages/macports/Portfile
trunk/client/packages/macports/Portfile.template
trunk/client/packages/rpm/
trunk/client/packages/rpm/etch-client.spec
trunk/client/packages/rpm/etch.spec
trunk/client/packages/sysv/
trunk/client/packages/sysv/depend
trunk/client/packages/sysv/pkginfo
trunk/client/packages/sysv/postinstall.solaris
trunk/client/packages/sysv/postremove.solaris
Removed Paths:
-------------
trunk/client/Portfile
trunk/client/Portfile.template
trunk/client/ca.pem
trunk/client/control
trunk/client/depend
trunk/client/dhparams
trunk/client/etch
trunk/client/etch-client.spec
trunk/client/etch.8
trunk/client/etch.conf
trunk/client/etch.spec
trunk/client/etch_cron
trunk/client/etch_cron_wrapper
trunk/client/etch_to_trunk
trunk/client/etchclient.rb
trunk/client/gemspec
trunk/client/pkginfo
trunk/client/postinstall.solaris
trunk/client/postremove.solaris
Deleted: trunk/client/Portfile
===================================================================
--- trunk/client/Portfile 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/Portfile 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,41 +0,0 @@
-# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
-# $Id$
-
-PortSystem 1.0
-PortGroup ruby 1.0
-
-name etch
-version 3.17.0
-categories sysutils
-maintainers aput.net:jheiss openmaintainer
-
-description Etch is a system configuration management tool.
-
-long_description Etch is a tool for managing the configuration of \
- Unix systems. Etch can manage text or binary \
- files, links and directories. The contents of \
- files can be supplied from static files or \
- generated on the fly by scripts or templates. \
- Permissions and ownership as well as any pre or \
- post commands to run when updating the file are \
- configured in simple XML files.
-
-homepage http://etch.sourceforge.net/
-platforms darwin
-
-master_sites sourceforge
-
-checksums md5 9b5216f62d4add225f50984cc07f630b \
- sha1 a4bc1c61f349464ca793d858abeca08760bde6e5 \
- rmd160 f827b360c69cceab1a864b11c7cf434b7c7a4a39
-
-depends_build port:rb-rake
-depends_run port:facter
-
-worksrcdir ${worksrcdir}/client
-supported_archs noarch
-use_configure no
-build {}
-destroot.cmd ${prefix}/bin/rake
-destroot.target install\[${destroot}\]
-destroot.destdir
Deleted: trunk/client/Portfile.template
===================================================================
--- trunk/client/Portfile.template 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/Portfile.template 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,42 +0,0 @@
-# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
-# $Id$
-
-PortSystem 1.0
-PortGroup ruby 1.0
-
-name etch
-version %VER%
-categories sysutils
-maintainers aput.net:jheiss openmaintainer
-supported_archs noarch
-
-description Etch is a system configuration management tool.
-
-long_description Etch is a tool for managing the configuration of \
- Unix systems. Etch can manage text or binary \
- files, links and directories. The contents of \
- files can be supplied from static files or \
- generated on the fly by scripts or templates. \
- Permissions and ownership as well as any pre or \
- post commands to run when updating the file are \
- configured in simple XML files.
-
-homepage http://etch.sourceforge.net/
-platforms darwin
-
-master_sites sourceforge
-
-checksums md5 %MD5% \
- sha1 %SHA1% \
- rmd160 %RMD160%
-
-depends_build port:rb-rake
-depends_run port:facter
-
-worksrcdir ${worksrcdir}/client
-supported_archs noarch
-use_configure no
-build {}
-destroot.cmd ${prefix}/bin/rake
-destroot.target install\[${destroot}\]
-destroot.destdir
Modified: trunk/client/Rakefile
===================================================================
--- trunk/client/Rakefile 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/Rakefile 2011-05-04 17:43:47 UTC (rev 278)
@@ -28,7 +28,7 @@
if options[:ruby]
# Change #! line
File.open(File.join(bindir, binapp), 'w') do |newfile|
- File.open(binapp) do |oldfile|
+ File.open(File.join('bin', binapp)) do |oldfile|
# Modify the first line
firstline = oldfile.gets
# Preserve any options. I.e. #!/usr/bin/ruby -w
@@ -39,7 +39,7 @@
end
end
else
- cp(binapp, bindir, :preserve => true)
+ cp(File.join('bin', binapp), bindir, :preserve => true)
end
chmod(0555, File.join(bindir, binapp))
end
@@ -48,11 +48,12 @@
if options[:libdir]
libdir = File.join(destdir, options[:libdir])
mkdir_p(libdir)
+ mkdir_p(File.join(libdir, 'etch'))
- # Substitute ETCHVER into etchclient.rb
- # Substitute proper path into CONFIGDIR in etchclient.rb if appropriate
- File.open(File.join(libdir, 'etchclient.rb'), 'w') do |newfile|
- IO.foreach('etchclient.rb') do |line|
+ # Substitute ETCHVER into etch/client.rb
+ # Substitute proper path into CONFIGDIR in etch/client.rb if appropriate
+ File.open(File.join(libdir, 'etch', 'client.rb'), 'w') do |newfile|
+ IO.foreach(File.join('etch', 'client.rb')) do |line|
if line =~ /^\s*VERSION/
line.sub!(/=.*/, "= '#{ETCHVER}'")
end
@@ -62,9 +63,9 @@
newfile.write(line)
end
end
- chmod(0444, File.join(libdir, 'etchclient.rb'))
+ chmod(0444, File.join(libdir, 'etch', 'client.rb'))
- serverlibs = ['etch.rb', 'versiontype.rb']
+ serverlibs = ['etch.rb', 'silently.rb', 'versiontype.rb']
serverlibs.each do |serverlib|
cp(File.join('..', 'server', 'lib', serverlib), libdir, :preserve => true)
chmod(0444, File.join(libdir, serverlib))
@@ -75,21 +76,21 @@
mandir = File.join(destdir, options[:mandir])
man8dir = File.join(mandir, 'man8')
mkdir_p(man8dir)
- cp('etch.8', man8dir, :preserve => true)
+ cp(File.join('man', 'man8', 'etch.8'), man8dir, :preserve => true)
chmod(0444, File.join(man8dir, 'etch.8'))
end
if options[:etcdir]
etcdir = File.join(destdir, options[:etcdir])
mkdir_p(etcdir)
- cp('etch.conf', etcdir, :preserve => true)
+ cp(File.join('etc', 'etch.conf'), etcdir, :preserve => true)
chmod(0644, File.join(etcdir, 'etch.conf'))
# All of the supporting config files go into a subdirectory
etcetchdir = File.join(etcdir, 'etch')
mkdir_p(etcetchdir)
etcetchfiles = ['ca.pem', 'dhparams']
etcetchfiles.each do |etcetchfile|
- cp(etcetchfile, etcetchdir, :preserve => true)
+ cp(File.join('etc', 'etch', etcetchfile), etcetchdir, :preserve => true)
chmod(0644, File.join(etcetchdir, etcetchfile))
end
end
@@ -97,9 +98,7 @@
if options[:crondir]
crondir = File.join(destdir, options[:crondir])
mkdir_p(crondir)
- # Note file renamed to 'etch' here. Filename is different in the repo for
- # clarity and to avoid conflict with the main executable.
- cp('etch_cron', File.join(crondir, 'etch'), :preserve => true)
+ cp(File.join('etc', 'cron.d', 'etch'), crondir, :preserve => true)
chmod(0444, File.join(crondir, 'etch'))
end
end
@@ -132,7 +131,7 @@
#
spec = Tempfile.new('etchrpm')
- IO.foreach('etch-client.spec') do |line|
+ IO.foreach(File.join('packages', 'rpm', 'etch-client.spec')) do |line|
line.sub!('%VER%', ETCHVER)
spec.puts(line)
end
@@ -161,7 +160,7 @@
mkdir_p(File.join(BUILDROOT, 'DEBIAN'))
File.open(File.join(BUILDROOT, 'DEBIAN', 'control'), 'w') do |control|
- IO.foreach('control') do |line|
+ IO.foreach(File.join('packages', 'deb', 'control')) do |line|
next if line =~ /^\s*#/ # Remove comments
line.sub!('%VER%', ETCHVER)
control.puts(line)
@@ -220,19 +219,19 @@
rm_rf('solbuild')
mkdir('solbuild')
File.open(File.join('solbuild', 'pkginfo'), 'w') do |pkginfo|
- IO.foreach('pkginfo') do |line|
+ IO.foreach(File.join('packages', 'sysv', 'pkginfo')) do |line|
line.sub!('%VER%', ETCHVER)
pkginfo.puts(line)
end
end
File.open(File.join('solbuild', 'prototype'), 'w') do |prototype|
prototype.puts("i pkginfo=./pkginfo")
- cp('depend', 'solbuild/depend')
+ cp(File.join('packages', 'sysv', 'depend'), 'solbuild/depend')
prototype.puts("i depend=./depend")
- cp('postinstall.solaris', 'solbuild/postinstall')
+ cp(File.join('packages', 'sysv', 'postinstall.solaris'), 'solbuild/postinstall')
chmod(0555, 'solbuild/postinstall')
prototype.puts("i postinstall=./postinstall")
- cp('postremove.solaris', 'solbuild/postremove')
+ cp(File.join('packages', 'sysv', 'postremove.solaris'), 'solbuild/postremove')
chmod(0555, 'solbuild/postremove')
prototype.puts("i postremove=./postremove")
# The tail +2 removes the first line, which is the base directory
@@ -311,19 +310,19 @@
rm_rf('solbuild')
mkdir('solbuild')
File.open(File.join('solbuild', 'pkginfo'), 'w') do |pkginfo|
- IO.foreach('pkginfo') do |line|
+ IO.foreach(File.join('packages', 'sysv', 'pkginfo')) do |line|
line.sub!('%VER%', ETCHVER)
pkginfo.puts(line)
end
end
File.open(File.join('solbuild', 'prototype'), 'w') do |prototype|
prototype.puts("i pkginfo=./pkginfo")
- cp('depend', 'solbuild/depend')
+ cp(File.join('packages', 'sysv', 'depend'), 'solbuild/depend')
prototype.puts("i depend=./depend")
- cp('postinstall.solaris', 'solbuild/postinstall')
+ cp(File.join('packages', 'sysv', 'postinstall.solaris'), 'solbuild/postinstall')
chmod(0555, 'solbuild/postinstall')
prototype.puts("i postinstall=./postinstall")
- cp('postremove.solaris', 'solbuild/postremove')
+ cp(File.join('packages', 'sysv', 'postremove.solaris'), 'solbuild/postremove')
chmod(0555, 'solbuild/postremove')
prototype.puts("i postremove=./postremove")
# The tail +2 removes the first line, which is the base directory
@@ -395,7 +394,7 @@
portfile = File.join(Dir.tmpdir, 'Portfile')
rm_f(portfile)
File.open(portfile, 'w') do |newfile|
- IO.foreach('Portfile.template') do |line|
+ IO.foreach(File.join('packages', 'macports', 'Portfile.template')) do |line|
line.sub!('%VER%', ETCHVER)
line.sub!('%MD5%', md5)
line.sub!('%SHA1%', sha1)
@@ -421,7 +420,7 @@
# Prep gemspec (renaming to Rakefile in the process)
#
File.open(File.join(BUILDROOT, 'Rakefile'), 'w') do |gemspec|
- IO.foreach('gemspec') do |line|
+ IO.foreach(File.join('packages', 'gem', 'gemspec')) do |line|
line.sub!('%VER%', ETCHVER)
gemspec.puts(line)
end
Copied: trunk/client/bin/etch (from rev 272, trunk/client/etch)
===================================================================
--- trunk/client/bin/etch (rev 0)
+++ trunk/client/bin/etch 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,111 @@
+#!/usr/bin/ruby -w
+##############################################################################
+# Etch configuration file management tool
+##############################################################################
+
+# Ensure we can find etch/client.rb when run within the development repository
+$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
+
+require 'optparse'
+require 'etch/client'
+
+#
+# Parse the command line options
+#
+
+options = {}
+@generateall = nil
+
+# Extra options to OptionParser reduce the amount of whitespace it introduces
+# into the help message, making it easier to make the help message fit in a
+# 80x24 window.
+opts = OptionParser.new(nil, 24, ' ')
+opts.banner = 'Usage: etch [options] [/path/to/config/file | command] [otherfile ...]'
+opts.on('--generate-all', 'Request all configuration.') do |opt|
+ @generateall = opt
+end
+opts.on('--dry-run', '-n', 'Make no changes.') do |opt|
+ options[:dryrun] = opt
+end
+opts.on('--damp-run', "Perform a dry run but run 'setup' entries for files.") do |opt|
+ # Rather than sprinkle checks of two different variables throught the code, if
+ # we're asked to do a damp run then just set the dry run flag to a unique
+ # value. Then we can just check for that specific value at the one place where
+ # the two modes differ: enabling or disabling the execution of 'setup'
+ # entries.
+ options[:dryrun] = 'damp'
+end
+opts.on('--list-files', 'Just list the files that would be configured') do |opt|
+ options[:listfiles] = opt
+ # generate all is implied
+ @generateall = true
+ # Set :dryrun as a extra measure to make sure we don't change anything
+ options[:dryrun] = 'listfiles'
+end
+opts.on('--interactive', 'Prompt for confirmation before each change.') do |opt|
+ options[:interactive] = opt
+end
+opts.on('--full-file', 'Display full new file contents instead of a diff.') do |opt|
+ options[:fullfile] = opt
+end
+opts.on('--filename-only', 'Display filename of changed files instead of a diff.') do |opt|
+ options[:filenameonly] = opt
+end
+opts.on('--disable-force', 'Ignore the disable_etch file. Use with caution.') do |opt|
+ options[:disableforce] = opt
+end
+opts.on('--lock-force', 'Force the removal of any existing lockfiles.') do |opt|
+ options[:lockforce] = opt
+end
+opts.on('--local DIR', 'Read configuration from local directory, not server.') do |opt|
+ options[:local] = opt
+end
+opts.on('--server SERVER', 'Point etch to an alternate server.') do |opt|
+ options[:server] = opt
+end
+opts.on('--tag TAG', 'Request a specific repository tag from the server.') do |opt|
+ options[:tag] = opt
+end
+opts.on('--key PRIVATE_KEY', 'Use this private key for signing messages to server.') do |opt|
+ options[:key] = opt
+end
+opts.on('--test-root TESTDIR', 'For use by the test suite only.') do |opt|
+ options[:file_system_root] = opt
+end
+opts.on('--debug', 'Print lots of messages about what etch is doing.') do |opt|
+ options[:debug] = opt
+end
+opts.on('--version', 'Show etch client version.') do |opt|
+ puts Etch::Client::VERSION
+ exit
+end
+opts.on_tail('-h', '--help', 'Show this message.') do
+ puts opts
+ exit
+end
+
+leftovers = opts.parse(ARGV)
+files = []
+commands = []
+leftovers.each do |leftover|
+ if leftover[0,1] == File::SEPARATOR
+ files << leftover
+ else
+ commands << leftover
+ end
+end
+
+# Display a usage message if the user did not specify a valid action to perform.
+if files.empty? && commands.empty? && !@generateall
+ puts opts
+ exit
+end
+
+#
+# Do stuff
+#
+
+etchclient = Etch::Client.new(options)
+status = etchclient.process_until_done(files, commands)
+exit status
+
Copied: trunk/client/bin/etch_cron_wrapper (from rev 272, trunk/client/etch_cron_wrapper)
===================================================================
--- trunk/client/bin/etch_cron_wrapper (rev 0)
+++ trunk/client/bin/etch_cron_wrapper 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,18 @@
+#!/usr/bin/ruby -w
+
+require 'socket'
+require 'digest/sha1'
+
+# Seed the random number generator with the hostname of this box so that
+# we get a consistent random number. We want to run the registration at a
+# consistent time on each individual box, but randomize the runs across
+# the environment.
+srand(Digest::SHA1.hexdigest(Socket.gethostname)[0,7].hex)
+
+# Cron job is set to run every hour
+MAX_SLEEP = 60 * 60
+
+sleep(rand(MAX_SLEEP))
+
+exec('/usr/sbin/etch', '--generate-all')
+
Copied: trunk/client/bin/etch_to_trunk (from rev 272, trunk/client/etch_to_trunk)
===================================================================
--- trunk/client/bin/etch_to_trunk (rev 0)
+++ trunk/client/bin/etch_to_trunk 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,77 @@
+#!/usr/bin/ruby -w
+
+require 'nventory'
+require 'time'
+require 'optparse'
+
+ETCH_SERVER_TZ = 'UTC' # The etch servers run in UTC
+
+options = {}
+opts = OptionParser.new
+opts.banner = 'Usage: etch_to_trunk [options] <server1> [<server2> <server3>]'
+opts.on('-u', '--username USERNAME', 'Username for connecting to nventory server.') do |opt|
+ options[:username] = opt
+end
+opts.on('-t', '--timezone TIMEZONE', 'Time zone of etch server.') do |opt|
+ options[:timezone] = opt
+end
+opts.on('--nv SERVER', 'Where nVentory server is running.') do |opt|
+ options[:nv_server] = opt
+end
+opts.on_tail('-h', '--help', 'Show this message.') do
+ puts opts
+ exit
+end
+
+nodes = opts.parse(ARGV)
+
+if ARGV.length == 0
+ puts opts
+ exit
+end
+
+@username = options[:username] || ENV['LOGNAME']
+etch_server_tz = options[:timezone] || ETCH_SERVER_TZ
+
+if etch_server_tz
+ currentheadtag = Time.at(Time.now.utc + Time.zone_offset(etch_server_tz)).strftime('trunk-%Y%m%d-%H00')
+else # if no timezone is specified then just use local time for the tag
+ currentheadtag = Time.now.strftime('trunk-%Y%m%d-%H00')
+end
+
+# Find the requested clients
+nv_server = options[:nv_server]
+if nv_server
+ nvclient = NVentory::Client.new(:server=>"http://#{nv_server}")
+else
+ nvclient = NVentory::Client.new
+end
+results = nvclient.get_objects('nodes', {}, { 'name' => nodes }, {}, {})
+nodes.each do |name|
+ if results.empty? && results[name].nil?
+ abort "No entry found for #{name}"
+ else
+ if !results[name]['config_mgmt_tag'].nil? &&
+ !results[name]['config_mgmt_tag'].empty?
+ puts "Changing #{name} from #{results[name]['config_mgmt_tag']} to #{currentheadtag}"
+ else
+ puts "Setting #{name} to #{currentheadtag}"
+ end
+ end
+end
+
+while true
+ print "Proceed? [y|n] "
+ response = $stdin.gets.chomp
+ if response == 'y'
+ break
+ elsif response == 'n'
+ exit
+ end
+end
+
+# Update the clients
+successcount = nvclient.set_objects('nodes', results, {'config_mgmt_tag' => currentheadtag}, @username)
+
+puts "#{File.basename($0)} operation succeeded for #{successcount} of #{results.size} nodes"
+
Deleted: trunk/client/ca.pem
===================================================================
--- trunk/client/ca.pem 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/ca.pem 2011-05-04 17:43:47 UTC (rev 278)
@@ -1 +0,0 @@
-# Add your SSL certificate authority's cert(s) to this file
Deleted: trunk/client/control
===================================================================
--- trunk/client/control 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/control 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,7 +0,0 @@
-Package: etch-client
-Version: %VER%-1
-Maintainer: etch-users@...
-Architecture: all
-Depends: ruby, libopenssl-ruby1.8, facter, lsb-release, lsb-base
-Description: Etch client
-
Deleted: trunk/client/depend
===================================================================
--- trunk/client/depend 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/depend 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,3 +0,0 @@
-P CSWruby
-P CSWfacter
-
Deleted: trunk/client/dhparams
===================================================================
--- trunk/client/dhparams 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/dhparams 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,9 +0,0 @@
------BEGIN DH PARAMETERS-----
-MIIBCAKCAQEA3HySq1WdL67BCSRCJCZYMUIojAWAsvK63D3cOGk0wI9UeM/yeVhz
-jTswvHOVPZFKIBg1Aeo2eAEdPryDnmjVTgvLbuWkCPouQhBCVsQ1El9ZcXPix1rC
-tYsg4Kll1jgnwFoHf4xvjPnD/SqsASAiDxYlh4CFVyT1gLgSiUU0rIdudgO3agI5
-NgiyGOKwyHmNOOQSKA62M/JnoxcBDC7Nou3lqtHpR5yWsUz+csyk+hXZeUba97bm
-M8OB0PmfK4Vo6JpdO+yc8hjeYBoMsH7g/l3Gm1JqUxxctcY/OuJ+2nkXwsD66E3D
-yZCoiVd3u4OqAxNO/GG0iUmskjIvokMhUwIBAg==
------END DH PARAMETERS-----
-
Copied: trunk/client/etc/cron.d/etch (from rev 272, trunk/client/etch_cron)
===================================================================
--- trunk/client/etc/cron.d/etch (rev 0)
+++ trunk/client/etc/cron.d/etch 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,10 @@
+# Run the etch client every hour
+# If you want to disable etch updates do NOT comment out this cron job.
+# Our next client update will just put it back anyway.
+# Instead create /var/etch/disable_etch and add a comment as to
+# why this box shouldn't be getting updates. Etch will notice
+# that file and abort. disable_etch files with no comment will be
+# removed by the System Administration team without warning.
+MAILTO=""
+0 * * * * root /usr/sbin/etch_cron_wrapper
+
Copied: trunk/client/etc/etch/ca.pem (from rev 272, trunk/client/ca.pem)
===================================================================
--- trunk/client/etc/etch/ca.pem (rev 0)
+++ trunk/client/etc/etch/ca.pem 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1 @@
+# Add your SSL certificate authority's cert(s) to this file
Copied: trunk/client/etc/etch/dhparams (from rev 272, trunk/client/dhparams)
===================================================================
--- trunk/client/etc/etch/dhparams (rev 0)
+++ trunk/client/etc/etch/dhparams 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,9 @@
+-----BEGIN DH PARAMETERS-----
+MIIBCAKCAQEA3HySq1WdL67BCSRCJCZYMUIojAWAsvK63D3cOGk0wI9UeM/yeVhz
+jTswvHOVPZFKIBg1Aeo2eAEdPryDnmjVTgvLbuWkCPouQhBCVsQ1El9ZcXPix1rC
+tYsg4Kll1jgnwFoHf4xvjPnD/SqsASAiDxYlh4CFVyT1gLgSiUU0rIdudgO3agI5
+NgiyGOKwyHmNOOQSKA62M/JnoxcBDC7Nou3lqtHpR5yWsUz+csyk+hXZeUba97bm
+M8OB0PmfK4Vo6JpdO+yc8hjeYBoMsH7g/l3Gm1JqUxxctcY/OuJ+2nkXwsD66E3D
+yZCoiVd3u4OqAxNO/GG0iUmskjIvokMhUwIBAg==
+-----END DH PARAMETERS-----
+
Copied: trunk/client/etc/etch.conf (from rev 272, trunk/client/etch.conf)
===================================================================
--- trunk/client/etc/etch.conf (rev 0)
+++ trunk/client/etc/etch.conf 2011-05-04 17:43:47 UTC (rev 278)
@@ -0,0 +1,25 @@
+# The default server url is https://etch
+#server = https://etch
+
+# Etch is usually run from cron, and cron usually executes jobs with a very
+# minimal PATH environment variable setting. You may want etch to have a more
+# complete PATH setting so that pre and post commands are more readily found.
+#path = /bin:/usr/bin:/sbin:/usr/sbin
+
+# Etch can read configuration from a local directory rather than from a
+# server. If set this will override the server setting.
+#local = /var/etch/configs
+
+# Etch will try to find an SSH host key and use it to sign all requests to the
+# etch server. Several standard locations for host keys are searched by
+# default. If your host key is not in a standard location then you can point
+# etch to it.
+# See http://sourceforge.net/apps/trac/etch/wiki/ClientAuthentication
+#key = /etc/ssh/ssh_host_rsa_key
+
+# Etch can send detailed results back to the server for viewing in the web UI
+# and/or to a local log file. They are sent to the server by default.
+# Note that SERVER is ignored when etch is running in local mode.
+#detailed_results = SERVER
+#detailed_results = /var/etch/results.log
+
Deleted: trunk/client/etch
===================================================================
--- trunk/client/etch 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,111 +0,0 @@
-#!/usr/bin/ruby -w
-##############################################################################
-# Etch configuration file management tool
-##############################################################################
-
-# Ensure we can find etchclient.rb
-$:.unshift(File.dirname(__FILE__))
-
-require 'optparse'
-require 'etchclient'
-
-#
-# Parse the command line options
-#
-
-options = {}
-@... = nil
-
-# Extra options to OptionParser reduce the amount of whitespace it introduces
-# into the help message, making it easier to make the help message fit in a
-# 80x24 window.
-opts = OptionParser.new(nil, 24, ' ')
-opts.banner = 'Usage: etch [options] [/path/to/config/file | command] [otherfile ...]'
-opts.on('--generate-all', 'Request all configuration.') do |opt|
- @generateall = opt
-end
-opts.on('--dry-run', '-n', 'Make no changes.') do |opt|
- options[:dryrun] = opt
-end
-opts.on('--damp-run', "Perform a dry run but run 'setup' entries for files.") do |opt|
- # Rather than sprinkle checks of two different variables throught the code, if
- # we're asked to do a damp run then just set the dry run flag to a unique
- # value. Then we can just check for that specific value at the one place where
- # the two modes differ: enabling or disabling the execution of 'setup'
- # entries.
- options[:dryrun] = 'damp'
-end
-opts.on('--list-files', 'Just list the files that would be configured') do |opt|
- options[:listfiles] = opt
- # generate all is implied
- @generateall = true
- # Set :dryrun as a extra measure to make sure we don't change anything
- options[:dryrun] = 'listfiles'
-end
-opts.on('--interactive', 'Prompt for confirmation before each change.') do |opt|
- options[:interactive] = opt
-end
-opts.on('--full-file', 'Display full new file contents instead of a diff.') do |opt|
- options[:fullfile] = opt
-end
-opts.on('--filename-only', 'Display filename of changed files instead of a diff.') do |opt|
- options[:filenameonly] = opt
-end
-opts.on('--disable-force', 'Ignore the disable_etch file. Use with caution.') do |opt|
- options[:disableforce] = opt
-end
-opts.on('--lock-force', 'Force the removal of any existing lockfiles.') do |opt|
- options[:lockforce] = opt
-end
-opts.on('--local DIR', 'Read configuration from local directory, not server.') do |opt|
- options[:local] = opt
-end
-opts.on('--server SERVER', 'Point etch to an alternate server.') do |opt|
- options[:server] = opt
-end
-opts.on('--tag TAG', 'Request a specific repository tag from the server.') do |opt|
- options[:tag] = opt
-end
-opts.on('--key PRIVATE_KEY', 'Use this private key for signing messages to server.') do |opt|
- options[:key] = opt
-end
-opts.on('--test-root TESTDIR', 'For use by the test suite only.') do |opt|
- options[:file_system_root] = opt
-end
-opts.on('--debug', 'Print lots of messages about what etch is doing.') do |opt|
- options[:debug] = opt
-end
-opts.on('--version', 'Show etch client version.') do |opt|
- puts Etch::Client::VERSION
- exit
-end
-opts.on_tail('-h', '--help', 'Show this message.') do
- puts opts
- exit
-end
-
-leftovers = opts.parse(ARGV)
-files = []
-commands = []
-leftovers.each do |leftover|
- if leftover[0,1] == File::SEPARATOR
- files << leftover
- else
- commands << leftover
- end
-end
-
-# Display a usage message if the user did not specify a valid action to perform.
-if files.empty? && commands.empty? && !@generateall
- puts opts
- exit
-end
-
-#
-# Do stuff
-#
-
-etchclient = Etch::Client.new(options)
-status = etchclient.process_until_done(files, commands)
-exit status
-
Deleted: trunk/client/etch-client.spec
===================================================================
--- trunk/client/etch-client.spec 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch-client.spec 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,25 +0,0 @@
-Name: etch-client
-Summary: Etch client
-Version: %VER%
-Release: 1
-Group: Applications/System
-License: MIT
-buildarch: noarch
-Requires: ruby, facter, crontabs
-BuildRoot: %{_builddir}/%{name}-buildroot
-%description
-Etch client
-
-%files
-%defattr(-,root,root)
-/usr/sbin/etch
-/usr/sbin/etch_to_trunk
-/usr/lib/ruby/site_ruby/1.8/etchclient.rb
-/usr/lib/ruby/site_ruby/1.8/etch.rb
-/usr/lib/ruby/site_ruby/1.8/versiontype.rb
-/usr/share/man/man8/etch.8
-/etc/etch.conf
-/etc/etch
-/usr/sbin/etch_cron_wrapper
-/etc/cron.d/etch
-
Deleted: trunk/client/etch.8
===================================================================
--- trunk/client/etch.8 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch.8 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,229 +0,0 @@
-.TH etch 8 "October 2009"
-
-.SH NAME
-
-.B etch
-\- Configuration management for Unix systems
-
-.SH SYNOPSIS
-
-.B etch
-.RB [ --generate-all ]
-.RB [ --dry-run | \-n ]
-.RB [ --damp-run ]
-.RB [ --interactive ]
-.RB [ --full-file ]
-.RB [ --filename-only ]
-.RB [ --disable-force ]
-.RB [ --lock-force ]
-.RB [ --debug ]
-.RB [ --local
-.IR DIR ]
-.RB [ --server
-.IR SERVER ]
-.RB [ --tag
-.IR TAG ]
-.RB [ --test-base
-.IR TESTDIR ]
-.RB [ --key
-.IR PRIVATE_KEY ]
-.RB [ --version ]
-.RB [ --help | \-h ]
-.RI [ "file ..." ]
-
-.SH DESCRIPTION
-
-Etch is a tool for managing the configuration of Unix systems. Etch can manage
-text or binary files, links and directories. The contents of files can be
-supplied from static files or generated on the fly by scripts or templates.
-Permissions and ownership as well as any pre or post commands to run when
-updating the file are configured in simple XML files.
-
-.SH OPTIONS
-.TP
-.B --generate-all
-Request that the server generate and send over all available configuration.
-Can be used instead of specifying specific files to request.
-.TP
-.B --dry-run | \-n
-.B etch
-will not make any changes to the system. Contents of generated config
-files are displayed instead of being written to disk.
-.TP
-.B --damp-run
-Perform an almost dry run, except that 'setup' entries for files are run.
-Normally all setup/pre/post entries are ignored for a dry run. However, files
-with setup entries will generally fail to build properly if the setup entry
-hasn't been run. This allows you to preview the full and correct
-configuration while making minimal changes to the system.
-.TP
-.B --interactive
-Causes
-.B etch
-to pause before making each change and prompt the user for
-confirmation.
-.TP
-.B --full-file
-Normally
-.B etch
-will print a diff to show what changes it will make to a file. This will cause
-.B etch
-to display the full new file contents instead. Useful if the diff is hard to
-read for a particular file.
-.TP
-.B --filename-only
-Normally
-.B etch
-will print a diff to show what changes it will make to a file. This will cause
-etch to display only the name of file to be changed. Useful on the initial
-run during your system build process or other times when you want less output.
-.TP
-.B --disable-force
-Ignore the disable_etch file. Use with caution. Typically used with
---interactive or --dry-run to evaluate what would happen if etch were
-re-enabled.
-.TP
-.B --lock-force
-.B Etch
-does per-file internal locking as it runs to prevent problems with
-simultaneous instances of the
-.B etch
-client stepping on each other.
-.B Etch
-automatically cleans up lockfiles over two hours old on the assumption that
-any lockfiles that old were left behind by a previous instance that failed for
-some (hopefully transient) reason. In a large environment you don't want to
-have to run around manually cleaning up lockfiles every time a full disk or
-unavailable package server or other transient failure causes
-.B etch
-to fail
-temporarily. This option forces the removal of any existing lockfiles no
-matter how old. Useful if a buggy configuration or local environmental issue
-caused
-.B etch
-to abort and you want to immediately retry.
-.TP
-.BI --local " DIR"
-Read configuration from a local directory rather than requesting it from
-a server.
-.TP
-.BI --server " SERVER"
-Point
-.B etch
-to an alternate server, specified in the form of a URL.
-.TP
-.BI --tag " TAG"
-Request a specific repository tag from the server. Tags are directory paths
-relative to /etc/etchserver by default. Thus examples might be
-.I trunk
-for /etc/etchserver/trunk, or
-.I branches/mytestbranch
-for /etc/etchserver/branches/mytestbranch.
-.TP
-.BI --key " PRIVATE_KEY"
-Use an alternate SSH private key file for signing messages that are sent to
-the server.
-.TP
-.BI --test-base " TESTDIR"
-Use an alternate local working directory instead of /var/etch. Generally only
-used for allowing the etch test suite to be run as a non-root user.
-.TP
-.B --debug
-Print lots of messages about what etch is doing.
-.TP
-.B --version
-Show the etch client version and exit.
-.TP
-.B --help | \-h
-Display the etch usage message and exit.
-
-.SH FILES
-
-.TP
-.B /etc/etch.conf
-Optional configuration file for the etch client. The distribution ships with
-an example file showing the available settings and their defaults. The file
-syntax consists of "name = value" parameters. Lines starting with '#' and
-empty lines are interpreted as comments.
-.IP
-.B server =
-The URL for connecting to the server. http:// and https:// are supported.
-.IP
-.B path =
-PATH environment etch should use when executing external commands.
-.IP
-.B local =
-Etch can read configuration from a local directory rather than from a server.
-If set this will override the server setting.
-.IP
-.B key =
-SSH host key to use for authentication rather than standard system key.
-.IP
-.B detailed_results =
-Etch can send detailed results back to the server for viewing in the web UI
-and/or to a local log file. Value should be SERVER or the full path to a log
-file. Setting may be specified multiple times to send logs to multiple
-destinations.
-.TP
-.B /etc/etch/ca.pem
-SSL certificate(s) needed to verify the
-.B etch
-server's identity. If
-.B etch
-is using a server with an https:// URL and if this file exists then
-.B etch
-will not proceed if the server's SSL certificate can't be verified against the
-certs in this file.
-.TP
-.B /etc/etch/dhparams
-The Diffie-Hellman parameters used as part of the SSL connection process. Etch
-comes with a set and there's no need to generate your own, but a new set can
-be generated via "openssl dhparam" if desired. If this file is not present the
-Ruby SSL library will warn that it is using its internal default set of
-parameters.
-.TP
-.B /var/etch/disable_etch
-If this file is present
-.B etch
-will do nothing other than send a message to the
-.B etch
-server that it is disabled. Any text in this file will be included in
-that message to the server and displayed locally. If you are disabling
-.B etch
-always put a note in this file indicating who you are, today's date, and why
-you are disabling
-.B etch
-so that fellow administrators won't have to guess later why
-.B etch
-was disabled. Re-enabling
-.B etch
-after it has been disabled for a long
-time can be time-consuming, as the person doing so must review any potential
-changes that have been queued up and ensure that they won't interfere with the
-current usage of the system. As such be thoughtful about whether disabling
-.B etch
-is necessary, and re-enable it as soon as possible.
-.TP
-.B /var/etch/history
-A revision history of each
-.B etch
-managed file, stored using the RCS revision control system.
-.TP
-.B /var/etch/locks
-Directory used by the
-.B etch
-internal locking mechanism. See the --lock-force option for more details.
-.TP
-.B /var/etch/orig
-A backup of the original contents of each
-.B etch
-managed file as it was before etch first touched it.
-
-.SH DIAGNOSTICS
-
-See the --debug option and the server logs.
-
-.SH AUTHOR
-
-.B Etch
-is designed and maintained by Jason Heiss
Deleted: trunk/client/etch.conf
===================================================================
--- trunk/client/etch.conf 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch.conf 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,25 +0,0 @@
-# The default server url is https://etch
-#server = https://etch
-
-# Etch is usually run from cron, and cron usually executes jobs with a very
-# minimal PATH environment variable setting. You may want etch to have a more
-# complete PATH setting so that pre and post commands are more readily found.
-#path = /bin:/usr/bin:/sbin:/usr/sbin
-
-# Etch can read configuration from a local directory rather than from a
-# server. If set this will override the server setting.
-#local = /var/etch/configs
-
-# Etch will try to find an SSH host key and use it to sign all requests to the
-# etch server. Several standard locations for host keys are searched by
-# default. If your host key is not in a standard location then you can point
-# etch to it.
-# See http://sourceforge.net/apps/trac/etch/wiki/ClientAuthentication
-#key = /etc/ssh/ssh_host_rsa_key
-
-# Etch can send detailed results back to the server for viewing in the web UI
-# and/or to a local log file. They are sent to the server by default.
-# Note that SERVER is ignored when etch is running in local mode.
-#detailed_results = SERVER
-#detailed_results = /var/etch/results.log
-
Deleted: trunk/client/etch.spec
===================================================================
--- trunk/client/etch.spec 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch.spec 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,44 +0,0 @@
-Name: etch
-Version: %VER%
-Release: 1%{?dist}
-BuildArch: noarch
-Summary: A tool for system configuration management
-Group: Applications/System
-License: MIT
-URL: http://etch.sourceforge.net/
-Source0: http://downloads.sourceforge.net/project/etch/etch/%{version}/etch-%{version}.tar.gz
-BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-
-BuildRequires: rubygem-rake
-Requires: ruby(abi) = 1.8, facter
-
-# Per http://fedoraproject.org/wiki/Packaging:Ruby
-%{!?ruby_sitelib: %global ruby_sitelib %(ruby -rrbconfig -e 'puts Config::CONFIG["sitelibdir"] ')}
-
-%description
-A tool for system configuration management, i.e. management of the
-configuration files of the operating system and core applications. Easy for a
-professional system administrator to start using, yet scalable to large
-and/or complex environments.
-
-%prep
-%setup -q
-
-%build
-
-%install
-rm -rf %{buildroot}
-cd client && rake install[%{buildroot}]
-
-%clean
-rm -rf %{buildroot}
-
-%files
-%defattr(-,root,root,-)
-%{_sbindir}/*
-%{ruby_sitelib}/*
-%{_mandir}/man8/*
-%config(noreplace) %{_sysconfdir}/*
-
-%changelog
-
Deleted: trunk/client/etch_cron
===================================================================
--- trunk/client/etch_cron 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch_cron 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,10 +0,0 @@
-# Run the etch client every hour
-# If you want to disable etch updates do NOT comment out this cron job.
-# Our next client update will just put it back anyway.
-# Instead create /var/etch/disable_etch and add a comment as to
-# why this box shouldn't be getting updates. Etch will notice
-# that file and abort. disable_etch files with no comment will be
-# removed by the System Administration team without warning.
-MAILTO=""
-0 * * * * root /usr/sbin/etch_cron_wrapper
-
Deleted: trunk/client/etch_cron_wrapper
===================================================================
--- trunk/client/etch_cron_wrapper 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch_cron_wrapper 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,18 +0,0 @@
-#!/usr/bin/ruby -w
-
-require 'socket'
-require 'digest/sha1'
-
-# Seed the random number generator with the hostname of this box so that
-# we get a consistent random number. We want to run the registration at a
-# consistent time on each individual box, but randomize the runs across
-# the environment.
-srand(Digest::SHA1.hexdigest(Socket.gethostname)[0,7].hex)
-
-# Cron job is set to run every hour
-MAX_SLEEP = 60 * 60
-
-sleep(rand(MAX_SLEEP))
-
-exec('/usr/sbin/etch', '--generate-all')
-
Deleted: trunk/client/etch_to_trunk
===================================================================
--- trunk/client/etch_to_trunk 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etch_to_trunk 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,77 +0,0 @@
-#!/usr/bin/ruby -w
-
-require 'nventory'
-require 'time'
-require 'optparse'
-
-ETCH_SERVER_TZ = 'UTC' # The etch servers run in UTC
-
-options = {}
-opts = OptionParser.new
-opts.banner = 'Usage: etch_to_trunk [options] <server1> [<server2> <server3>]'
-opts.on('-u', '--username USERNAME', 'Username for connecting to nventory server.') do |opt|
- options[:username] = opt
-end
-opts.on('-t', '--timezone TIMEZONE', 'Time zone of etch server.') do |opt|
- options[:timezone] = opt
-end
-opts.on('--nv SERVER', 'Where nVentory server is running.') do |opt|
- options[:nv_server] = opt
-end
-opts.on_tail('-h', '--help', 'Show this message.') do
- puts opts
- exit
-end
-
-nodes = opts.parse(ARGV)
-
-if ARGV.length == 0
- puts opts
- exit
-end
-
-@... = options[:username] || ENV['LOGNAME']
-etch_server_tz = options[:timezone] || ETCH_SERVER_TZ
-
-if etch_server_tz
- currentheadtag = Time.at(Time.now.utc + Time.zone_offset(etch_server_tz)).strftime('trunk-%Y%m%d-%H00')
-else # if no timezone is specified then just use local time for the tag
- currentheadtag = Time.now.strftime('trunk-%Y%m%d-%H00')
-end
-
-# Find the requested clients
-nv_server = options[:nv_server]
-if nv_server
- nvclient = NVentory::Client.new(:server=>"http://#{nv_server}")
-else
- nvclient = NVentory::Client.new
-end
-results = nvclient.get_objects('nodes', {}, { 'name' => nodes }, {}, {})
-nodes.each do |name|
- if results.empty? && results[name].nil?
- abort "No entry found for #{name}"
- else
- if !results[name]['config_mgmt_tag'].nil? &&
- !results[name]['config_mgmt_tag'].empty?
- puts "Changing #{name} from #{results[name]['config_mgmt_tag']} to #{currentheadtag}"
- else
- puts "Setting #{name} to #{currentheadtag}"
- end
- end
-end
-
-while true
- print "Proceed? [y|n] "
- response = $stdin.gets.chomp
- if response == 'y'
- break
- elsif response == 'n'
- exit
- end
-end
-
-# Update the clients
-successcount = nvclient.set_objects('nodes', results, {'config_mgmt_tag' => currentheadtag}, @username)
-
-puts "#{File.basename($0)} operation succeeded for #{successcount} of #{results.size} nodes"
-
Deleted: trunk/client/etchclient.rb
===================================================================
--- trunk/client/etchclient.rb 2011-05-04 14:34:24 UTC (rev 277)
+++ trunk/client/etchclient.rb 2011-05-04 17:43:47 UTC (rev 278)
@@ -1,2565 +0,0 @@
-##############################################################################
-# Etch configuration file management tool library
-##############################################################################
-
-# Ensure we can find etch.rb if run within the development directory structure
-# This is roughly equivalent to "../server/lib"
-serverlibdir = File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'server', 'lib')
-if File.exist?(serverlibdir)
- $:.unshift(serverlibdir)
-end
-
-begin
- # Try loading facter w/o gems first so that we don't introduce a
- # dependency on gems if it is not needed.
- require 'facter' # Facter
-rescue LoadError
- require 'rubygems'
- require 'facter'
-end
-require 'find'
-require 'digest/sha1' # hexdigest
-require 'openssl' # OpenSSL
-require 'base64' # decode64, encode64
-require 'uri'
-require 'net/http'
-require 'net/https'
-require 'rexml/document'
-require 'fileutils' # copy, mkpath, rmtree
-require 'fcntl' # Fcntl::O_*
-require 'etc' # getpwnam, getgrnam
-require 'tempfile' # Tempfile
-require 'find' # Find.find
-require 'cgi'
-require 'timeout'
-require 'logger'
-require 'etch'
-
-class Etch::Client
- VERSION = 'unset'
-
- CONFIRM_PROCEED = 1
- CONFIRM_SKIP = 2
- CONFIRM_QUIT = 3
- PRIVATE_KEY_PATHS = ["/etc/ssh/ssh_host_rsa_key", "/etc/ssh_host_rsa_key"]
- DEFAULT_CONFIGDIR = '/etc'
- DEFAULT_VARBASE = '/var/etch'
- DEFAULT_DETAILED_RESULTS = ['SERVER']
-
- # We need these in relation to the output capturing
- ORIG_STDOUT = STDOUT.dup
- ORIG_STDERR = STDERR.dup
-
- attr_reader :exec_once_per_run
-
- def initialize(options)
- @server = options[:server] ? options[:server] : 'https://etch'
- @tag = options[:tag]
- @local = options[:local] ? File.expand_path(options[:local]) : nil
- @debug = options[:debug]
- @dryrun = options[:dryrun]
- @listfiles = options[:listfiles]
- @interactive = options[:interactive]
- @filenameonly = options[:filenameonly]
- @fullfile = options[:fullfile]
- @key = options[:key] ? options[:key] : get_private_key_path
- @disableforce = options[:disableforce]
- @lockforce = options[:lockforce]
-
- @last_response = ""
-
- @configdir = DEFAULT_CONFIGDIR
- @varbase = DEFAULT_VARBASE
-
- @file_system_root = '/' # Not sure if this needs to be more portable
- # This option is only intended for use by the test suite
- if options[:file_system_root]
- @file_system_root = options[:file_system_root]
- @varbase = File.join(@file_system_root, @varbase)
- @configdir = File.join(@file_system_root, @configdir)
- end
-
- @configfile = File.join(@configdir, 'etch.conf')
- @detailed_results = []
-
- if File.exist?(@configfile)
- IO.foreach(@configfile) do |line|
- line.chomp!
- next if (line =~ /^\s*$/); # Skip blank lines
- next if (line =~ /^\s*#/); # Skip comments
- line.strip! # Remove leading/trailing whitespace
- key, value = line.split(/\s*=\s*/, 2)
- if key == 'server'
- # A setting for the server to use which comes from upstream
- # (generally from a command line option) takes precedence
- # over the config file
- if !options[:server]
- @server = value
- # Warn the user, as this could potentially be confusing
- # if they don't realize there's a config file lying
- # around
- warn "Using server #{@server} from #{@configfile}" if @debug
- else
- # "command line override" isn't necessarily accurate, we don't
- # know why the caller passed us an option to override the config
- # file, but most of the time it will be due to a command line
- # option and I want the message to be easily understood by users.
- # If someone can come up with some better wording without turning
- # the message into something as long as this comment that would be
- # welcome.
- warn "Ignoring 'server' option in #{@configfile} due to command line override" if @debug
- end
- elsif key == 'local'
- if !options[:local] && !options[:server]
- @local = value
- warn "Using local directory #{@local} from #{@configfile}" if @debug
- else
- warn "Ignoring 'local' option in #{@configfile} due to command line override" if @debug
- end
- elsif key == 'key'
- if !options[:key]
- @key = value
- warn "Using key #{@key} from #{@configfile}" if @debug
- else
- warn "Ignoring 'key' option in #{@configfile} due to command line override" if @debug
- end
- elsif key == 'path'
- ENV['PATH'] = value
- elsif key == 'detailed_results'
- warn "Adding detailed results destination '#{value}'" if @debug
- @detailed_results << value
- end
- end
- end
-
- if @key && !File.readable?(@key)
- @key = nil
- end
- if !@key
- warn "No readable private key found, messages to server will not be signed and may be rejected depending on server configuration"
- end
-
- if @detailed_results.empty?
- @detailed_results = DEFAULT_DETAILED_RESULTS
- end
-
- @origbase = File.join(@varbase, 'orig')
- @historybase = File.join(@varbase, 'history')
- @lockbase = File.join(@varbase, 'locks')
- @requestbase = File.join(@varbase, 'requests')
-
- @facts = Facter.to_hash
- if @facts['operatingsystemrelease']
- # Some versions of Facter have a bug that leaves extraneous
- # whitespace on this fact. Work around that with strip. I.e. on
- # CentOS you'll get '5 ' or '5.2 '.
- @facts['operatingsystemrelease'].strip!
- end
-
- if @local
- logger = Logger.new(STDOUT)
- dlogger = Logger.new(STDOUT)
- if @debug
- dlogger.level = Logger::DEBUG
- else
- dlogger.level = Logger::INFO
- end
- @etch = Etch.new(logger, dlogger)
- else
- # Make sure the server URL ends in a / so that we can append paths
- # to it using URI.join
- if @server !~ %r{/$}
- @server << '/'
- end
- @filesuri = URI.join(@server, 'files')
- @resultsuri = URI.join(@server, 'results')
-
- @blankrequest = {}
- # If the user specified a non-standard key then override the
- # sshrsakey fact so that authentication works
- if @key
- @facts['sshrsakey'] = IO.read(@key+'.pub').chomp.split[1]
- end
- @facts.each_pair { |key, value| @blankrequest["facts[#{key}]"] = value.to_s }
- @blankrequest['fqdn'] = @facts['fqdn']
- if @debug
- @blankrequest['debug'] = '1'
- end
- if @tag
- @blankrequest['tag'] = @tag
- end
- end
-
- @locked_files = {}
- @first_update = {}
- @already_processed = {}
- @exec_already_processed = {}
- @exec_once_per_run = {}
- @results = []
- # See start/stop_output_capture for these
- @output_pipes = []
-
- @lchown_supported = nil
- @lchmod_supported = nil
- end
-
- def process_until_done(files, commands)
- # Our overall status. Will be reported to the server and used as the
- # return value for this method. Command-line clients should use it as
- # their exit value. Zero indicates no errors.
- status = 0
- message = ''
-
- # A variable to collect filenames if operating in @listfiles mode
- files_to_list = {}
-
- # Prep http instance
- http = nil
- if !@local
- http = Net::HTTP.new(@filesuri.host, @filesuri.port)
- if @filesuri.scheme == "https"
- # Eliminate the OpenSSL "using default DH parameters" warning
- if File.exist?(File.join(@configdir, 'etch', 'dhparams'))
- dh = OpenSSL::PKey::DH.new(IO.read(File.join(@configdir, 'etch', 'dhparams')))
- Net::HTTP.ssl_context_accessor(:tmp_dh_callback)
- http.tmp_dh_callback = proc { dh }
- end
- http.use_ssl = true
- if File.exist?(File.join(@configdir, 'etch', 'ca.pem'))
- http.ca_file = File.join(@configdir, 'etch', 'ca.pem')
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
- elsif File.directory?(File.join(@configdir, 'etch', 'ca'))
- http.ca_path = File.join(@configdir, 'etch', 'ca')
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
- end
- end
- http.start
- end
-
- # catch/throw for expected/non-error events that end processing
- # begin/raise for error events that end processing
- catch :stop_processing do
- begin
- enabled, message = check_for_disable_etch_file
- if !enabled
- # 200 is the arbitrarily picked exit value indicating
- # that etch is disabled
- status = 200
- throw :stop_processing
- end
- remove_stale_lock_files
-
- # Assemble the initial request
- request = nil
- if @local
- request = {}
- if files && !files.empty?
- request[:files] = {}
- files.each do |file|
- request[:files][file] = {:orig => save_orig(file)}
- local_requests = get_local_requests(file)
- if local_requests
- request[:files][file][:local_requests] = local_requests
- end
- end
- end
- if commands && !commands.empty?
- request[:commands] = {}
- commands.each do |command|
- request[:commands][command] = {}
- end
- end
- else
- request = get_blank_request
- if (files && !files.empty?) || (commands && !commands.empty?)
- if files
- files.each do |file|
- request["files[#{CGI.escape(file)}][sha1sum]"] =
- get_orig_sum(file)
- local_requests = get_local_requests(file)
- if local_requests
- request["files[#{CGI.escape(file)}][local_requests]"] =
- local_requests
- end
- end
- end
- if commands
- commands.each do |command|
- request["commands[#{CGI.escape(command)}]"] = '1'
- end
- end
- else
- request['files[GENERATEALL]'] = '1'
- end
- end
-
- #
- # Loop back and forth with the server sending requests for files and
- # responding to the server's requests for original contents or sums
- # it needs
- #
-
- Signal.trap('EXIT') do
- STDOUT.reopen(ORIG_STDOUT)
- STDERR.reopen(ORIG_STDERR)
- unlock_all_files
- end
-
- # It usually takes a few back and forth exchanges with the server to
- # exchange all needed data and get a complete set of configuration.
- # The number of iterations is capped at 10 to prevent any unplanned
- # infinite loops. The limit of 10 was chosen somewhat arbitrarily but
- # seems fine in practice.
- 10.times do
- #
- # Send request to server
- #
-
- responsedata = {}
- if @local
- results = @etch.generate(@local, @facts, request)
- # FIXME: Etch#generate returns parsed XML using whatever XML
- # library it happens to use. In order to avoid re-parsing
- # the XML we'd have to use the XML abstraction code from Etch
- # everwhere here.
- # Until then re-parse the XML using REXML.
- #responsedata[:configs] = results[:configs]
- responsedata[:configs] = {}
- results[:configs].each {|f,c| responsedata[:configs][f] = REXML::Document.new(c.to_s) }
- responsedata[:need_sums] = {}
- responsedata[:need_origs] = results[:need_orig]
- #responsedata[:allcommands] = results[:allcommands]
- responsedata[:allcommands] = {}
- results[:allcommands].each {|cn,c| responsedata[:allcommands][cn] = REXML::Document.new(c.to_s) }
- responsedata[:retrycommands] = results[:retrycommands]
- else
- puts "Sending request to server #{@filesuri}: #{request.inspect}" if (@debug)
- post = Net::HTTP::Post.new(@filesuri.path)
- post.set_form_data(request)
- sign_post!(post, @key)
- response = http.request(post)
- if !response.kind_of?(Net::HTTPSuccess)
- $stderr.puts response.body
- # error! raises an exception
- response.error!
- end
- puts "Response from server:\n'#{response.body}'" if (@debug)
- if !response.body.nil? && !response.body.empty?
- response_xml = REXML::Document.new(response.body)
- responsedata[:configs] = {}
- response_xml.elements.each('/files/configs/config') do |config|
- file = config.attributes['filename']
- # We have to make a new document so that XPath paths are
- # referenced relative to the configuration for this
- # specific file.
- #responsedata[:configs][file] = REXML::Document.new(response_xml.elements["/files/configs/config[@filename='#{file}']"].to_s)
- responsedata[:configs][file] = REXML::Document.new(config.to_s)
- end
- responsedata[:need_sums] = {}
- response_xml.elements.each('/files/need_sums/need_sum') do |ns|
- responsedata[:need_sums][ns.text] = true
- end
- responsedata[:need_origs] = {}
- response_xml.elements.each('/files/need_origs/need_orig') do |no|
- responsedata[:need_origs][no.text] = true
- end
- responsedata[:allcommands] = {}
- response_xml.elements.each('/files/allcommands/commands') do |command|
- commandname = command.attributes['commandname']
- # We have to make a new document so that XPath paths are
- # referenced relative to the configuration for this
- # specific file.
- #responsedata[:allcommands][commandname] = REXML::Document.new(response_xml.root.elements["/files/allcommands/commands[@commandname='#{commandname}']"].to_s)
- responsedata[:allcommands][commandname] = REXML::Document.new(command.to_s)
- end
- responsedata[:retrycommands] = {}
- response_xml.elements.each('/files/retrycommands/retrycommand') do |rc|
- responsedata[:retrycommands][rc.text] = true
- end
- else
- puts " Response is empty" if (@debug)
- break
- end
- end
-
- #
- # Process the response from the server
- #
-
- # Prep a clean request hash
- if @local
- request = {}
- if !responsedata[:need_origs].empty?
- request[:files] = {}
- end
- if !responsedata[:retrycommands].empty?
- request[:commands] = {}
- end
- else
- request = get_blank_request
- end
-
- # With generateall we expect to make at least two round trips
- # to the server.
- # 1) Send GENERATEALL request, get back a list of need_sums
- # 2) Send sums, possibly get back some need_origs
- # 3) Send origs, get back generated files
- need_to_loop = false
- reset_already_processed
- # Process configs first, as they may contain setup entries that are
- # needed to create the original files.
- responsedata[:configs].each_key do |file|
- puts "Processing config for #{file}" if (@debug)
- if !@listfiles
- continue_processing = process_file(file, responsedata)
- if !continue_processing
- throw :stop_processing
- end
- else
- files_to_list[file] = true
- end
- end
- responsedata[:need_sums].each_key do |need_sum|
- puts "Processing request for sum of #{need_sum}" if (@debug)
- if @local
- # If this happens we screwed something up, the local mode
- # code never requests sums.
- raise "No support for sums in local mode"
- else
- request["files[#{CGI.escape(need_sum)}][sha1sum]"] =
- get_orig_sum(need_sum)
- end
- local_requests = get_local_requests(need_sum)
- if local_requests
- if @local
- request[:files][need_sum][:local_requests] = local_requests
- else
- request["files[#{CGI.escape(need_sum)}][local_requests]"] =
- local_requests
- end
- end
- need_to_loop = true
- end
- responsedata[:need_origs].each_key do |need_orig|
- puts "Processing request for contents of #{need_orig}" if (@debug)
- if @local
- request[:files][need_orig] = {:orig => save_orig(need_orig)}
- else
- request["files[#{CGI.escape(need_orig)}][contents]"] =
- Base64.encode64(get_orig_contents(need_orig))
- request["files[#{CGI.escape(need_orig)}][sha1sum]"] =
- get_orig_sum(need_orig)
- end
- local_requests = get_local_requests(need_orig)
- if local_requests
- if @local
- request[:files][need_orig][:local_requests] = local_requests
- else
- request["files[#{CGI.escape(need_orig)}][local_requests]"] =
- local_requests
- end
- end
- need_to_loop = true
- end
- responsedata[:allcommands].each_key do |commandname|
- puts "Processing commands #{commandname}" if (@debug)
- continue_processing = process_commands(commandname, responsedata)
- if !continue_processing
- throw :stop_processing
- end
- end
- responsedata[:retrycommands].each_key do |commandname|
- puts "Processing request to retry command #{commandname}" if (@debug)
- if @local
- request[:commands][commandname] = true
- else
- request["commands[#{CGI.escape(commandname)}]"] = '1'
- end
- need_to_loop = true
- end
-
- if !need_to_loop
- break
- end
- end
-
- puts "Processing 'exec once per run' commands" if (!exec_once_per_run.empty?)
- exec_once_per_run.keys.each do |exec|
- process_exec('post', exec)
- end
- rescue Exception => e
- status = 1
- $stderr.puts e.message
- message << e.message
- $stderr.puts e.backtrace.join("\n") if @debug
- message << e.backtrace.join("\n") if @debug
- end # begin/rescue
- end # catch
-
- if @listfiles
- puts "Files under management:"
- files_to_list.keys.sort.each {|file| puts file}
- end
-
- # Send results to server
- if !@dryrun && !@local
- rails_results = []
- # A few of the fields here are numbers or booleans and need a
- # to_s to make them compatible with CGI.escape, which expects a
- # string.
- rails_results << "fqdn=#{CGI.escape(@facts['fqdn'])}"
- rails_results << "status=#{CGI.escape(status.to_s)}"
- rails_results << "message=#{CGI.escape(message)}"
- if @detailed_results.include?('SERVER')
- @results.each do |result|
- # Strangely enough this works. Even though the key is not unique to
- # each result the Rails parameter parsing code keeps track of keys it
- # has seen, and if it sees a duplicate it starts a new hash.
- rails_results << "results[][file]=#{CGI.escape(result['file'])}"
- rails_results << "results[][success]=#{CGI.escape(result['success'].to_s)}"
- rails_results << "results[][message]=#{CGI.escape(result['message'])}"
- end
- end
- puts "Sending results to server #{@resultsuri}" if (@debug)
- resultspost = Net::HTTP::Post.new(@resultsuri.path)
- # We have to bypass Net::HTTP's set_form_data method in this case
- # because it expects a hash and we can't provide the results in the
- # format we want in a hash because we'd have duplicate keys (see above).
- results_as_string = rails_results.join('&')
- resultspost.body = results_as_string
- resultspost.content_type = 'application/x-www-form-urlencoded'
- sign_post!(resultspost, @key)
- response = http.request(resultspost)
- case response
- when Net::HTTPSuccess
- puts "Response from server:\n'#{response.body}'" if (@debug)
- else
- $stderr.puts "Error submitting results:"
- $stderr.puts response.body
- end
- end
-
- if !@dryrun
- @detailed_results.each do |detail_dest|
- # If any of the destinations look like a file (start with a /) then we
- # log to that file
- if detail_dest =~ %r{^/}
- FileUtils.mkpath(File.dirname(detail_dest))
- File.open(detail_dest, 'a') do |file|
- # Add a header for the overall status of the run
- file.puts "Etch run at #{Time.now}"
- file.puts "Status: #{status}"
- if !message.empty?
- file.puts "Message:\n#{message}\n"
- end
- # Then the detailed results
- @results.each do |result|
- file.puts "File #{result['file']}, result #{result['success']}:\n"
- file.puts result['message']
- end
- end
- end
- end
- end
-
- status
- end
-
- def check_for_disable_etch_file
- disable_etch = File.join(@varbase, 'disable_etch')
- message = ''
- if File.exist?(disable_etch)
- if !@disableforce
- message = "Etch disabled:\n"
- message << IO.read(disable_etch)
- puts message
- return false, message
- else
- puts "Ignoring disable_etch file"
- end
- end
- return true, message
- end
-
- def get_blank_request
- @blankrequest.dup
- end
-
- # Raises an exception if any fatal error is encountered
- # Returns a boolean, true unless the user indicated in interactive mode
- # that further processing should be halted
- def process_file(file, responsedata)
- continue_processing = true
- save_results = true
- exception = nil
-
- # We may not have configuration for this file, if it does not apply
- # to this host. The server takes care of detecting any errors that
- # might involve, so here we can just silently return.
- config = responsedata[:configs][file]
- if !config
- puts "No configuration for #{file}, skipping" if (@debug)
- return continue_processing
- end
-
- # Skip files we've already processed in response to <depend>
- # statements.
- if @already_processed.has_key?(file)
- puts "Skipping already processed #{file}" if (@debug)
- return continue_processing
- end
-
- # Prep the results capturing for this file
- result = {}
- result['file'] = file
- result['success'] = true
- result['message'] = ''
-
- # catch/throw for expected/non-error events that end processing
- # begin/raise for error events that end processing
- # Within this block you should throw :process_done if you've reached
- # a natural stopping point and nothing further needs to be done. You
- # should raise an exception if you encounter an error condition.
- # Do not 'return' or 'abort'.
- catch :process_done do
- begin
- start_output_capture
-
- puts "Processing #{file}" if (@debug)
-
- # The %locked_files hash provides a convenient way to
- # detect circular dependancies. It doesn't give us an ordered
- # list of dependencies, which might be handy to help the user
- # debug the problem, but I don't think it's worth maintaining a
- # seperate array just for that purpose.
- if @locked_files.has_key?(file)
- raise "Circular dependancy detected. " +
- "Dependancy list (unsorted) contains:\n " +
- @locked_files.keys.join(', ')
- end
-
- # This needs to be after the circular dependency check
- lock_file(file)
-
- # Process any other files that this file depends on
- config.elements.each('/config/depend') do |depend|
- puts "Processing dependency #{depend.text}" if (@debug)
- process_file(depend.text, responsedata)
- end
-
- # Process any commands that this file depends on
- config.elements.each('/config/dependcommand') do |dependcommand|
- puts "Processing command dependency #{dependcommand.text}" if (@debug)
- process_commands(dependcommand.text, responsedata)
- end
-
- # See what type of action the user has requested
-
- # Check to see if the user has requested that we revert back to the
- # original file.
- if config.elements['/config/revert']
- origpathbase = File.join(@origbase, file)
- origpath = nil
-
- # Restore the original file if it is around
- revert_occurred = false
- if File.exist?("#{origpathbase}.ORIG")
- origpath = "#{origpathbase}.ORIG"
- origdir = File.dirname(origpath)
- origbase = File.basename(origpath)
- filedir = File.dirname(file)
-
- # Remove anything we might have written out for this file
- remove_file(file) if (!@dryrun)
-
- puts "Restoring #{origpath} to #{file}"
- recursive_copy_and_rename(origdir, origbase, file) if (!@dryrun)
- revert_occurred = true
- elsif File.exist?("#{origpathbase}.TAR")
- origpath = "#{origpathbase}.TAR"
- filedir = File.dirname(file)
-
- # Remove anything we might have written out for this file
- remove_file(file) if (!@dryrun)
-
- puts "Restoring #{file} from #{origpath}"
- system("cd #{filedir} && tar xf #{origpath}") if (!@dryrun)
- revert_occurred = true
- elsif File.exist?("#{origpathbase}.NOORIG")
- origpath = "#{origpathbase}.NOORIG"
- puts "Original #{file} didn't exist, restoring that state"
-
- # Remove anything we might have written out for this file
- remove_file(file) if (!@dryrun)
- revert_occurred = true
- end
-
- # Update the history log
- if revert_occurred
- save_history(file)
- end
-
- # Now remove the backed-up original so that future runs
- # don't do anything
- if origpath
- remove_file(origpath) if (!@dryrun)
- end
-
- throw :process_done
- end
-
- # Perform any setup commands that the user has requested.
- # These are occasionally needed to install software that is
- # required to generate the file (think m4 for sendmail.cf) or to
- # install a package containing a sample config file which we
- # then edit with a script, and thus doing the install in <pre>
- # is too late.
- if config.elements['/config/setup']
- process_setup(file, config)
- end
-
- if config.elements['/config/file'] # Regular file
- newcontents = nil
- if config.elements['/config/file/contents']
- newcontents = Base64.decode64(config.elements['/config/file/contents'].text)
- end
-
- permstring = config.elements['/config/file/perms'].text
- perms = permstring.oct
- owner = config.elements['/config/file/owner'].text
- group = config.elements['/config/file/group'].text
- uid = lookup_uid(owner)
- gid = lookup_gid(group)
-
- set_file_contents = false
- if newcontents
- set_file_contents = !compare_file_contents(file, newcontents)
- end
- set_permissions = nil
- set_ownership = nil
- # If the file is currently something other than a plain file then
- # always set the flags to set the permissions and ownership.
- # Checking the permissions/ownership of whatever is there currently
- # is useless.
- if set_file_contents && (!File.file?(file) || File.symlink?(file))
- set_permissions = true
- set_ownership = true
- else
- set_permissions = !compare_permissions(file, perms)
- set_ownership = !compare_ownership(file, uid, gid)
- end
-
- # Proceed if:
- # - The new contents are different from the current file
- # - The permissions or ownership requested don't match the
- # current permissions or ownership
- if !set_file_contents &&
- !set_permissions &&
- !set_ownership
- puts "No change to #{file} necessary" if (@debug)
- throw :process_done
- else
- # Tell the user what we're going to do
- if set_file_contents
- # If the new contents are different from the current file
- # show that to the user in the format they've requested.
- # If the requested permissions are not world-readable then
- # use the filenameonly format so that we don't disclose
- # non-public data, unless we're in interactive mode
- if @filenameonly || (permstring.to_i(8) & 0004 == 0 && !@interactive)
- puts "Will write out new #{file}"
- elsif @fullfile
- # Grab the first 8k of the contents
- first8k = newcontents.slice(0, 8192)
- # Then check it for null characters. If it has any it's
- # likely a binary file.
- hasnulls = true if (first8k =~ /\0/)
-
- if !hasnulls
- puts "Generated contents for #{file}:"
- puts "============================================="
- puts newcontents
- puts "============================================="
- else
- puts "Will write out new #{file}, but " +
- "generated contents are not plain text so " +
- "they will not be displayed"
- end
- else
- # Default is to show a diff of the current file and the
- # newly generated file.
- puts "Will make the following changes to #{file}, diff -c:"
- tempfile = Tempfile.new(File.basename(file))
- tempfile.write(newcontents)
- tempfile.close
- puts "============================================="
- if File.file?(file) && !File.symlink?(file)
- system("diff -c #{file} #{tempfile.path}")
- else
- # Either the file doesn't currently exist,
- # or is something other than a normal file
- # that we'll be replacing with a file. In
- # either case diffing against /dev/null will
- # produce the most logical output.
- system("diff -c /dev/null #{tempfile.path}")
- end
- puts "============================================="
- tempfile.delete
- end
- end
- if set_permissions
- puts "Will set permissions on #{file} to #{permstring}"
- end
- if set_ownership
- puts "Will set ownership of #{file} to #{uid}:#{gid}"
- end
-
- # If the user requested interactive mode ask them for
- # confirmation to proceed.
- if @interactive
- case get_user_confirmation()
- when CONFIRM_PROCEED
- # No need to do anything
- when CONFIRM_SKIP
- save_results = false
- throw :process_done
- when CONFIRM_QUIT
- unlock_all_files
- continue_processing = false
- save_results = false
- throw :process_done
- else
- raise "Unexpected result from get_user_confirmation()"
- end
- end
-
- # Perform any pre-action commands that the user has requested
- if config.elements['/config/pre']
- process_pre(file, config)
- end
-
- # If the original "file" is a directory and the user hasn't
- # specifically told us we can overwrite it then raise an exception.
- #
- # The test is here, rather than a bit earlier where you might
- # expect it, because the pre section may be used to address
- # originals which are directories. So we don't check until
- # after any pre commands are run.
- if File.directory?(file) && !File.symlink?(file) &&
- !config.elements['/config/file/overwrite_directory']
- raise "Can't proceed, original of #{file} is a directory,\n" +
- " consider the overwrite_directory flag if appropriate."
- end
-
- # Give save_orig a definitive answer on whether or not to save the
- # contents of an original directory.
- origpath = save_orig(file, true)
- # Update the history log
- save_history(file)
-
- # Make sure the directory tree for this file exists
- filedir = File.dirname(file)
- if !File.directory?(filedir)
- puts "Making directory tree #{filedir}"
- FileUtils.mkpath(filedir) if (!@dryrun)
- end
-
- # Make a backup in case we need to roll back. We have no use
- # for a backup if there are no test commands defined (since we
- # only use the backup to roll back if the test fails), so don't
- # bother to create a backup unless there is a test command defined.
- backup = nil
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- backup = make_backup(file)
- puts "Created backup #{backup}"
- end
-
- # If the new contents are different from the current file,
- # replace the file.
- if set_file_contents
- if !@dryrun
- # Write out the new contents into a temporary file
- filebase = File.basename(file)
- filedir = File.dirname(file)
- newfile = Tempfile.new(filebase, filedir)
-
- # Set the proper permissions on the file before putting
- # data into it.
- newfile.chmod(perms)
- begin
- newfile.chown(uid, gid)
- rescue Errno::EPERM
- raise if Process.euid == 0
- end
-
- puts "Writing new contents of #{file} to #{newfile.path}" if (@debug)
- newfile.write(newcontents)
- newfile.close
-
- # If the current file is not a plain file, remove it.
- # Plain files are left alone so that the replacement is
- # atomic.
- if File.symlink?(file) || (File.exist?(file) && ! File.file?(file))
- puts "Current #{file} is not a plain file, removing it" if (@debug)
- remove_file(file)
- end
-
- # Move the new file into place
- File.rename(newfile.path, file)
-
- # Check the permissions and ownership now to ensure they
- # end up set properly
- set_permissions = !compare_permissions(file, perms)
- set_ownership = !compare_ownership(file, uid, gid)
- end
- end
-
- # Ensure the permissions are set properly
- if set_permissions
- File.chmod(perms, file) if (!@dryrun)
- end
-
- # Ensure the ownership is set properly
- if set_ownership
- begin
- File.chown(uid, gid, file) if (!@dryrun)
- rescue Errno::EPERM
- raise if Process.euid == 0
- end
- end
-
- # Perform any test_before_post commands that the user has requested
- if config.elements['/config/test_before_post']
- if !process_test_before_post(file, config)
- restore_backup(file, backup)
- raise "test_before_post failed"
- end
- end
-
- # Perform any post-action commands that the user has requested
- if config.elements['/config/post']
- process_post(file, config)
- end
-
- # Perform any test commands that the user has requested
- if config.elements['/config/test']
- if !process_test(file, config)
- restore_backup(file, backup)
-
- # Re-run any post commands
- if config.elements['/config/post']
- process_post(file, config)
- end
- end
- end
-
- # Clean up the backup, we don't need it anymore
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- puts "Removing backup #{backup}"
- remove_file(backup) if (!@dryrun)
- end
-
- # Update the history log again
- save_history(file)
-
- throw :process_done
- end
- end
-
- if config.elements['/config/link'] # Symbolic link
-
- dest = config.elements['/config/link/dest'].text
-
- set_link_destination = !compare_link_destination(file, dest)
- absdest = File.expand_path(dest, File.dirname(file))
-
- permstring = config.elements['/config/link/perms'].text
- perms = permstring.oct
- owner = config.elements['/config/link/owner'].text
- group = config.elements['/config/link/group'].text
- uid = lookup_uid(owner)
- gid = lookup_gid(group)
-
- # lchown and lchmod are not supported on many platforms. The server
- # always includes ownership and permissions settings with any link
- # (pulling them from defaults.xml if the user didn't specify them in
- # the config.xml file.) As such link management would always fail
- # on systems which don't support lchown/lchmod, which seems like bad
- # behavior. So instead we check to see if they are implemented, and
- # if not just ignore ownership/permissions settings. I suppose the
- # ideal would be for the server to tell the client whether the
- # ownership/permissions were specifically requested (in config.xml)
- # rather than just defaults, and then for the client to always try to
- # manage ownership/permissions if the settings are not defaults (and
- # fail in the event that they aren't implemented.)
- if @lchown_supported.nil?
- lchowntestlink = Tempfile.new('etchlchowntest').path
- lchowntestfile = Tempfile.new('etchlchowntest').path
- File.delete(lchowntestlink)
- File.symlink(lchowntestfile, lchowntestlink)
- begin
- File.lchown(0, 0, lchowntestfile)
- @lchown_supported = true
- rescue NotImplementedError
- @lchown_supported = false
- rescue Errno::EPERM
- raise if Process.euid == 0
- end
- File.delete(lchowntestlink)
- end
- if @lchmod_supported.nil?
- lchmodtestlink = Tempfile.new('etchlchmodtest').path
- lchmodtestfile = Tempfile.new('etchlchmodtest').path
- File.delete(lchmodtestlink)
- File.symlink(lchmodtestfile, lchmodtestlink)
- begin
- File.lchmod(0644, lchmodtestfile)
- @lchmod_supported = true
- rescue NotImplementedError
- @lchmod_supported = false
- end
- File.delete(lchmodtestlink)
- end
-
- set_permissions = false
- if @lchmod_supported
- # If the file is currently something other than a link then
- # always set the flags to set the permissions and ownership.
- # Checking the permissions/ownership of whatever is there currently
- # is useless.
- if set_link_destination && !File.symlink?(file)
- set_permissions = true
- else
- set_permissions = !compare_permissions(file, perms)
- end
- end
- set_ownership = false
- if @lchown_supported
- if set_link_destination && !File.symlink?(file)
- set_ownership = true
- else
- set_ownership = !compare_ownership(file, uid, gid)
- end
- end
-
- # Proceed if:
- # - The new link destination differs from the current one
- # - The permissions or ownership requested don't match the
- # current permissions or ownership
- if !set_link_destination &&
- !set_permissions &&
- !set_ownership
- puts "No change to #{file} necessary" if (@debug)
- throw :process_done
- # Check that the link destination exists, and refuse to create
- # the link unless it does exist or the user told us to go ahead
- # anyway.
- #
- # Note that the destination may be a relative path, and the
- # target directory may not exist yet, so we have to convert the
- # destination to an absolute path and test that for existence.
- # expand_path should handle paths that are already absolute
- # properly.
- elsif ! File.exist?(absdest) && ! File.symlink?(absdest) &&
- ! config.elements['/config/link/allow_nonexistent_dest']
- puts "Destination #{dest} for link #{file} does not exist," +
- " consider the allow_nonexistent_dest flag if appropriate."
- throw :process_done
- else
- # Tell the user what we're going to do
- if set_link_destination
- puts "Linking #{file} -> #{dest}"
- end
- if set_permissions
- puts "Will set permissions on #{file} to #{permstring}"
- end
- if set_ownership
- puts "Will set ownership of #{file} to #{uid}:#{gid}"
- end
-
- # If the user requested interactive mode ask them for
- # confirmation to proceed.
- if @interactive
- case get_user_confirmation()
- when CONFIRM_PROCEED
- # No need to do anything
- when CONFIRM_SKIP
- save_results = false
- throw :process_done
- when CONFIRM_QUIT
- unlock_all_files
- continue_processing = false
- save_results = false
- throw :process_done
- else
- raise "Unexpected result from get_user_confirmation()"
- end
- end
-
- # Perform any pre-action commands that the user has requested
- if config.elements['/config/pre']
- process_pre(file, config)
- end
-
- # If the original "file" is a directory and the user hasn't
- # specifically told us we can overwrite it then raise an exception.
- #
- # The test is here, rather than a bit earlier where you might
- # expect it, because the pre section may be used to address
- # originals which are directories. So we don't check until
- # after any pre commands are run.
- if File.directory?(file) && !File.symlink?(file) &&
- !config.elements['/config/link/overwrite_directory']
- raise "Can't proceed, original of #{file} is a directory,\n" +
- " consider the overwrite_directory flag if appropriate."
- end
-
- # Give save_orig a definitive answer on whether or not to save the
- # contents of an original directory.
- origpath = save_orig(file, true)
- # Update the history log
- save_history(file)
-
- # Make sure the directory tree for this link exists
- filedir = File.dirname(file)
- if !File.directory?(filedir)
- puts "Making directory tree #{filedir}"
- FileUtils.mkpath(filedir) if (!@dryrun)
- end
-
- # Make a backup in case we need to roll back. We have no use
- # for a backup if there are no test commands defined (since we
- # only use the backup to roll back if the test fails), so don't
- # bother to create a backup unless there is a test command defined.
- backup = nil
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- backup = make_backup(file)
- puts "Created backup #{backup}"
- end
-
- # Create the link
- if set_link_destination
- remove_file(file) if (!@dryrun)
- File.symlink(dest, file) if (!@dryrun)
-
- # Check the permissions and ownership now to ensure they
- # end up set properly
- if @lchmod_supported
- set_permissions = !compare_permissions(file, perms)
- end
- if @lchown_supported
- set_ownership = !compare_ownership(file, uid, gid)
- end
- end
-
- # Ensure the permissions are set properly
- if set_permissions
- # Note: lchmod
- File.lchmod(perms, file) if (!@dryrun)
- end
-
- # Ensure the ownership is set properly
- if set_ownership
- begin
- # Note: lchown
- File.lchown(uid, gid, file) if (!@dryrun)
- rescue Errno::EPERM
- raise if Process.euid == 0
- end
- end
-
- # Perform any test_before_post commands that the user has requested
- if config.elements['/config/test_before_post']
- if !process_test_before_post(file, config)
- restore_backup(file, backup)
- raise "test_before_post failed"
- end
- end
-
- # Perform any post-action commands that the user has requested
- if config.elements['/config/post']
- process_post(file, config)
- end
-
- # Perform any test commands that the user has requested
- if config.elements['/config/test']
- if !process_test(file, config)
- restore_backup(file, backup)
-
- # Re-run any post commands
- if config.elements['/config/post']
- process_post(file, config)
- end
- end
- end
-
- # Clean up the backup, we don't need it anymore
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- puts "Removing backup #{backup}"
- remove_file(backup) if (!@dryrun)
- end
-
- # Update the history log again
- save_history(file)
-
- throw :process_done
- end
- end
-
- if config.elements['/config/directory'] # Directory
-
- # A little safety check
- create = config.elements['/config/directory/create']
- raise "No create element found in directory section" if !create
-
- permstring = config.elements['/config/directory/perms'].text
- perms = permstring.oct
- owner = config.elements['/config/directory/owner'].text
- group = config.elements['/config/directory/group'].text
- uid = lookup_uid(owner)
- gid = lookup_gid(group)
-
- set_directory = !File.directory?(file) || File.symlink?(file)
- set_permissions = nil
- set_ownership = nil
- # If the file is currently something other than a directory then
- # always set the flags to set the permissions and ownership.
- # Checking the permissions/ownership of whatever is there currently
- # is useless.
- if set_directory
- set_permissions = true
- set_ownership = true
- else
- set_permissions = !compare_permissions(file, perms)
- set_ownership = !compare_ownership(file, uid, gid)
- end
-
- # Proceed if:
- # - The current file is not a directory
- # - The permissions or ownership requested don't match the
- # current permissions or ownership
- if !set_directory &&
- !set_permissions &&
- !set_ownership
- puts "No change to #{file} necessary" if (@debug)
- throw :process_done
- else
- # Tell the user what we're going to do
- if set_directory
- puts "Making directory #{file}"
- end
- if set_permissions
- puts "Will set permissions on #{file} to #{permstring}"
- end
- if set_ownership
- puts "Will set ownership of #{file} to #{uid}:#{gid}"
- end
-
- # If the user requested interactive mode ask them for
- # confirmation to proceed.
- if @interactive
- case get_user_confirmation()
- when CONFIRM_PROCEED
- # No need to do anything
- when CONFIRM_SKIP
- save_results = false
- throw :process_done
- when CONFIRM_QUIT
- unlock_all_files
- continue_processing = false
- save_results = false
- throw :process_done
- else
- raise "Unexpected result from get_user_confirmation()"
- end
- end
-
- # Perform any pre-action commands that the user has requested
- if config.elements['/config/pre']
- process_pre(file, config)
- end
-
- # Give save_orig a definitive answer on whether or not to save the
- # contents of an original directory.
- origpath = save_orig(file, false)
- # Update the history log
- save_history(file)
-
- # Make sure the directory tree for this directory exists
- filedir = File.dirname(file)
- if !File.directory?(filedir)
- puts "Making directory tree #{filedir}"
- FileUtils.mkpath(filedir) if (!@dryrun)
- end
-
- # Make a backup in case we need to roll back. We have no use
- # for a backup if there are no test commands defined (since we
- # only use the backup to roll back if the test fails), so don't
- # bother to create a backup unless there is a test command defined.
- backup = nil
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- backup = make_backup(file)
- puts "Created backup #{backup}"
- end
-
- # Create the directory
- if set_directory
- remove_file(file) if (!@dryrun)
- Dir.mkdir(file) if (!@dryrun)
-
- # Check the permissions and ownership now to ensure they
- # end up set properly
- set_permissions = !compare_permissions(file, perms)
- set_ownership = !compare_ownership(file, uid, gid)
- end
-
- # Ensure the permissions are set properly
- if set_permissions
- File.chmod(perms, file) if (!@dryrun)
- end
-
- # Ensure the ownership is set properly
- if set_ownership
- begin
- File.chown(uid, gid, file) if (!@dryrun)
- rescue Errno::EPERM
- raise if Process.euid == 0
- end
- end
-
- # Perform any test_before_post commands that the user has requested
- if config.elements['/config/test_before_post']
- if !process_test_before_post(file, config)
- restore_backup(file, backup)
- raise "test_before_post failed"
- end
- end
-
- # Perform any post-action commands that the user has requested
- if config.elements['/config/post']
- process_post(file, config)
- end
-
- # Perform any test commands that the user has requested
- if config.elements['/config/test']
- if !process_test(file, config)
- restore_backup(file, backup)
-
- # Re-run any post commands
- if config.elements['/config/post']
- process_post(file, config)
- end
- end
- end
-
- # Clean up the backup, we don't need it anymore
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- puts "Removing backup #{backup}"
- remove_file(backup) if (!@dryrun)
- end
-
- # Update the history log again
- save_history(file)
-
- throw :process_done
- end
- end
-
- if config.elements['/config/delete'] # Delete whatever is there
-
- # A little safety check
- proceed = config.elements['/config/delete/proceed']
- raise "No proceed element found in delete section" if !proceed
-
- # Proceed only if the file currently exists
- if !File.exist?(file) && !File.symlink?(file)
- throw :process_done
- else
- # Tell the user what we're going to do
- puts "Removing #{file}"
-
- # If the user requested interactive mode ask them for
- # confirmation to proceed.
- if @interactive
- case get_user_confirmation()
- when CONFIRM_PROCEED
- # No need to do anything
- when CONFIRM_SKIP
- save_results = false
- throw :process_done
- when CONFIRM_QUIT
- unlock_all_files
- continue_processing = false
- save_results = false
- throw :process_done
- else
- raise "Unexpected result from get_user_confirmation()"
- end
- end
-
- # Perform any pre-action commands that the user has requested
- if config.elements['/config/pre']
- process_pre(file, config)
- end
-
- # If the original "file" is a directory and the user hasn't
- # specifically told us we can overwrite it then raise an exception.
- #
- # The test is here, rather than a bit earlier where you might
- # expect it, because the pre section may be used to address
- # originals which are directories. So we don't check until
- # after any pre commands are run.
- if File.directory?(file) && !File.symlink?(file) &&
- !config.elements['/config/delete/overwrite_directory']
- raise "Can't proceed, original of #{file} is a directory,\n" +
- " consider the overwrite_directory flag if appropriate."
- end
-
- # Give save_orig a definitive answer on whether or not to save the
- # contents of an original directory.
- origpath = save_orig(file, true)
- # Update the history log
- save_history(file)
-
- # Make a backup in case we need to roll back. We have no use
- # for a backup if there are no test commands defined (since we
- # only use the backup to roll back if the test fails), so don't
- # bother to create a backup unless there is a test command defined.
- backup = nil
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- backup = make_backup(file)
- puts "Created backup #{backup}"
- end
-
- # Remove the file
- remove_file(file) if (!@dryrun)
-
- # Perform any test_before_post commands that the user has requested
- if config.elements['/config/test_before_post']
- if !process_test_before_post(file, config)
- restore_backup(file, backup)
- raise "test_before_post failed"
- end
- end
-
- # Perform any post-action commands that the user has requested
- if config.elements['/config/post']
- process_post(file, config)
- end
-
- # Perform any test commands that the user has requested
- if config.elements['/config/test']
- if !process_test(file, config)
- restore_backup(file, backup)
-
- # Re-run any post commands
- if config.elements['/config/post']
- process_post(file, config)
- end
- end
- end
-
- # Clean up the backup, we don't need it anymore
- if config.elements['/config/test_before_post'] ||
- config.elements['/config/test']
- puts "Removing backup #{backup}"
- remove_file(backup) if (!@dryrun)
- end
-
- # Update the history log again
- save_history(file)
-
- throw :process_done
- end
- end
- rescue Exception
- result['success'] = false
- exception = $!
- end # End begin block
- end # End :process_done catch block
-
- unlock_file(file)
-
- output = stop_output_capture
- if exception
- output << exception.message
- output << exception.backtrace.join("\n") if @debug
- end
- result['message'] << output
- if save_results
- @results << result
- end
-
- if exception
- raise exception
- end
-
- @already_processed[file] = true
-
- continue_processing
- end
-
- # Raises an exception if any fatal error is encountered
- # Returns a boolean, true unless the user indicated in interactive mode
- # that further processing should be halted
- def process_commands(commandname, responsedata)
- continue_processing = true
- save_results = true
- exception = nil
-
- # We may not have configuration for this file, if it does not apply
- # to this host. The server takes care of detecting any errors that
- # might involve, so here we can just silently return.
- command = responsedata[:allcommands][commandname]
- if !command
- puts "No configuration for command #{commandname}, skipping" if (@debug)
- return continue_processing
- end
-
- # Skip commands we've already processed in response to <depend>
- # statements.
- if @already_processed.has_key?(commandname)
- puts "Skipping already processed command #{commandname}" if (@debug)
- return continue_processing
- end
-
- # Prep the results capturing for this command
- result = {}
- result['file'] = commandname
- result['success'] = true
- result['message'] = ''
-
@@ Diff output truncated at 100000 characters. @@
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|