You can subscribe to this list here.
2008 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
(2) |
Dec
|
---|---|---|---|---|---|---|---|---|---|---|---|---|
2009 |
Jan
(3) |
Feb
(15) |
Mar
|
Apr
(9) |
May
|
Jun
(6) |
Jul
|
Aug
|
Sep
(23) |
Oct
(25) |
Nov
(44) |
Dec
(9) |
2010 |
Jan
(14) |
Feb
|
Mar
(4) |
Apr
(1) |
May
|
Jun
(3) |
Jul
|
Aug
(4) |
Sep
|
Oct
(10) |
Nov
(4) |
Dec
(22) |
2011 |
Jan
(14) |
Feb
|
Mar
(3) |
Apr
(7) |
May
(16) |
Jun
(4) |
Jul
(6) |
Aug
(3) |
Sep
|
Oct
(4) |
Nov
|
Dec
|
2012 |
Jan
|
Feb
|
Mar
(10) |
Apr
(24) |
May
|
Jun
|
Jul
(2) |
Aug
(2) |
Sep
|
Oct
|
Nov
|
Dec
|
From: <jh...@us...> - 2009-10-13 18:30:07
|
Revision: 119 http://etch.svn.sourceforge.net/etch/?rev=119&view=rev Author: jheiss Date: 2009-10-13 18:29:59 +0000 (Tue, 13 Oct 2009) Log Message: ----------- Add a little wrapper script to simplify running etch from the working directory and/or distribution directory. Added Paths: ----------- trunk/client/run_etch Added: trunk/client/run_etch =================================================================== --- trunk/client/run_etch (rev 0) +++ trunk/client/run_etch 2009-10-13 18:29:59 UTC (rev 119) @@ -0,0 +1,11 @@ +#!/bin/sh + +# This script makes it easier to run the etch client from the +# distribution directory structure. It is not needed when the etch +# client is packaged and installed. + +RUBYLIB=../server/lib +export RUBYLIB + +exec ./etch "$@" + Property changes on: trunk/client/run_etch ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-13 18:26:11
|
Revision: 118 http://etch.svn.sourceforge.net/etch/?rev=118&view=rev Author: jheiss Date: 2009-10-13 18:26:04 +0000 (Tue, 13 Oct 2009) Log Message: ----------- Change the owner/group from nobody/wheel to demouser/demogroup. Nobody caused problems on Mac OS X (it's UID is -2, which Ruby choked on), seems easier to just set it to something bogus that will fall back to 0/0. Modified Paths: -------------- trunk/etchserver-demo/source/tmp/etchdemo/plainfile/config.xml Modified: trunk/etchserver-demo/source/tmp/etchdemo/plainfile/config.xml =================================================================== --- trunk/etchserver-demo/source/tmp/etchdemo/plainfile/config.xml 2009-10-13 17:40:53 UTC (rev 117) +++ trunk/etchserver-demo/source/tmp/etchdemo/plainfile/config.xml 2009-10-13 18:26:04 UTC (rev 118) @@ -6,8 +6,8 @@ group doesn't exist UID 0 and GID 0 will be used as a faillback. --> - <owner>nobody</owner> - <group>wheel</group> + <owner>demouser</owner> + <group>demogroup</group> <source> <plain operatingsystem="!FreeBSD">plainfile</plain> <plain operatingsystem="FreeBSD">plainfile2</plain> This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-13 17:41:01
|
Revision: 117 http://etch.svn.sourceforge.net/etch/?rev=117&view=rev Author: jheiss Date: 2009-10-13 17:40:53 +0000 (Tue, 13 Oct 2009) Log Message: ----------- Tag 3.7 release Modified Paths: -------------- Makefile Added Paths: ----------- tags/release-3.7/ Modified: Makefile =================================================================== --- Makefile 2009-10-12 23:49:06 UTC (rev 116) +++ Makefile 2009-10-13 17:40:53 UTC (rev 117) @@ -1,4 +1,4 @@ -VER=3.6 +VER=3.7 TAGNAME=release-$(VER) all: dist This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:49:17
|
Revision: 116 http://etch.svn.sourceforge.net/etch/?rev=116&view=rev Author: jheiss Date: 2009-10-12 23:49:06 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Add support for guarded commands http://sourceforge.net/apps/trac/etch/wiki/ConfigurationCommands Add local mode so that the etch client can work against a local config repository rather than a server. Rename etch.rb to etchclient.rb to reflect its purpose. Break out the code from etchserver.rb which is not server-specific to a separate file named etch.rb. Abstract the XML calls in that file so that either REXML or LibXML can be used. This will support future modifications to the client to allow the client to run in standalone mode against a local configuration repository. Abstract the XML response generation in Etch::Server so that the server does not depend on LibXML. All code should now fall back to REXML if LibXML is not available. Add in support for an xmllib environment variable that can be used to force the code to a particular XML library. Fix bug in configfilter! which was triggering a bug in LibXML Fix issues with timing around saving originals, for non-directories etch was saving the original the first time it was asked for the original contents or metadata from the server. Fix bug with ordering of creating backups and creating directory structure. Modified Paths: -------------- trunk/server/lib/etchserver.rb trunk/test/README trunk/test/TODO trunk/test/etchtest.rb trunk/test/outputcapture.rb Added Paths: ----------- trunk/client/etchclient.rb trunk/etchserver-demo/commands.dtd trunk/etchserver-samples/commands.dtd trunk/server/lib/etch.rb trunk/test/commands.rb trunk/test/testrepo/commands.dtd Removed Paths: ------------- trunk/client/etch.rb Deleted: trunk/client/etch.rb =================================================================== --- trunk/client/etch.rb 2009-10-12 23:44:00 UTC (rev 115) +++ trunk/client/etch.rb 2009-10-12 23:49:06 UTC (rev 116) @@ -1,2085 +0,0 @@ -############################################################################## -# Etch configuration file management tool library -############################################################################## - -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 'cgi' -require 'timeout' - -module Etch -end - -class Etch::Client - VERSION = '1.14' - - CONFIRM_PROCEED = 1 - CONFIRM_SKIP = 2 - CONFIRM_QUIT = 3 - PRIVATE_KEY_PATHS = ["/etc/ssh/ssh_host_rsa_key", "/etc/ssh_host_rsa_key"] - - # 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] - @varbase = options[:varbase] ? options[:varbase] : '/var/etch' - @debug = options[:debug] - @dryrun = options[:dryrun] - @interactive = options[:interactive] - @filenameonly = options[:filenameonly] - @fullfile = options[:fullfile] - @key = options[:key] ? options[:key] : get_private_key_path - @disableforce = options[:disableforce] - @lockforce = options[:lockforce] - - # Ensure we have a sane path, particularly since we are often run from - # cron. - # FIXME: Read from config file - ENV['PATH'] = '/bin:/usr/bin:/sbin:/usr/sbin:/opt/csw/bin:/opt/csw/sbin' - - # 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') - - @origbase = File.join(@varbase, 'orig') - @historybase = File.join(@varbase, 'history') - @lockbase = File.join(@varbase, 'locks') - @requestbase = File.join(@varbase, 'requests') - - @blankrequest = {} - @facts = Facter.to_hash - # 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 @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 @debug - @blankrequest['debug'] = '1' - end - if @tag - @blankrequest['tag'] = @tag - 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_to_generate) - # 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 = '' - - http = Net::HTTP.new(@filesuri.host, @filesuri.port) - if @filesuri.scheme == "https" - # Eliminate the OpenSSL "using default DH parameters" warning - if File.exist?('/etc/etch/dhparams') - dh = OpenSSL::PKey::DH.new(IO.read('/etc/etch/dhparams')) - Net::HTTP.ssl_context_accessor(:tmp_dh_callback) - http.tmp_dh_callback = proc { dh } - end - http.use_ssl = true - if File.exist?('/etc/etch/ca.pem') - http.ca_file = '/etc/etch/ca.pem' - http.verify_mode = OpenSSL::SSL::VERIFY_PEER - elsif File.directory?('/etc/etch/ca') - http.ca_path = '/etc/etch/ca' - http.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - end - http.start - - # 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 = get_blank_request - - if !files_to_generate.nil? && !files_to_generate.empty? - files_to_generate.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 - else - request['files[GENERATEALL]'] = '1' - 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 - - 10.times do - # - # Send request to server - # - - 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) - response_xml = nil - case response - when Net::HTTPSuccess - puts "Response from server:\n'#{response.body}'" if (@debug) - if !response.body.nil? && !response.body.empty? - response_xml = REXML::Document.new(response.body) - else - puts " Response is empty" if (@debug) - break - end - else - $stderr.puts response.body - # error! raises an exception - response.error! - end - - # - # Process the response from the server - # - - # Prep a clean request hash - request = get_blank_request - - # 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. - response_xml.root.elements.each('/files/configs/config') do |config| - file = config.attributes['filename'] - puts "Processing config for #{file}" if (@debug) - continue_processing = process(response_xml, file) - if !continue_processing - throw :stop_processing - end - end - response_xml.root.elements.each('/files/need_sums/need_sum') do |need_sum| - puts "Processing request for sum of #{need_sum.text}" if (@debug) - request["files[#{CGI.escape(need_sum.text)}][sha1sum]"] = get_orig_sum(need_sum.text) - local_requests = get_local_requests(need_sum.text) - if local_requests - request["files[#{CGI.escape(need_sum.text)}][local_requests]"] = local_requests - end - need_to_loop = true - end - response_xml.root.elements.each('/files/need_origs/need_orig') do |need_orig| - puts "Processing request for contents of #{need_orig.text}" if (@debug) - request["files[#{CGI.escape(need_orig.text)}][contents]"] = Base64.encode64(get_orig_contents(need_orig.text)) - request["files[#{CGI.escape(need_orig.text)}][sha1sum]"] = get_orig_sum(need_orig.text) - local_requests = get_local_requests(need_orig.text) - if local_requests - request["files[#{CGI.escape(need_orig.text)}][local_requests]"] = local_requests - 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 - $stderr.puts e.backtrace.join("\n") if @debug - end # begin/rescue - end # catch - - # Send results to server - if !@dryrun - 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)}" - @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 - 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 - - 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(response_xml, file) - continue_processing = true - save_results = true - exception = nil - - # 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) - - # We have to make a new document so that XPath paths are referenced - # relative to the configuration for this specific file. - config = REXML::Document.new(response_xml.root.elements["/files/configs/config[@filename='#{file}']"].to_s) - - # Process any other files that this file depends on - config.elements.each('/config/depend') do |depend| - puts "Generating dependency #{depend.text}" if (@debug) - process(response_xml, depend.text) - 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) - - # Restore the original file if it is around - 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) - - # Now remove the backed-up original so that future runs - # don't do anything - remove_file(origpath) if (!@dryrun) - 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) - - # Now remove the backed-up original so that future runs - # don't do anything - remove_file(origpath) if (!@dryrun) - 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) - - # Now remove the backed-up original so that future runs - # don't do anything - 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 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 - - # 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 - - # 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 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 - - # 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 - - # 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 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 - - # 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 - - # 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 - - # Returns true if the new contents are different from the current file, - # or if the file does not currently exist. - def compare_file_contents(file, newcontents) - r = false - - # If the file currently exists and is a regular file, check to see - # if the new contents are different. - if File.file?(file) && !File.symlink?(file) - contents = IO.read(file) - if newcontents != contents - r = true - end - else - # The file doesn't currently exist or isn't a regular file - r = true - end - - r - end - - # Returns true if the new link destination is different from the current - # link, or if the link does not currently exist. - def compare_link_destination(file, newdest) - r = false - - # If the file currently exists and is a link, check to see if the - # new destination is different. - if File.symlink?(file) - currentdest = File.readlink(file) - if currentdest != newdest - r = true - end - else - # The file doesn't currently exist or isn't a link - r = true - end - - r - end - - def get_orig_sum(file) - Digest::SHA1.hexdigest(get_orig_contents(file)) - end - def get_orig_contents(file) - origpath = save_orig(file) - orig_contents = nil - # We only send back the actual original file contents if the original is - # a regular file, otherwise we send back an empty string. - if origpath =~ /\.ORIG$/ && File.file?(origpath) && !File.symlink?(origpath) - orig_contents = IO.read(origpath) - else - orig_contents = '' - end - orig_contents - end - # Save an original copy of the file if that hasn't been done already. - # Return the path to that original copy. - def save_orig(file, save_directory_contents=nil) - origpathbase = File.join(@origbase, file) - origpath = nil - - if File.exist?("#{origpathbase}.ORIG") || File.symlink?("#{origpathbase}.ORIG") - origpath = "#{origpathbase}.ORIG" - elsif File.exist?("#{origpathbase}.NOORIG") - origpath = "#{origpathbase}.NOORIG" - elsif File.exist?("#{origpathbase}.TAR") - origpath = "#{origpathbase}.TAR" - elsif File.exist?("#{origpathbase}.DIRTMP") && save_directory_contents.nil? - origpath = "#{origpathbase}.DIRTMP" - else - # The original file has not yet been saved - first_update = true - - # Make sure the directory tree for this file exists in the - # directory we save originals in. - origdir = File.dirname(origpathbase) - if !File.directory?(origdir) - puts "Making directory tree #{origdir}" - FileUtils.mkpath(origdir) if (!@dryrun) - end - - if File.directory?(file) && !File.symlink?(file) - # The original "file" is a directory - if save_directory_contents - # Tar up the original directory - origpath = "#{origpathbase}.TAR" - filedir = File.dirname(file) - filebase = File.basename(file) - puts "Saving contents of original directory #{file}" - system("cd #{filedir} && tar cf #{origpath} #{filebase}") if (!@dryrun) - # There may be contents in that directory that the - # user doesn't want exposed. Without a way to know, - # the safest thing is to set restrictive permissions - # on the tar file. - File.chmod(0400, origpath) if (!@dryrun) - elsif save_directory_contents.nil? - # We have a timing issue, in that we generally save original - # files before we have the configuration for that file. For - # directories that's a problem, because we save directories - # differently depending on whether we're configuring them to - # remain a directory, or replacing the directory with something - # else (file or symlink). So if we don't have a definitive - # directive on how to save the directory - # (i.e. save_directory_contents is nil) then just save a - # placeholder until we do get a definitive directive. - origpath = "#{origpathbase}.DIRTMP" - puts "Creating temporary original placeholder for directory #{file}" - File.open(origpath, 'w') { |file| } if (!@dryrun) - first_update = nil - else - # Just create a directory in the originals repository with - # ownership and permissions to match the original directory. - origpath = "#{origpathbase}.ORIG" - st = File::Stat.new(file) - puts "Saving ownership/permissions of original directory" - Dir.mkdir(origpath, st.mode) if (!@dryrun) - begin - File.chown(st.uid, st.gid, origpath) if (!@dryrun) - rescue Errno::EPERM - raise if Process.euid == 0 - end - end - elsif File.exist?(file) || File.symlink?(file) - # The original file exists, and is not a directory - origpath = "#{origpathbase}.ORIG" - puts "Saving original file: #{file} -> #{origpath}" - filedir = File.dirname(file) - filebase = File.basename(file) - recursive_copy_and_rename(filedir, filebase, origpath) if (!@dryrun) - else - origpath = "#{origpathbase}.NOORIG" - # If the original doesn't exist, we need to flag that so - # that we don't try to save our generated file as an - # original on future runs - puts "Original file doesn't exist: #{file}" - File.open(origpath, 'w') { |file| } if (!@dryrun) - end - - if !@first_update.has_key?(file) && !first_update.nil? - @first_update[file] = first_update - end - end - - # Remove the DIRTMP placeholder if it exists and no longer applies - if origpath !~ /\.DIRTMP$/ && File.exists?("#{origpathbase}.DIRTMP") - File.delete("#{origpathbase}.DIRTMP") - end - - origpath - end - - # This subroutine maintains a revision history for the file in @historybase - def save_history(file) - histpath = File.join(@historybase, "#{file}.HISTORY") - - # Make sure the directory tree for this file exists in the - # directory we save history in. - histdir = File.dirname(histpath) - if !File.directory?(histdir) - puts "Making directory tree #{histdir}" - FileUtils.mkpath(histdir) if (!@dryrun) - end - # Make sure the corresponding RCS directory exists as well. - histrcsdir = File.join(histdir, 'RCS') - if !File.directory?(histrcsdir) - puts "Making directory tree #{histrcsdir}" - FileUtils.mkpath(histrcsdir) if (!@dryrun) - end - - # If the history log doesn't exist and we didn't just create the - # original backup, that indicates that the original backup was made - # previously but the history log was not started at the same time. - # There are a variety of reasons why this might be the case (the - # original was saved by a previous version of etch that didn't have - # the history log feature, or the original was saved manually by - # someone) but whatever the reason is we want to use the original - # backup to start the history log before updating the history log - # with the current file. - if !File.exist?(histpath) && !@first_update[file] - origpath = save_orig(file) - if File.file?(origpath) && !File.symlink?(origpath) - puts "Starting history log with saved original file: " + - "#{origpath} -> #{histpath}" - FileUtils.copy(origpath, histpath) if (!@dryrun) - else - puts "Starting history log with 'ls -ld' output for " + - "saved original file: #{origpath} -> #{histpath}" - system("ls -ld #{origpath} > #{histpath} 2>&1") if (!@dryrun) - end - # Check the newly created history file into RCS - histbase = File.basename(histpath) - puts "Checking initial history log into RCS: #{histpath}" - if !@dryrun - # The -m flag shouldn't be needed, but it won't hurt - # anything and if something is out of sync and an RCS file - # already exists it will prevent ci from going interactive. - system( - "cd #{histdir} && " + - "ci -q -t-'Original of an etch modified file' " + - "-m'Update of an etch modified file' #{histbase} && " + - "co -q -r -kb #{histbase}") - end - set_history_permissions(file) - end - - # Copy current file - - # If the file already exists in RCS we need to check out a locked - # copy before updating it - histbase = File.basename(histpath) - rcsstatus = false - if !@dryrun - rcsstatus = system("cd #{histdir} && rlog -R #{histbase} > /dev/null 2>&1") - end - if rcsstatus - # set_history_permissions may set the checked-out file - # writeable, which normally causes co to abort. Thus the -f - # flag. - system("cd #{histdir} && co -q -l -f #{histbase}") if !@dryrun - end - - if File.file?(file) && !File.symlink?(file) - puts "Updating history log: #{file} -> #{histpath}" - FileUtils.copy(file, histpath) if (!@dryrun) - else - puts "Updating history log with 'ls -ld' output: " + - "#{histpath}" - system("ls -ld #{file} > #{histpath} 2>&1") if (!@dryrun) - end - - # Check the history file into RCS - puts "Checking history log update into RCS: #{histpath}" - if !@dryrun - # We only need one of the -t or -m flags depending on whether - # the history log already exists or not, rather than try to - # keep track of which one we need just specify both and let RCS - # pick the one it needs. - system( - "cd #{histdir} && " + - "ci -q -t-'Original of an etch modified file' " + - "-m'Update of an etch modified file' #{histbase} && " + - "co -q -r -kb #{histbase}") - end - - set_history_permissions(file) - end - - # Ensures that the history log file has appropriate permissions to avoid - # leaking information. - def set_history_permissions(file) - origpath = File.join(@origbase, "#{file}.ORIG") - histpath = File.join(@historybase, "#{file}.HISTORY") - - # We set the permissions to the more restrictive of the original - # file permissions and the current file permissions. - origperms = 0777 - if File.e... [truncated message content] |
From: <jh...@us...> - 2009-10-12 23:44:06
|
Revision: 115 http://etch.svn.sourceforge.net/etch/?rev=115&view=rev Author: jheiss Date: 2009-10-12 23:44:00 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Create etch manpage, add it to the client packages. Simplify the etch help output now that more details can be put into the man page. Rename etch.rb to etchclient.rb to reflect its purpose. Add etch.rb and versiontype.rb to the packages Modified Paths: -------------- trunk/client/Makefile trunk/client/etch trunk/client/etch-client.spec Added Paths: ----------- trunk/client/etch.8 Modified: trunk/client/Makefile =================================================================== --- trunk/client/Makefile 2009-10-12 23:36:25 UTC (rev 114) +++ trunk/client/Makefile 2009-10-12 23:44:00 UTC (rev 115) @@ -1,6 +1,6 @@ BUILDROOT=/var/tmp/etch-client-buildroot # Grab the current version from the library -VER=$(shell ruby -e "$$:.unshift('.'); require 'etch'; puts Etch::Client::VERSION") +VER=$(shell ruby -e "$$:.unshift('.'); require 'etchclient'; puts Etch::Client::VERSION") all: @@ -22,8 +22,14 @@ cp -p etch_to_trunk $(BUILDROOT)/usr/sbin chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 - cp -p etch.rb $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 + cp -p etchclient.rb $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8/etchclient.rb + cp -p ../server/lib/etch.rb $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8/etch.rb + cp -p ../server/lib/versiontype.rb $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8/versiontype.rb + mkdir -p $(BUILDROOT)/usr/share/man/man8 + cp -p etch.8 $(BUILDROOT)/usr/share/man/man8 mkdir -p $(BUILDROOT)/etc/etch cp -p ca.pem $(BUILDROOT)/etc/etch cp -p dhparams $(BUILDROOT)/etc/etch @@ -53,8 +59,14 @@ cp -p etch_to_trunk $(BUILDROOT)/usr/sbin chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/usr/local/lib/site_ruby/1.8 - cp -p etch.rb $(BUILDROOT)/usr/local/lib/site_ruby/1.8 + cp -p etchclient.rb $(BUILDROOT)/usr/local/lib/site_ruby/1.8 + chmod 444 $(BUILDROOT)/usr/local/lib/site_ruby/1.8/etchclient.rb + cp -p ../server/lib/etch.rb $(BUILDROOT)/usr/local/lib/site_ruby/1.8 chmod 444 $(BUILDROOT)/usr/local/lib/site_ruby/1.8/etch.rb + cp -p ../server/lib/versiontype.rb $(BUILDROOT)/usr/local/lib/site_ruby/1.8 + chmod 444 $(BUILDROOT)/usr/local/lib/site_ruby/1.8/versiontype.rb + mkdir -p $(BUILDROOT)/usr/share/man/man8 + cp -p etch.8 $(BUILDROOT)/usr/share/man/man8 mkdir -p $(BUILDROOT)/etc/etch cp -p ca.pem $(BUILDROOT)/etc/etch cp -p dhparams $(BUILDROOT)/etc/etch @@ -85,8 +97,14 @@ rm $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 - cp -p etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + cp -p etchclient.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etchclient.rb + cp -p ../server/lib/etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etch.rb + cp -p ../server/lib/versiontype.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/versiontype.rb + mkdir -p $(BUILDROOT)/usr/share/man/man8 + cp -p etch.8 $(BUILDROOT)/usr/share/man/man8 mkdir -p $(BUILDROOT)/etc/etch cp -p ca.pem $(BUILDROOT)/etc/etch cp -p dhparams $(BUILDROOT)/etc/etch @@ -121,7 +139,7 @@ # core dumping when running etch. The Sunfreeware ruby seems to work. # Sunfreeware doesn't play well with pkg-get, so we create a bit of a # hybrid. We still express all the dependencies against CSW, and put -# our library file (etch.rb) into /opt/csw. We modify etch to use +# our library file (etchclient.rb) into /opt/csw. We modify etch to use # the Sunfreeware ruby in /usr/local/bin, but then tell it to also look # in the /opt/csw directory for libraries. # I'm going to guess that the smaller utilities like etch_to_trunk won't have @@ -147,8 +165,14 @@ rm $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 - cp -p etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + cp -p etchclient.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etchclient.rb + cp -p ../server/lib/etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etch.rb + cp -p ../server/lib/versiontype.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 + chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/versiontype.rb + mkdir -p $(BUILDROOT)/usr/share/man/man8 + cp -p etch.8 $(BUILDROOT)/usr/share/man/man8 mkdir -p $(BUILDROOT)/etc/etch cp -p ca.pem $(BUILDROOT)/etc/etch cp -p dhparams $(BUILDROOT)/etc/etch Modified: trunk/client/etch =================================================================== --- trunk/client/etch 2009-10-12 23:36:25 UTC (rev 114) +++ trunk/client/etch 2009-10-12 23:44:00 UTC (rev 115) @@ -3,11 +3,11 @@ # Etch configuration file management tool ############################################################################## -# Ensure we can find etch.rb +# Ensure we can find etchclient.rb $:.unshift File.dirname(__FILE__) require 'optparse' -require 'etch' +require 'etchclient' # # Parse the command line options @@ -16,15 +16,15 @@ options = {} @generateall = nil -opts = OptionParser.new +opts = OptionParser.new(nil, 24, ' ') opts.banner = 'Usage: etch [options] [/path/to/config/file]' -opts.on('--generate-all', 'Can be used instead of giving a specific file to generate.') do |opt| +opts.on('--generate-all', 'Request all configuration.') do |opt| @generateall = opt end -opts.on('--dry-run', '-n', 'Prints contents of generated files instead of writing them out to disk.') do |opt| +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. Normally all setup/pre/post entries are ignored for a dry run. However, files with setup entries will generally fail to build if the setup entry hasn't been run.") do |opt| +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 @@ -32,41 +32,44 @@ # entries. options[:dryrun] = 'damp' end -opts.on('--interactive', 'Causes etch to pause before making each change and prompt the user for confirmation.') do |opt| +opts.on('--interactive', 'Prompt for confirmation before each change.') do |opt| options[:interactive] = opt end -opts.on('--full-file', 'Normally etch will print a diff to show what changes it will make to a file. This will cause etch to display the full new file contents instead.') do |opt| +opts.on('--full-file', 'Display full new file contents instead of a diff.') do |opt| options[:fullfile] = opt end -opts.on('--filename-only', 'Similar to the previous option, but in the opposite direction. Etch will only display the name of file to be changed.') do |opt| +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. Normally only lockfile over 2 hours old are removed.') do |opt| +opts.on('--lock-force', 'Force the removal of any existing lockfiles.') do |opt| options[:lockforce] = opt end -opts.on('--debug', 'Print lots of messages about what etch is doing') do |opt| - options[:debug] = opt +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| +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| +opts.on('--tag TAG', 'Request a specific repository tag from the server.') do |opt| options[:tag] = opt end -opts.on('--test-base TESTDIR', 'Use an alternate local working directory (for use by test suite only)') do |opt| +opts.on('--key PRIVATE_KEY', 'Use this private key for signing messages to server.') do |opt| + options[:key] = opt +end +opts.on('--test-base TESTDIR', 'Use an alternate local working directory.') do |opt| options[:varbase] = opt end -opts.on('--key PRIVATE_KEY', 'Use this private key for signing messages that are sent to the server') do |opt| - options[:key] = opt +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| +opts.on('--version', 'Show etch client version.') do |opt| puts Etch::Client::VERSION exit end -opts.on_tail("-h", "--help", "Show this message") do +opts.on_tail('-h', '--help', 'Show this message.') do puts opts exit end Modified: trunk/client/etch-client.spec =================================================================== --- trunk/client/etch-client.spec 2009-10-12 23:36:25 UTC (rev 114) +++ trunk/client/etch-client.spec 2009-10-12 23:44:00 UTC (rev 115) @@ -14,7 +14,10 @@ %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 /usr/sbin/etch_cron_wrapper /etc/cron.d/etch Added: trunk/client/etch.8 =================================================================== --- trunk/client/etch.8 (rev 0) +++ trunk/client/etch.8 2009-10-12 23:44:00 UTC (rev 115) @@ -0,0 +1,203 @@ +.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/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 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 This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:36:38
|
Revision: 114 http://etch.svn.sourceforge.net/etch/?rev=114&view=rev Author: jheiss Date: 2009-10-12 23:36:25 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Add a newline when assembling error response message so that the error and the backtrace don't blend together on the same line. Modified Paths: -------------- trunk/server/app/controllers/files_controller.rb Modified: trunk/server/app/controllers/files_controller.rb =================================================================== --- trunk/server/app/controllers/files_controller.rb 2009-10-12 23:35:37 UTC (rev 113) +++ trunk/server/app/controllers/files_controller.rb 2009-10-12 23:36:25 UTC (rev 114) @@ -19,7 +19,7 @@ rescue Exception => e logger.error e.message logger.info e.backtrace.join("\n") if params[:debug] - response = e.message + response = e.message + "\n" response << e.backtrace.join("\n") if params[:debug] render :text => response, :status => :internal_server_error end This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:35:45
|
Revision: 113 http://etch.svn.sourceforge.net/etch/?rev=113&view=rev Author: jheiss Date: 2009-10-12 23:35:37 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Add test cases for ensuring that the original file backup is not created until we receive a full configuration for that file. I.e. we want to save the original file the first time etch changes that particular file, not some previous point like the first time etch runs. Modified Paths: -------------- trunk/test/history.rb Modified: trunk/test/history.rb =================================================================== --- trunk/test/history.rb 2009-10-12 23:35:05 UTC (rev 112) +++ trunk/test/history.rb 2009-10-12 23:35:37 UTC (rev 113) @@ -179,6 +179,74 @@ assert_equal(origcontents + "\n", get_file_contents(origfile), 'original backup of file via setup') end + def test_delayed_history_setup + # + # Like the previous test this uses a setup command to put some content + # into the target file. However, the first run of etch is such that there + # is no configuration for the file on this particular client, and then a + # second run where the client is added to a node group such that the + # configuration for the file now applies. Ensure that the original is not + # saved until after the file configuration applies to this host and the + # setup command has a chance to run. + # + # Imagine for example that you have configuration for DNS servers in your + # repository, which includes a setup command which installs BIND and then + # configuration which operates on the original config file from the BIND + # package. You have a server that is running etch but not configured as + # anything particular and you decide to make it a DNS server. If etch + # saved the original file the first time it ran on that box it would have + # saved a NOORIG file, and then when you added the box to the dns_servers + # node group the setup command would run, install BIND (which would create + # the config file), but continue to report an empty original file. + # + testname = 'delayed history setup' + + FileUtils.mkdir_p("#{@repodir}/source/#{@targetfile}") + File.open("#{@repodir}/source/#{@targetfile}/config.xml", 'w') do |file| + file.puts <<-EOF + <config> + <file> + </file> + </config> + EOF + end + + # Run etch + #puts "Running '#{testname}' test" + run_etch(@port, @testbase) + + origfile = File.join(@testbase, 'orig', "#{@targetfile}.ORIG") + origcontents = "This is the original text for #{testname}" + + FileUtils.mkdir_p("#{@repodir}/source/#{@targetfile}") + File.open("#{@repodir}/source/#{@targetfile}/config.xml", 'w') do |file| + file.puts <<-EOF + <config> + <setup> + <exec>echo "#{origcontents}" > #{@targetfile}</exec> + </setup> + <file> + <warning_file/> + <source> + <plain>source</plain> + </source> + </file> + </config> + EOF + end + + sourcecontents = "Test #{testname}\n" + File.open("#{@repodir}/source/#{@targetfile}/source", 'w') do |file| + file.write(sourcecontents) + end + + # Run etch + #puts "Running '#{testname}' test" + run_etch(@port, @testbase) + + assert_equal(origcontents + "\n", get_file_contents(origfile), testname) + end + def test_history_link # # Ensure original file is backed up when it is a link This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:35:14
|
Revision: 112 http://etch.svn.sourceforge.net/etch/?rev=112&view=rev Author: jheiss Date: 2009-10-12 23:35:05 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Break the tests up into a number of test methods rather than one giant method. Reduces the risk of interference between the tests and makes it easier to run specific tests for debugging purposes. Add a test for the case where we're starting to manage a file in a directory that doesn't yet exist, and where a test is defined. I noticed that we have a ordering issue in that case, we try to make the backup due to the test before creating the directory structure, and making the backup fails because the directory structure doesn't exist yet. Modified Paths: -------------- trunk/test/actions.rb Modified: trunk/test/actions.rb =================================================================== --- trunk/test/actions.rb 2009-10-12 23:34:32 UTC (rev 111) +++ trunk/test/actions.rb 2009-10-12 23:35:05 UTC (rev 112) @@ -106,7 +106,9 @@ run_etch(@port, @testbase) assert_equal("exec_once\n", get_file_contents("#{@repodir}/exec_once"), 'exec_once_2nd_check') - + end + + def test_failed_setup # # Test a failed setup command to ensure etch aborts # @@ -147,7 +149,9 @@ # Verify that the file was not touched assert_equal(origcontents, get_file_contents(@targetfile), 'failed setup') - + end + + def test_failed_pre # # Test a failed pre command to ensure etch aborts # @@ -188,7 +192,9 @@ # Verify that the file was not touched assert_equal(origcontents, get_file_contents(@targetfile), 'failed pre') - + end + + def test_failed_test # # Run a test where the test action fails, ensure that the original # target file is restored and any post actions re-run afterwards @@ -229,9 +235,6 @@ file.write("Testing a failed test\n") end - # Clean up from previous runs - File.delete("#{@repodir}/post") if File.exist?("#{@repodir}/post") - # Run etch #puts "Running failed test test" run_etch(@port, @testbase) @@ -239,7 +242,9 @@ # Verify that the original was restored, and that post was run twice assert_equal(origcontents, get_file_contents(@targetfile), 'failed test target') assert_equal("post\npost\n", get_file_contents("#{@repodir}/post"), 'failed test post') - + end + + def test_failed_test_before_post # # Run a test where the test_before_post action fails, ensure that # post is not run @@ -266,9 +271,6 @@ EOF end - # Clean up from previous runs - File.delete("#{@repodir}/post") if File.exist?("#{@repodir}/post") - # Run etch #puts "Running failed test_before_post test" run_etch(@port, @testbase, true) @@ -330,7 +332,57 @@ # Verify that the lack of an original file was restored assert(!File.exist?(@targetfile) && !File.symlink?(@targetfile), 'failed test no original file') + end + + def test_nested_target + # + # Run a test where a test action is defined and the target file is in a + # directory that does not exist yet, thus requiring that we make the + # directory before creating the backup (or rather the NOORIG marker in + # this case). We had a bug where that failed, as the code to make the + # directory structure was after the code to make the backup. + # + # I.e. configuration to create /etc/foo/bar, but /etc/foo does not exist. + # The backup that is created when a test is defined (so that we can roll + # back if the test fails) is made as /etc/foo/bar.XXXXX, which requires + # that /etc/foo exist first. + # + nestedtargetdir = Tempfile.new('etchtest').path + File.delete(nestedtargetdir) + nestedtargetfile = File.join(nestedtargetdir, 'etchnestedtest') + + FileUtils.mkdir_p("#{@repodir}/source/#{nestedtargetfile}") + File.open("#{@repodir}/source/#{nestedtargetfile}/config.xml", 'w') do |file| + file.puts <<-EOF + <config> + <file> + <warning_file/> + <source> + <plain>source</plain> + </source> + </file> + <test> + <exec>true</exec> + </test> + </config> + EOF + end + + sourcecontents = "Testing a nested target\n" + File.open("#{@repodir}/source/#{nestedtargetfile}/source", 'w') do |file| + file.write(sourcecontents) + end + + # Run etch + #puts "Running nested target with test test" + run_etch(@port, @testbase) + + # Verify that the file was created properly + assert_equal(sourcecontents, get_file_contents(nestedtargetfile), 'nested target with test') + end + + def test_action_with_xml_escape # # Test an action requiring XML escape # The XML spec says that < and & must be escaped almost anywhere This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:34:38
|
Revision: 111 http://etch.svn.sourceforge.net/etch/?rev=111&view=rev Author: jheiss Date: 2009-10-12 23:34:32 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Break the tests up into a number of test methods rather than one giant method. Reduces the risk of interference between the tests and makes it easier to run specific tests for debugging purposes. Modified Paths: -------------- trunk/test/link.rb Modified: trunk/test/link.rb =================================================================== --- trunk/test/link.rb 2009-10-12 23:33:28 UTC (rev 110) +++ trunk/test/link.rb 2009-10-12 23:34:32 UTC (rev 111) @@ -18,6 +18,10 @@ @targetfile = Tempfile.new('etchtest').path #puts "Using #{@targetfile} as target file" + # Delete the target file so that we're starting with nothing. Creating + # the target file just served to create a unique filename. + File.delete(@targetfile) + # Generate a directory for our test repository @repodir = initialize_repository @port, @pid = start_server(@repodir) @@ -48,10 +52,6 @@ EOF end - # Delete the target file so that we're starting with nothing. Creating - # the target file just served to create a unique filename. - File.delete(@targetfile) - # Run etch #puts "Running initial link test" run_etch(@port, @testbase) @@ -98,7 +98,8 @@ EOF end - # Remove the file that the link is currently pointing to + # Remove the file that the link is currently pointing to (due to the + # previous test) File.delete(@destfile2) # Run etch @@ -106,33 +107,33 @@ run_etch(@port, @testbase) assert_equal(@destfile, File.readlink(@targetfile), 'link update from non-existent file') - + end + + def test_link_to_nonexistent_dest # # Run a test where we ask etch to create a link to a non-existent # destination. It should fail by design. # - # We removed @destfile2 in the previous test, so that will work for our - # test destination - FileUtils.mkdir_p("#{@repodir}/source/#{@targetfile}") File.open("#{@repodir}/source/#{@targetfile}/config.xml", 'w') do |file| file.puts <<-EOF <config> <link> - <dest>#{@destfile2}</dest> + <dest>#{@destfile}</dest> </link> </config> EOF end + File.delete(@destfile) + # Run etch #puts "Running link to non-existent destination test" run_etch(@port, @testbase) - # Verify that the link was not updated, it should still point to - # @destfile - assert_equal(@destfile, File.readlink(@targetfile), 'link to non-existent destination') + # Verify that the link was not created + assert(!File.symlink?(@targetfile), 'link to non-existent destination') # # Then run the same test (link to non-existent destination) with the @@ -145,7 +146,7 @@ <config> <link> <allow_nonexistent_dest/> - <dest>#{@destfile2}</dest> + <dest>#{@destfile}</dest> </link> </config> EOF @@ -156,8 +157,10 @@ run_etch(@port, @testbase) # Verify that the link was updated properly - assert_equal(@destfile2, File.readlink(@targetfile), 'link to non-existent destination with override') - + assert_equal(@destfile, File.readlink(@targetfile), 'link to non-existent destination with override') + end + + def test_link_relative # # Test creating a relative link # @@ -184,7 +187,9 @@ # Verify that the link was updated properly assert_equal(reldestfile, File.readlink(@targetfile), 'relative link') - + end + + def test_link_metadata # # Test ownership and permissions # @@ -219,7 +224,9 @@ # Verify that the link permissions got set correctly perms = File.lstat(@targetfile).mode & 07777 assert_equal(0777, perms, 'link perms') - + end + + def test_link_duplicate_dest # # Test duplicate dest instructions # @@ -236,14 +243,14 @@ EOF end - File.delete(@targetfile) - # Run etch #puts "Running duplicate dest instructions test" run_etch(@port, @testbase) assert_equal(@destfile, File.readlink(@targetfile), 'duplicate dest instructions') - + end + + def test_link_contradictory_dest # # Test contradictory dest instructions # @@ -259,16 +266,16 @@ </config> EOF end - - File.delete(@targetfile) if File.symlink?(@targetfile) - + # Run etch #puts "Running contradictory dest instructions test" run_etch(@port, @testbase, true) # Verify that the link wasn't created assert(!File.symlink?(@targetfile) && !File.exist?(@targetfile), 'contradictory dest instructions') - + end + + def test_link_duplicate_script # # Test duplicate script instructions # @@ -285,8 +292,6 @@ EOF end - File.delete(@targetfile) if File.symlink?(@targetfile) - File.open("#{@repodir}/source/#{@targetfile}/source", 'w') do |file| file.puts("@contents << '#{@destfile}'") end @@ -296,7 +301,9 @@ run_etch(@port, @testbase) assert_equal(@destfile, File.readlink(@targetfile), 'duplicate script instructions') - + end + + def test_link_contradictory_script # # Test contradictory script instructions # @@ -313,8 +320,6 @@ EOF end - File.delete(@targetfile) if File.symlink?(@targetfile) - File.open("#{@repodir}/source/#{@targetfile}/source", 'w') do |file| file.puts("@contents << '#{@destfile}'") end @@ -328,7 +333,6 @@ # Verify that the link wasn't created assert(!File.symlink?(@targetfile) && !File.exist?(@targetfile), 'contradictory script instructions') - end def teardown This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-10-12 23:33:37
|
Revision: 110 http://etch.svn.sourceforge.net/etch/?rev=110&view=rev Author: jheiss Date: 2009-10-12 23:33:28 +0000 (Mon, 12 Oct 2009) Log Message: ----------- Add test cast that help output fits on one 80x24 screen. Modified Paths: -------------- trunk/test/options.rb Modified: trunk/test/options.rb =================================================================== --- trunk/test/options.rb 2009-09-30 18:00:37 UTC (rev 109) +++ trunk/test/options.rb 2009-10-12 23:33:28 UTC (rev 110) @@ -106,7 +106,19 @@ assert_equal(origcontents, get_file_contents(@targetfile), '--dry-run') end - + + def test_help + output = nil + IO.popen("ruby ../client/etch --help") do |pipe| + output = pipe.readlines + end + # Make sure at least something resembling help output is there + assert(output.any? {|line| line.include?('Usage: etch')}, 'help output content') + # Make sure it fits on the screen + assert(output.all? {|line| line.length <= 80}, 'help output columns') + assert(output.size <= 23, 'help output lines') + end + def teardown stop_server(@pid) remove_repository(@repodir) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-30 18:00:50
|
Revision: 109 http://etch.svn.sourceforge.net/etch/?rev=109&view=rev Author: jheiss Date: 2009-09-30 18:00:37 +0000 (Wed, 30 Sep 2009) Log Message: ----------- Set appropriate svn:ignore Property Changed: ---------------- trunk/server/tmp/pids/ Property changes on: trunk/server/tmp/pids ___________________________________________________________________ Added: svn:ignore + *.pid This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 23:51:38
|
Revision: 108 http://etch.svn.sourceforge.net/etch/?rev=108&view=rev Author: jheiss Date: 2009-09-23 23:51:23 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Updates as a result of running "rake rails:update" following upgrade to Rails 2.3.4 Modified Paths: -------------- trunk/server/config/boot.rb trunk/server/public/javascripts/controls.js trunk/server/public/javascripts/dragdrop.js trunk/server/public/javascripts/effects.js trunk/server/public/javascripts/prototype.js Modified: trunk/server/config/boot.rb =================================================================== --- trunk/server/config/boot.rb 2009-09-23 18:57:44 UTC (rev 107) +++ trunk/server/config/boot.rb 2009-09-23 23:51:23 UTC (rev 108) @@ -44,6 +44,7 @@ def load_initializer require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" Rails::Initializer.run(:install_gem_spec_stubs) + Rails::GemDependency.add_frozen_gem_path end end @@ -67,7 +68,7 @@ class << self def rubygems_version - Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion + Gem::RubyGemsVersion rescue nil end def gem_version @@ -81,15 +82,15 @@ end def load_rubygems + min_version = '1.3.2' require 'rubygems' - - unless rubygems_version >= '0.9.4' - $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + unless rubygems_version >= min_version + $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) exit 1 end rescue LoadError - $stderr.puts %(Rails requires RubyGems >= 0.9.4. Please install RubyGems and try again: http://rubygems.rubyforge.org) + $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) exit 1 end Modified: trunk/server/public/javascripts/controls.js =================================================================== --- trunk/server/public/javascripts/controls.js 2009-09-23 18:57:44 UTC (rev 107) +++ trunk/server/public/javascripts/controls.js 2009-09-23 23:51:23 UTC (rev 108) @@ -1,22 +1,22 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ -// Autocompleter.Base handles all the autocompletion functionality +// Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // -// Specific autocompleters need to provide, at the very least, +// Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method +// the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized @@ -30,23 +30,23 @@ // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); -var Autocompleter = { } +var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { - element = $(element) - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; @@ -59,28 +59,28 @@ this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ + this.options.onShow = this.options.onShow || + function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { - setHeight: false, + setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; - this.options.onHide = this.options.onHide || + this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - if(typeof(this.options.tokens) == 'string') + if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; - + this.element.setAttribute('autocomplete','off'); Element.hide(this.update); @@ -91,10 +91,10 @@ show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && + if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, + new Insertion.After(this.update, '<iframe id="' + this.update.id + '_iefix" '+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); @@ -102,7 +102,7 @@ } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, - + fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; @@ -150,15 +150,15 @@ Event.stop(event); return; } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); - this.observer = + this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, @@ -170,35 +170,35 @@ onHover: function(event) { var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) + if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, - + onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, - + onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; - this.active = false; - }, - + this.active = false; + }, + render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { + if(this.hasFocus) { this.show(); this.active = true; } @@ -207,27 +207,27 @@ this.hide(); } }, - + markPrevious: function() { - if(this.index > 0) this.index-- + if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, - + markNext: function() { - if(this.index < this.entryCount-1) this.index++ + if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, - + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - + getCurrentEntry: function() { return this.getEntry(this.index); }, - + selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); @@ -244,7 +244,7 @@ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - + var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); @@ -257,7 +257,7 @@ } this.oldElementValue = this.element.value; this.element.focus(); - + if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, @@ -269,20 +269,20 @@ Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = + this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } - } else { + } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; - + if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); @@ -298,7 +298,7 @@ }, onObserverEvent: function() { - this.changed = false; + this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); @@ -351,16 +351,16 @@ getUpdatedChoices: function() { this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + + + var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - if(this.options.defaultParams) + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; - + new Ajax.Request(this.url, this.options); }, @@ -382,7 +382,7 @@ // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the +// text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to @@ -399,7 +399,7 @@ // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // -// It's possible to pass in a custom function as the 'selector' +// It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. @@ -427,20 +427,20 @@ var entry = instance.getToken(); var count = 0; - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + + if (foundPos == 0 && elem.length != entry.length) { + ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" + elem.substr(entry.length) + "</li>"); break; - } else if (entry.length >= instance.options.partialChars && + } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" + @@ -450,14 +450,14 @@ } } - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "<ul>" + ret.join('') + "</ul>"; } }, options || { }); @@ -474,7 +474,7 @@ setTimeout(function() { Field.activate(field); }, 1); -} +}; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { @@ -604,7 +604,7 @@ this.triggerCallback('onEnterHover'); }, getText: function() { - return this.element.innerHTML; + return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); @@ -780,7 +780,7 @@ onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw 'Server returned an invalid collection representation.'; + throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), @@ -937,7 +937,7 @@ loadingCollectionText: 'Loading options...' }; -// Delayed observer, like Form.Element.Observer, +// Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields @@ -947,7 +947,7 @@ this.element = $(element); this.callback = callback; this.timer = null; - this.lastValue = $F(this.element); + this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { @@ -960,4 +960,4 @@ this.timer = null; this.callback(this.element, $F(this.element)); } -}); +}); \ No newline at end of file Modified: trunk/server/public/javascripts/dragdrop.js =================================================================== --- trunk/server/public/javascripts/dragdrop.js 2009-09-23 18:57:44 UTC (rev 107) +++ trunk/server/public/javascripts/dragdrop.js 2009-09-23 23:51:23 UTC (rev 108) @@ -1,6 +1,6 @@ // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sa...@or...) -// +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sa...@or...) +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ @@ -32,7 +32,7 @@ options._containers.push($(containment)); } } - + if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE @@ -40,34 +40,34 @@ this.drops.push(options); }, - + findDeepestChild: function(drops) { deepest = drops[0]; - + for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; - + return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { - containmentNode = element.treeNode; + containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, - + isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || - (Element.classNames(element).detect( + (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, @@ -87,12 +87,12 @@ show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; - + this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); - + if(affected.length>0) drop = Droppables.findDeepestChild(affected); @@ -101,7 +101,7 @@ Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - + if (drop != this.last_active) Droppables.activate(drop); } }, @@ -112,8 +112,8 @@ if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; + this.last_active.onDrop(element, this.last_active.element, event); + return true; } }, @@ -121,25 +121,25 @@ if(this.last_active) this.deactivate(this.last_active); } -} +}; var Draggables = { drags: [], observers: [], - + register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); - + Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, - + unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { @@ -148,24 +148,24 @@ Event.stopObserving(document, "keypress", this.eventKeypress); } }, - + activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, - + deactivate: function() { this.activeDraggable = null; }, - + updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -173,36 +173,36 @@ // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; - + this.activeDraggable.updateDrag(event, pointer); }, - + endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, - + keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, - + addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, - + removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, - + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { @@ -210,7 +210,7 @@ }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, - + _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( @@ -218,7 +218,7 @@ ).length; }); } -} +}; /*--------------------------------------------------------------------------*/ @@ -234,12 +234,12 @@ }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false + afterFinish: function(){ + Draggable._dragging[element] = false } - }); + }); }, zindex: 1000, revert: false, @@ -250,57 +250,57 @@ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; - + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); - + var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); - + if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); - + if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; - + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } - Element.makePositioned(this.element); // fix IE + Element.makePositioned(this.element); // fix IE this.options = options; - this.dragging = false; + this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); - + Draggables.register(this); }, - + destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, - + currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, - + initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { + if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( @@ -309,34 +309,34 @@ tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; - + var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = Position.cumulativeOffset(this.element); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - + Draggables.activate(this); Event.stop(event); } }, - + startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); - + if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } - + if(this.options.ghosting) { this._clone = this.element.cloneNode(true); - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this.element._originallyAbsolute) + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } - + if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); @@ -347,28 +347,28 @@ this.originalScrollTop = this.options.scroll.scrollTop; } } - + Draggables.notify('onStart', this, event); - + if(this.options.starteffect) this.options.starteffect(this.element); }, - + updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); - + if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } - + Draggables.notify('onDrag', this, event); - + this.draw(pointer); if(this.options.change) this.options.change(this); - + if(this.options.scroll) { this.stopScrolling(); - + var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } @@ -386,16 +386,16 @@ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } - + // fix AppleWebKit rendering if(Prototype.Browser.WebKit) window.scrollBy(0,0); - + Event.stop(event); }, - + finishDrag: function(event, success) { this.dragging = false; - + if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -403,24 +403,24 @@ } if(this.options.ghosting) { - if (!this.element._originallyAbsolute) + if (!this._originallyAbsolute) Position.relativize(this.element); - delete this.element._originallyAbsolute; + delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); - + var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') @@ -433,67 +433,67 @@ if(this.options.zindex) this.element.style.zIndex = this.originalZ; - if(this.options.endeffect) + if(this.options.endeffect) this.options.endeffect(this.element); - + Draggables.deactivate(this); Droppables.reset(); }, - + keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, - + endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, - + draw: function(point) { var pos = Position.cumulativeOffset(this.element); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } - + var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; - + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) }.bind(this)); - + if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)) + return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} - + var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; - + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, - + stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); @@ -501,14 +501,14 @@ Draggables._lastScrollPointer = null; } }, - + startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, - + scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; @@ -524,7 +524,7 @@ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } - + Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); @@ -538,10 +538,10 @@ Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } - + if(this.options.change) this.options.change(this); }, - + _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { @@ -560,7 +560,7 @@ H = documentElement.clientHeight; } else { W = body.offsetWidth; - H = body.offsetHeight + H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; @@ -577,11 +577,11 @@ this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, - + onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - + onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) @@ -591,11 +591,11 @@ var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - + sortables: { }, - + _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { + while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } @@ -606,22 +606,23 @@ if(!element) return; return Sortable.sortables[element.id]; }, - + destroy: function(element){ - var s = Sortable.options(element); - + element = $(element); + var s = Sortable.sortables[element.id]; + if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); - + delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); - var options = Object.extend({ + var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, @@ -635,17 +636,17 @@ delay: 0, hoverclass: null, ghosting: false, - quiet: false, + quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be + + // these take arrays of elements or ids and can be // used for better initialization performance elements: false, handles: false, - + onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); @@ -682,24 +683,24 @@ if(options.zindex) options_for_draggable.zindex = options.zindex; - // build options for the droppables + // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover - } - + }; + var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass - } + }; // fix for gecko engine - Element.cleanWhitespace(element); + Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; @@ -712,14 +713,14 @@ (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); + (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; - options.droppables.push(e); + options.droppables.push(e); }); - + if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); @@ -741,7 +742,7 @@ return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, - + findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); @@ -758,7 +759,7 @@ var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } @@ -769,26 +770,26 @@ var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, - + onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); - + if(!Element.isParent(dropon, element)) { var index; - + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; - + if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - + for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); @@ -801,9 +802,9 @@ } } } - + dropon.insertBefore(element, child); - + Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } @@ -816,34 +817,34 @@ mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; + if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { - Sortable._marker = + Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } + } var offsets = Position.cumulativeOffset(dropon); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - + if(position=='after') - if(sortable.overlap == 'horizontal') + if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - + Sortable._marker.show(); }, - + _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; - + for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; - + var child = { id: encodeURIComponent(match ? match[1] : null), element: element, @@ -851,16 +852,16 @@ children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) - } - + }; + /* Get the element containing the children and recurse over it */ if (child.container) - this._tree(child.container, options, child) - + this._tree(child.container, options, child); + parent.children.push (child); } - return parent; + return parent; }, tree: function(element) { @@ -873,15 +874,15 @@ name: element.id, format: sortableOptions.format }, arguments[1] || { }); - + var root = { id: null, parent: null, children: [], container: element, position: 0 - } - + }; + return Sortable._tree(element, options, root); }, @@ -897,7 +898,7 @@ sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); - + return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); @@ -906,14 +907,14 @@ setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); - + var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); - + new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { @@ -922,16 +923,16 @@ } }); }, - + serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - + if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + + return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { @@ -940,16 +941,16 @@ }).join('&'); } } -} +}; // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); -} +}; -Element.findChildren = function(element, only, recursive, tagName) { +Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); @@ -965,8 +966,8 @@ }); return (elements.length>0 ? elements.flatten() : []); -} +}; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -} +}; \ No newline at end of file Modified: trunk/server/public/javascripts/effects.js =================================================================== --- trunk/server/public/javascripts/effects.js 2009-09-23 18:57:44 UTC (rev 107) +++ trunk/server/public/javascripts/effects.js 2009-09-23 23:51:23 UTC (rev 108) @@ -3,46 +3,46 @@ // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ +// For details, see the script.aculo.us web site: http://script.aculo.us/ -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { var color = '#'; - if (this.slice(0,4) == 'rgb(') { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if (this.slice(0,1) == '#') { - if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if (this.length==7) color = this.toLowerCase(); - } - } - return (color.length==7 ? color : (arguments[0] || this)); + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ -Element.collectTextNodes = function(element) { +Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : + return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; -Element.collectTextNodesIgnoreClass = function(element, className) { +Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { - element = $(element); - element.setStyle({fontSize: (percent/100) + 'em'}); + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; @@ -70,28 +70,23 @@ Transitions: { linear: Prototype.K, sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; + return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, - pulse: function(pos, pulses) { - pulses = pulses || 5; - return ( - ((pos % (1/pulses)) * pulses).round() == 0 ? - ((pos * pulses * 2) - (pos * pulses * 2).floor()) : - 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) - ); + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, - spring: function(pos) { - return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; @@ -112,14 +107,14 @@ tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; - + element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( - character == ' ' ? String.fromCharCode(160) : character), + character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); @@ -128,13 +123,13 @@ }, multiple: function(element, effect) { var elements; - if (((typeof element == 'object') || - Object.isFunction(element)) && + if (((typeof element == 'object') || + Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; - + var options = Object.extend({ speed: 0.1, delay: 0.0 @@ -156,7 +151,7 @@ var options = Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } }, arguments[2] || { }); - Effect[element.visible() ? + Effect[element.visible() ? Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); } }; @@ -168,20 +163,20 @@ Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; - this.interval = null; + this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); - - var position = Object.isString(effect.options.queue) ? + + var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; - + switch(position) { case 'front': - // move unstarted effects after this effect + // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; @@ -195,13 +190,13 @@ timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } - + effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); - + if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, @@ -214,7 +209,7 @@ }, loop: function() { var timePos = new Date().getTime(); - for(var i=0, len=this.effects.length;i<len;i++) + for(var i=0, len=this.effects.length;i<len;i++) this.effects[i] && this.effects[i].loop(timePos); } }); @@ -223,7 +218,7 @@ instances: $H(), get: function(queueName) { if (!Object.isString(queueName)) return queueName; - + return this.instances.get(queueName) || this.instances.set(queueName, new Effect.ScopedQueue()); } @@ -248,23 +243,35 @@ this.fromToDelta = this.options.to-this.options.from; this.totalTime = this.finishOn-this.startOn; this.totalFrames = this.options.fps*this.options.duration; - - eval('this.render = function(pos){ '+ - 'if (this.state=="idle"){this.state="running";'+ - codeForEvent(this.options,'beforeSetup')+ - (this.setup ? 'this.setup();':'')+ - codeForEvent(this.options,'afterSetup')+ - '};if (this.state=="running"){'+ - 'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+ - 'this.position=pos;'+ - codeForEvent(this.options,'beforeUpdate')+ - (this.update ? 'this.update(pos);':'')+ - codeForEvent(this.options,'afterUpdate')+ - '}}'); - + + this.render = (function() { + function dispatch(effect, eventName) { + if (effect.options[eventName + 'Internal']) + effect.options[eventName + 'Internal'](effect); + if (effect.options[eventName]) + effect.options[eventName](effect); + } + + return function(pos) { + if (this.state === "idle") { + this.state = "running"; + dispatch(this, 'beforeSetup'); + if (this.setup) this.setup(); + dispatch(this, 'afterSetup'); + } + if (this.state === "running") { + pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from; + this.position = pos; + dispatch(this, 'beforeUpdate'); + if (this.update) this.update(pos); + dispatch(this, 'afterUpdate'); + } + }; + })(); + this.event('beforeStart'); if (!this.options.sync) - Effect.Queues.get(Object.isString(this.options.queue) ? + Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).add(this); }, loop: function(timePos) { @@ -273,9 +280,9 @@ this.render(1.0); this.cancel(); this.event('beforeFinish'); - if (this.finish) this.finish(); + if (this.finish) this.finish(); this.event('afterFinish'); - return; + return; } var pos = (timePos - this.startOn) / this.totalTime, frame = (pos * this.totalFrames).round(); @@ -287,7 +294,7 @@ }, cancel: function() { if (!this.options.sync) - Effect.Queues.get(Object.isString(this.options.queue) ? + Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, @@ -325,10 +332,10 @@ Effect.Tween = Class.create(Effect.Base, { initialize: function(object, from, to) { object = Object.isString(object) ? $(object) : object; - var args = $A(arguments), method = args.last(), + var args = $A(arguments), method = args.last(), options = args.length == 5 ? args[3] : null; this.method = Object.isFunction(method) ? method.bind(object) : - Object.isFunction(object[method]) ? object[method].bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : function(value) { object[method] = value }; this.start(Object.extend({ from: from, to: to }, options || { })); }, @@ -392,7 +399,7 @@ // for backwards compatibility Effect.MoveBy = function(element, toTop, toLeft) { - return new Effect.Move(element, + return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; @@ -414,15 +421,15 @@ setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); - + this.originalStyle = { }; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); - + this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; - + var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { if (fontSize.indexOf(fontSizeType)>0) { @@ -430,9 +437,9 @@ this.fontSizeType = fontSizeType; } }.bind(this)); - + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; - + this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; @@ -507,17 +514,16 @@ Effect.ScrollTo = function(element) { var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(), - max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, - elementOffsets[1] > max ? max : elementOffsets[1], + elementOffsets[1], options, - function(p){ scrollTo(scrollOffsets.left, p.round()) } + function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; @@ -529,9 +535,9 @@ var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, - afterFinishInternal: function(effect) { + afterFinishInternal: function(effect) { if (effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); + effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); @@ -547,15 +553,15 @@ effect.element.forceRerendering(); }, beforeSetup: function(effect) { - effect.element.setOpacity(effect.options.from).show(); + effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); - var oldStyle = { - opacity: element.getInlineOpacity(), + var oldStyle = { + opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, @@ -563,12 +569,12 @@ height: element.style.height }; return new Effect.Parallel( - [ new Effect.Scale(element, 200, - { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], - Object.extend({ duration: 1.0, + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element) + Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } @@ -580,12 +586,12 @@ element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, + Object.extend({ scaleContent: false, + scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); - } + } }, arguments[1] || { }) ); }; @@ -593,15 +599,15 @@ Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } @@ -616,16 +622,16 @@ from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { - new Effect.Scale(effect.element, 1, { + new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { + beforeSetup: function(effect) { effect.element.makePositioned().makeCl... [truncated message content] |
From: <jh...@us...> - 2009-09-23 18:57:51
|
Revision: 107 http://etch.svn.sourceforge.net/etch/?rev=107&view=rev Author: jheiss Date: 2009-09-23 18:57:44 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Tag 3.6 release Added Paths: ----------- tags/release-3.6/ This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 18:57:38
|
Revision: 106 http://etch.svn.sourceforge.net/etch/?rev=106&view=rev Author: jheiss Date: 2009-09-23 18:57:29 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Add -rf to rm flags when cleaning up uncommitted files, since they may be directories. Modified Paths: -------------- Makefile Modified: Makefile =================================================================== --- Makefile 2009-09-23 18:53:09 UTC (rev 105) +++ Makefile 2009-09-23 18:57:29 UTC (rev 106) @@ -18,7 +18,7 @@ svn copy trunk tags/$(TAGNAME) # Remove files that aren't in svn # $$ to escape $ from make - svn status tags/$(TAGNAME) | grep '^?' | awk '{print $$2}' | xargs rm + svn status tags/$(TAGNAME) | grep '^?' | awk '{print $$2}' | xargs rm -rf clean: rm etch-*.tar.gz* This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 18:53:28
|
Revision: 105 http://etch.svn.sourceforge.net/etch/?rev=105&view=rev Author: jheiss Date: 2009-09-23 18:53:09 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Bump version Fix bug in last commit Modified Paths: -------------- Makefile Modified: Makefile =================================================================== --- Makefile 2009-09-23 18:42:07 UTC (rev 104) +++ Makefile 2009-09-23 18:53:09 UTC (rev 105) @@ -1,4 +1,4 @@ -VER=3.5 +VER=3.6 TAGNAME=release-$(VER) all: dist @@ -16,8 +16,9 @@ tag: svn copy trunk tags/$(TAGNAME) - # Remove files that aren't in svn - svn status tags/$(TAGNAME) | grep '^?' | awk '{print $2}' | xargs rm +# Remove files that aren't in svn +# $$ to escape $ from make + svn status tags/$(TAGNAME) | grep '^?' | awk '{print $$2}' | xargs rm clean: rm etch-*.tar.gz* This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 18:42:15
|
Revision: 104 http://etch.svn.sourceforge.net/etch/?rev=104&view=rev Author: jheiss Date: 2009-09-23 18:42:07 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Remove files that aren't in svn when making a tag, otherwise they get included in the distribution (which should also be fixed by doing an svn export, but I'm taking the easy route for now). Modified Paths: -------------- Makefile Modified: Makefile =================================================================== --- Makefile 2009-09-23 18:10:52 UTC (rev 103) +++ Makefile 2009-09-23 18:42:07 UTC (rev 104) @@ -16,6 +16,8 @@ tag: svn copy trunk tags/$(TAGNAME) + # Remove files that aren't in svn + svn status tags/$(TAGNAME) | grep '^?' | awk '{print $2}' | xargs rm clean: rm etch-*.tar.gz* This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 18:11:00
|
Revision: 103 http://etch.svn.sourceforge.net/etch/?rev=103&view=rev Author: jheiss Date: 2009-09-23 18:10:52 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Upgrade from Rails 2.1.1 to Rails 2.3.4 Modified Paths: -------------- trunk/server/app/controllers/application_controller.rb Modified: trunk/server/app/controllers/application_controller.rb =================================================================== --- trunk/server/app/controllers/application_controller.rb 2009-09-23 17:56:52 UTC (rev 102) +++ trunk/server/app/controllers/application_controller.rb 2009-09-23 18:10:52 UTC (rev 103) @@ -15,9 +15,6 @@ # See environment.rb for the email address(s) to which exceptions are mailed include ExceptionNotifiable - # Pick a unique cookie name to distinguish our session data from others' - session :session_key => '_etch_session_id' - # Verify that any changes are signed if the administrator has # enabled authentication before_filter :authenticate, :only => [:create, :update, :destroy] This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 17:57:07
|
Revision: 102 http://etch.svn.sourceforge.net/etch/?rev=102&view=rev Author: jheiss Date: 2009-09-23 17:56:52 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Use unicorn or script/server for testing based on a constant. Modified Paths: -------------- trunk/test/etchtest.rb Modified: trunk/test/etchtest.rb =================================================================== --- trunk/test/etchtest.rb 2009-09-23 01:07:42 UTC (rev 101) +++ trunk/test/etchtest.rb 2009-09-23 17:56:52 UTC (rev 102) @@ -45,6 +45,7 @@ FileUtils.rm_rf(repodir) end + UNICORN = false def start_server(repodir) ENV['etchserverbase'] = repodir # Pick a random port in the 3001-6000 range (range somewhat randomly chosen) @@ -54,13 +55,11 @@ sleep(5) else Dir.chdir('../server') - # FIXME: silence the server (see various failed attempts...) - # Causes ruby to fork, so we're left with a useless pid - #exec("./script/server -d -p #{port}") - # Causes ruby to invoke a copy of sh to run the command (to handle - # the redirect), which gets in the way of killing things - #exec("./script/server -p #{port} > /dev/null") - exec("./script/server -p #{port}") + if UNICORN + exec("unicorn_rails -p #{port}") + else + exec("./script/server -p #{port}") + end end [port, pid] end This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 01:07:48
|
Revision: 101 http://etch.svn.sourceforge.net/etch/?rev=101&view=rev Author: jheiss Date: 2009-09-23 01:07:42 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Set appropriate svn:ignore Property Changed: ---------------- trunk/server/log/ Property changes on: trunk/server/log ___________________________________________________________________ Added: svn:ignore + *.log This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 01:07:01
|
Revision: 100 http://etch.svn.sourceforge.net/etch/?rev=100&view=rev Author: jheiss Date: 2009-09-23 01:06:53 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Set appropriate svn:ignore Property Changed: ---------------- trunk/server/db/ Property changes on: trunk/server/db ___________________________________________________________________ Added: svn:ignore + *.sqlite3 schema.rb This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 01:05:13
|
Revision: 99 http://etch.svn.sourceforge.net/etch/?rev=99&view=rev Author: jheiss Date: 2009-09-23 01:05:05 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Remove schema.rb from revision control since it is auto-generated Removed Paths: ------------- trunk/server/db/schema.rb Deleted: trunk/server/db/schema.rb =================================================================== --- trunk/server/db/schema.rb 2009-09-23 01:02:24 UTC (rev 98) +++ trunk/server/db/schema.rb 2009-09-23 01:05:05 UTC (rev 99) @@ -1,72 +0,0 @@ -# This file is auto-generated from the current state of the database. Instead of editing this file, -# please use the migrations feature of Active Record to incrementally modify your database, and -# then regenerate this schema definition. -# -# Note that this schema.rb definition is the authoritative source for your database schema. If you need -# to create the application database on another system, you should be using db:schema:load, not running -# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). -# -# It's strongly recommended to check this file into your version control system. - -ActiveRecord::Schema.define(:version => 20081003164106) do - - create_table "clients", :force => true do |t| - t.string "name", :null => false - t.integer "status" - t.text "message" - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "clients", ["updated_at"], :name => "index_clients_on_updated_at" - add_index "clients", ["status"], :name => "index_clients_on_status" - add_index "clients", ["name"], :name => "index_clients_on_name", :unique => true - - create_table "etch_configs", :force => true do |t| - t.integer "client_id", :null => false - t.string "file", :null => false - t.text "config", :null => false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "etch_configs", ["file"], :name => "index_etch_configs_on_file" - add_index "etch_configs", ["client_id"], :name => "index_etch_configs_on_client_id" - - create_table "facts", :force => true do |t| - t.integer "client_id", :null => false - t.string "key", :null => false - t.text "value", :null => false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "facts", ["key"], :name => "index_facts_on_key" - add_index "facts", ["client_id"], :name => "index_facts_on_client_id" - - create_table "originals", :force => true do |t| - t.integer "client_id", :null => false - t.string "file", :null => false - t.string "sum", :null => false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "originals", ["file"], :name => "index_originals_on_file" - add_index "originals", ["client_id"], :name => "index_originals_on_client_id" - - create_table "results", :force => true do |t| - t.integer "client_id", :null => false - t.string "file", :null => false - t.boolean "success", :null => false - t.text "message", :null => false - t.datetime "created_at" - t.datetime "updated_at" - end - - add_index "results", ["created_at"], :name => "index_results_on_created_at" - add_index "results", ["file"], :name => "index_results_on_file" - add_index "results", ["client_id"], :name => "index_results_on_client_id" - -end This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 01:02:30
|
Revision: 98 http://etch.svn.sourceforge.net/etch/?rev=98&view=rev Author: jheiss Date: 2009-09-23 01:02:24 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Add etch_to_trunk to package. This is a tool for setting the config_mgmt_tag in nVentory to trunk-YYYYMMDD-HH00 for etch clients to temporarily bump them to trunk. Modified Paths: -------------- trunk/client/Makefile trunk/client/etch-client.spec Added Paths: ----------- trunk/client/etch_to_trunk Modified: trunk/client/Makefile =================================================================== --- trunk/client/Makefile 2009-09-23 00:58:08 UTC (rev 97) +++ trunk/client/Makefile 2009-09-23 01:02:24 UTC (rev 98) @@ -19,6 +19,8 @@ mkdir -p $(BUILDROOT)/usr/sbin cp -p etch $(BUILDROOT)/usr/sbin chmod 555 $(BUILDROOT)/usr/sbin/etch + cp -p etch_to_trunk $(BUILDROOT)/usr/sbin + chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 cp -p etch.rb $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/usr/lib/ruby/site_ruby/1.8/etch.rb @@ -48,6 +50,8 @@ mkdir -p $(BUILDROOT)/usr/sbin cp -p etch $(BUILDROOT)/usr/sbin chmod 555 $(BUILDROOT)/usr/sbin/etch + cp -p etch_to_trunk $(BUILDROOT)/usr/sbin + chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/usr/local/lib/site_ruby/1.8 cp -p etch.rb $(BUILDROOT)/usr/local/lib/site_ruby/1.8 chmod 444 $(BUILDROOT)/usr/local/lib/site_ruby/1.8/etch.rb @@ -75,6 +79,11 @@ cat $(BUILDROOT)/usr/sbin/etch.tmp | sed 's,/usr/bin/ruby,/opt/csw/bin/ruby,' > $(BUILDROOT)/usr/sbin/etch rm $(BUILDROOT)/usr/sbin/etch.tmp chmod 555 $(BUILDROOT)/usr/sbin/etch + cp -p etch_to_trunk $(BUILDROOT)/usr/sbin + mv $(BUILDROOT)/usr/sbin/etch_to_trunk $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp + cat $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp | sed 's,/usr/bin/ruby,/opt/csw/bin/ruby,' > $(BUILDROOT)/usr/sbin/etch_to_trunk + rm $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp + chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 cp -p etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etch.rb @@ -115,6 +124,8 @@ # our library file (etch.rb) into /opt/csw. We modify etch to use # the Sunfreeware ruby in /usr/local/bin, but then tell it to also look # in the /opt/csw directory for libraries. +# I'm going to guess that the smaller utilities like etch_to_trunk won't have +# problems, so I'm leaving them set to /opt/csw/bin/ruby. sysvpkg-sparc: pkginfo depend # # Create package file structure in build root @@ -130,6 +141,11 @@ cat $(BUILDROOT)/usr/sbin/etch.tmp | awk '/unshift.*__FILE__/ {print "$$:.unshift \"/opt/csw/lib/ruby/site_ruby/1.8\"\n" $$0; next}; {print}' > $(BUILDROOT)/usr/sbin/etch rm $(BUILDROOT)/usr/sbin/etch.tmp chmod 555 $(BUILDROOT)/usr/sbin/etch + cp -p etch_to_trunk $(BUILDROOT)/usr/sbin + mv $(BUILDROOT)/usr/sbin/etch_to_trunk $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp + cat $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp | sed 's,/usr/bin/ruby,/opt/csw/bin/ruby,' > $(BUILDROOT)/usr/sbin/etch_to_trunk + rm $(BUILDROOT)/usr/sbin/etch_to_trunk.tmp + chmod 555 $(BUILDROOT)/usr/sbin/etch_to_trunk mkdir -p $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 cp -p etch.rb $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8 chmod 444 $(BUILDROOT)/opt/csw/lib/ruby/site_ruby/1.8/etch.rb Modified: trunk/client/etch-client.spec =================================================================== --- trunk/client/etch-client.spec 2009-09-23 00:58:08 UTC (rev 97) +++ trunk/client/etch-client.spec 2009-09-23 01:02:24 UTC (rev 98) @@ -13,6 +13,7 @@ %files %defattr(-,root,root) /usr/sbin/etch +/usr/sbin/etch_to_trunk /usr/lib/ruby/site_ruby/1.8/etch.rb /etc/etch /usr/sbin/etch_cron_wrapper Added: trunk/client/etch_to_trunk =================================================================== --- trunk/client/etch_to_trunk (rev 0) +++ trunk/client/etch_to_trunk 2009-09-23 01:02:24 UTC (rev 98) @@ -0,0 +1,45 @@ +#!/usr/bin/ruby -w + +require 'nventory' + +@username = ENV['LOGNAME'] + +if ARGV.length == 0 + abort "Usage: #{File.basename($0)} <server1> [<server2> <server3>]" +end + +# The etch servers run in UTC +ENV['TZ'] = 'UTC' +currentheadtag = Time.now.strftime('trunk-%Y%m%d-%H00') + +# Find the requested clients +nvclient = NVentory::Client.new +results = nvclient.get_objects('nodes', {}, { 'name' => ARGV }, {}, {}) +ARGV.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" + Property changes on: trunk/client/etch_to_trunk ___________________________________________________________________ Added: svn:executable + * This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 00:58:17
|
Revision: 97 http://etch.svn.sourceforge.net/etch/?rev=97&view=rev Author: jheiss Date: 2009-09-23 00:58:08 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Use URI.join to create URIs rather than string concatenation. Add the request to the debug message that is printed before sending the request to the server. Modified Paths: -------------- trunk/client/etch.rb Modified: trunk/client/etch.rb =================================================================== --- trunk/client/etch.rb 2009-09-23 00:55:17 UTC (rev 96) +++ trunk/client/etch.rb 2009-09-23 00:58:08 UTC (rev 97) @@ -29,7 +29,7 @@ end class Etch::Client - VERSION = '1.13' + VERSION = '1.14' CONFIRM_PROCEED = 1 CONFIRM_SKIP = 2 @@ -60,9 +60,15 @@ # FIXME: Read from config file ENV['PATH'] = '/bin:/usr/bin:/sbin:/usr/sbin:/opt/csw/bin:/opt/csw/sbin' - @filesuri = URI.parse(@server + '/files') - @resultsuri = URI.parse(@server + '/results') + # 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') + @origbase = File.join(@varbase, 'orig') @historybase = File.join(@varbase, 'history') @lockbase = File.join(@varbase, 'locks') @@ -174,7 +180,7 @@ # Send request to server # - puts "Sending request to server #{@filesuri}" if (@debug) + 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) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 00:55:27
|
Revision: 96 http://etch.svn.sourceforge.net/etch/?rev=96&view=rev Author: jheiss Date: 2009-09-23 00:55:17 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Upgrade from Rails 2.1.1 to Rails 2.3.4 Modified Paths: -------------- trunk/server/app/controllers/files_controller.rb trunk/server/config/environment.rb Added Paths: ----------- trunk/server/app/controllers/application_controller.rb Removed Paths: ------------- trunk/server/app/controllers/application.rb Deleted: trunk/server/app/controllers/application.rb =================================================================== --- trunk/server/app/controllers/application.rb 2009-09-23 00:54:12 UTC (rev 95) +++ trunk/server/app/controllers/application.rb 2009-09-23 00:55:17 UTC (rev 96) @@ -1,68 +0,0 @@ -require 'etchserver' - -# Filters added to this controller apply to all controllers in the application. -# Likewise, all the methods added will be available for all controllers. - -class ApplicationController < ActionController::Base - helper :all # include all helpers, all the time - - # See ActionController::Base for details - # Uncomment this to filter the contents of submitted sensitive data parameters - # from your application log (in this case, all fields with names like "password"). - # filter_parameter_logging :password - - # Turn on the exception_notification plugin - # See environment.rb for the email address(s) to which exceptions are mailed - include ExceptionNotifiable - - # Pick a unique cookie name to distinguish our session data from others' - session :session_key => '_etch_session_id' - - # Verify that any changes are signed if the administrator has - # enabled authentication - before_filter :authenticate, :only => [:create, :update, :destroy] - - # This authentication system is targeted at etch clients. There should be - # an alternate authentication mechanism targeted at humans so that humans - # can interact with this service when authentication is enabled. - def authenticate - if Etch::Server.auth_enabled? - if request.headers['Authorization'] && - request.headers['Authorization'] =~ /^EtchSignature / - signature = request.headers['Authorization'].sub(/^EtchSignature /, '') - verified = false - begin - verified = Etch::Server.verify_message(request.raw_post, - signature, - params) - rescue Exception => e - logger.error e.message - logger.info e.backtrace.join("\n") if params[:debug] - response = e.message - response << e.backtrace.join("\n") if params[:debug] - render :text => response, :status => :unauthorized - end - else - logger.info "Authentication required, no authentication data found" - render :text => "Authentication required, no authentication data found", :status => :unauthorized - end - end - end - - # find and to_xml take their :include options in different formats - # find wants: - # :include => { :rack => { :datacenter_rack_assignment => :datacenter } } - # or this (which is what we use because it is easier to generate recursively) - # :include => { :rack => { :datacenter_rack_assignment => { :datacenter => {} } } } - # to_xml wants: - # :include => { :rack => { :include => { :datacenter_rack_assignment => { :include => { :datacenter => {} } } } } } - # This method takes the find format and returns the to_xml format - def convert_includes(includes) - includes.each do |key, value| - unless (value.nil? || value.blank?) - includes[key] = { :include => convert_includes(value) } - end - end - includes - end -end Copied: trunk/server/app/controllers/application_controller.rb (from rev 92, trunk/server/app/controllers/application.rb) =================================================================== --- trunk/server/app/controllers/application_controller.rb (rev 0) +++ trunk/server/app/controllers/application_controller.rb 2009-09-23 00:55:17 UTC (rev 96) @@ -0,0 +1,68 @@ +require 'etchserver' + +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + helper :all # include all helpers, all the time + + # See ActionController::Base for details + # Uncomment this to filter the contents of submitted sensitive data parameters + # from your application log (in this case, all fields with names like "password"). + # filter_parameter_logging :password + + # Turn on the exception_notification plugin + # See environment.rb for the email address(s) to which exceptions are mailed + include ExceptionNotifiable + + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_etch_session_id' + + # Verify that any changes are signed if the administrator has + # enabled authentication + before_filter :authenticate, :only => [:create, :update, :destroy] + + # This authentication system is targeted at etch clients. There should be + # an alternate authentication mechanism targeted at humans so that humans + # can interact with this service when authentication is enabled. + def authenticate + if Etch::Server.auth_enabled? + if request.headers['Authorization'] && + request.headers['Authorization'] =~ /^EtchSignature / + signature = request.headers['Authorization'].sub(/^EtchSignature /, '') + verified = false + begin + verified = Etch::Server.verify_message(request.raw_post, + signature, + params) + rescue Exception => e + logger.error e.message + logger.info e.backtrace.join("\n") if params[:debug] + response = e.message + response << e.backtrace.join("\n") if params[:debug] + render :text => response, :status => :unauthorized + end + else + logger.info "Authentication required, no authentication data found" + render :text => "Authentication required, no authentication data found", :status => :unauthorized + end + end + end + + # find and to_xml take their :include options in different formats + # find wants: + # :include => { :rack => { :datacenter_rack_assignment => :datacenter } } + # or this (which is what we use because it is easier to generate recursively) + # :include => { :rack => { :datacenter_rack_assignment => { :datacenter => {} } } } + # to_xml wants: + # :include => { :rack => { :include => { :datacenter_rack_assignment => { :include => { :datacenter => {} } } } } } + # This method takes the find format and returns the to_xml format + def convert_includes(includes) + includes.each do |key, value| + unless (value.nil? || value.blank?) + includes[key] = { :include => convert_includes(value) } + end + end + includes + end +end Modified: trunk/server/app/controllers/files_controller.rb =================================================================== --- trunk/server/app/controllers/files_controller.rb 2009-09-23 00:54:12 UTC (rev 95) +++ trunk/server/app/controllers/files_controller.rb 2009-09-23 00:55:17 UTC (rev 96) @@ -9,7 +9,12 @@ response = nil begin etchserver = Etch::Server.new(params[:facts], params[:tag], params[:debug]) - response = etchserver.generate(params[:files]) + # params[:files] is a hash of filename => hash_of_options + # The client runs the filename through CGI.escape in case it contains + # special characters. Older versions of Rails automatically decoded the + # filename, but as of Rails 2.3 we need to do it ourself. + files = params[:files].inject({}) { |h, (file, value)| h[CGI.unescape(file)] = value; h } + response = etchserver.generate(files) render :text => response rescue Exception => e logger.error e.message Modified: trunk/server/config/environment.rb =================================================================== --- trunk/server/config/environment.rb 2009-09-23 00:54:12 UTC (rev 95) +++ trunk/server/config/environment.rb 2009-09-23 00:55:17 UTC (rev 96) @@ -5,7 +5,7 @@ # ENV['RAILS_ENV'] ||= 'production' # Specifies gem version of Rails to use when vendor/rails is not present -RAILS_GEM_VERSION = '2.1.1' unless defined? RAILS_GEM_VERSION +RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION # Bootstrap the Rails environment, frameworks, and default configuration require File.join(File.dirname(__FILE__), 'boot') This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <jh...@us...> - 2009-09-23 00:54:23
|
Revision: 95 http://etch.svn.sourceforge.net/etch/?rev=95&view=rev Author: jheiss Date: 2009-09-23 00:54:12 +0000 (Wed, 23 Sep 2009) Log Message: ----------- Latest version from tpkg Modified Paths: -------------- trunk/server/lib/versiontype.rb Modified: trunk/server/lib/versiontype.rb =================================================================== --- trunk/server/lib/versiontype.rb 2009-09-23 00:53:21 UTC (rev 94) +++ trunk/server/lib/versiontype.rb 2009-09-23 00:54:12 UTC (rev 95) @@ -1,13 +1,11 @@ # This class stores numbers with multiple decimal points, a format # commonly used for version numbers. For example '2.5.1'. -require 'generator' # SyncEnumerator - class Version include Comparable def initialize(version) - @version = version + @version = version.to_s end def to_s @@ -24,16 +22,8 @@ fields[0] = '0' end end - # Pad with zeros so that '1' == '1.0', etc. - if ourfields.length != otherfields.length - larger = [ourfields.length, otherfields.length].max - # For the longer number this depends on something like (3...1).each - # doing nothing. That currently works, but is not documented behavior. - (ourfields.length...larger).each { ourfields << '0' } - (otherfields.length...larger).each { otherfields << '0' } - end - ourfields, otherfields = convert_and_split(ourfields, otherfields) + convert_and_split!(ourfields, otherfields) # Array conveniently implements <=> ourfields <=> otherfields @@ -42,18 +32,27 @@ # Private methods below private + # Loops over two arrays in parallel. If the entry at a given # position in both arrays is numeric it is converted from a string to # a number, or if either entry is a mixture of numeric and # non-numeric characters then both are split into an array consisting # of the numeric and non-numeric components. - def convert_and_split(fields1, fields2) + def convert_and_split!(fields0, fields1) + # Pad the shorter of two arrays with zeros so that both arrays are + # the same length. This ensures that '1' == '1.0', etc. + if fields0.length != fields1.length + larger = [fields0.length, fields1.length].max + # For the longer number this depends on something like (3...1).each + # doing nothing. That currently works, but is not documented behavior. + (fields0.length...larger).each { fields0 << '0' } + (fields1.length...larger).each { fields1 << '0' } + end + # Squish both arrays together bothfields = [] - sync = SyncEnumerator.new(fields1, fields2) - sync.each do |field1, field2| - bothfields << [field1, field2] - end + (0...fields0.length).each { |i| bothfields << [fields0[i], fields1[i]] } + bothfields.map! do |fields| # Convert fields of all digits from string to number to get a numeric # rather than string comparison. This ensures that 5.9 < 5.10 @@ -72,14 +71,14 @@ fields.map! { |f| f.scan(/\d+|\D+/) } # Pass back through this method to convert the numeric # entries to numbers - fields = convert_and_split(fields[0], fields[1]) + convert_and_split!(fields[0], fields[1]) end end fields end # Unsquish back to separate arrays - fields1 = bothfields.collect { |fields| fields[0] } - fields2 = bothfields.collect { |fields| fields[1] } - [fields1, fields2] + fields0.clear + fields1.clear + bothfields.each { |fields| fields0 << fields[0]; fields1 << fields[1] } end end This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |