Initial commit. master
authorMichael Orlitzky <michael@orlitzky.com>
Tue, 18 Aug 2009 20:07:18 +0000 (16:07 -0400)
committerMichael Orlitzky <michael@orlitzky.com>
Tue, 18 Aug 2009 20:07:18 +0000 (16:07 -0400)
bin/janitize.rb [new file with mode: 0644]
makefile [new file with mode: 0644]
src/apache_conf_file.rb [new file with mode: 0644]
src/janitor.rb [new file with mode: 0644]
test/apache_conf_file_test.rb [new file with mode: 0644]
test/fixtures/example.com-boring.conf [new file with mode: 0644]
test/fixtures/example.com-different.conf [new file with mode: 0644]
test/fixtures/example.com-multi.conf [new file with mode: 0644]
test/fixtures/example.com-typical.conf [new file with mode: 0644]
test/janitor_test.rb [new file with mode: 0644]
test/test_suite.rb [new file with mode: 0644]

diff --git a/bin/janitize.rb b/bin/janitize.rb
new file mode 100644 (file)
index 0000000..4e8c039
--- /dev/null
@@ -0,0 +1,56 @@
+# Actually run the thing.
+
+require 'src/janitor'
+require 'optparse'
+
+# Set up the default options
+options = { :apache_vhost_directory => '/etc/apache2/vhosts.d',
+            :maximum_file_age       => 7 }
+
+# And parse any that were given on the command line.
+OptionParser.new do |opts|
+  opts.banner = "Usage: #{$0} [options]"
+
+  opts.on('-a', '--apache_vhosts_directory VHOSTS_DIR',
+          'Apache vhost conf file directory') do |vhost_dir|
+    options[:apache_vhost_directory] = vhost_dir
+  end
+  
+  opts.on('-m', '--maximum_file_age MAX_DAYS_OLD', Integer,
+          'Maximum file age (in days)') do |max_age|
+    options[:maximum_file_age] = max_age
+  end
+  
+  opts.on_tail("-h", "--help", "Show this message") do
+    puts opts
+    exit
+  end
+  
+end.parse!
+
+
+# Print an informational header every time the program is run.
+puts "Command line: #{$0 + ' ' + ARGV.join(' ')}"
+
+current_time = Time.now()
+if current_time.respond_to?(:iso8601)
+  # Somehow this method is missing on some machines.
+  puts "Time: #{current_time.iso8601}"
+else
+  # Fall back to whatever this looks like.
+  puts "Time: #{current_time}"
+end
+
+puts "Apache vhost directory: #{options[:apache_vhost_directory]}"
+puts "Max File Age: #{options[:maximum_file_age]}"
+
+j = Janitor.new()
+j.apache_vhosts_directory = options[:apache_vhost_directory]
+
+temp_dirs = j.get_temporary_directories()
+
+puts "Temporary directories: #{temp_dirs.join(', ')}\n\n"
+
+temp_dirs.each do |dir|
+  j.clean_directory(dir, options[:maximum_file_age])
+end
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..e0eb009
--- /dev/null
+++ b/makefile
@@ -0,0 +1,4 @@
+.PHONY : test
+
+test:
+       ruby test/test_suite.rb
diff --git a/src/apache_conf_file.rb b/src/apache_conf_file.rb
new file mode 100644 (file)
index 0000000..72b1f95
--- /dev/null
@@ -0,0 +1,69 @@
+class ApacheConfFile
+
+  def initialize(conf_file_path)
+    if conf_file_path.nil? or not File.file?(conf_file_path)
+      raise(ArgumentError.new('How about passing in a real file?'))
+    end
+    
+    # Read each line of the conf file into an array.
+    # Apache config directives are all one-line, right?
+    @file_lines = []
+    
+    File::open(conf_file_path) do |f|
+      @file_lines = f.readlines()
+    end
+
+    if @file_lines.length < 1
+      raise(ArgumentError.new('Your file sucks.'))
+    end
+  end
+
+
+  
+  def get_php_admin_values(directive_name)
+    # First, we have to regex-ize the directive name,
+    # since they can contain periods (at the least!).
+    directive_name.sub!('.', '\.')
+
+    # Our return variable, an array of matching directive values.
+    values = []
+
+    # Loop through each line in the conf file looking a
+    # matching pattern.
+    @file_lines.each do |line|
+      matches = line.match(/php_admin_value[[:space:]]+#{directive_name}[[:space:]](.*)$/)
+      
+      if not matches.nil?
+        # If there's a match, there should be only one
+        # (in addition to [0], the entire matched string).
+        if matches.length > 2
+          raise(StandardError.new('Matched the Regex more than once?'))
+        end
+
+        # If there's only one, add it to our list.
+        # These are probably likely to have some extra
+        # whitespace around them one way or another.
+        values << matches[1].strip
+      end
+    end
+
+    return values
+  end
+
+  
+  def get_php_temporary_directory_list(remove_duplicates = true)
+    # Return a list of all PHP "temporary" directories.
+    # I.e. upload_tmp_dir, session.save_path
+    temp_dirs = []
+    temp_dirs += self.get_php_admin_values('upload_tmp_dir')
+    temp_dirs += self.get_php_admin_values('session.save_path')
+
+    # We expect these to have been strip()ed already
+    if remove_duplicates
+      return temp_dirs.uniq
+    else
+      return temp_dirs
+    end
+  end
+  
+end
diff --git a/src/janitor.rb b/src/janitor.rb
new file mode 100644 (file)
index 0000000..24217ad
--- /dev/null
@@ -0,0 +1,77 @@
+require('src/apache_conf_file')
+
+class Janitor
+
+  attr_accessor :apache_vhosts_directory
+  
+  SECONDS_PER_MINUTE = 60
+  MINUTES_PER_HOUR = 60
+  HOURS_PER_DAY = 24
+  SECONDS_PER_DAY = SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY
+  
+
+  def initialize()
+    @apache_vhosts_directory = nil
+  end
+
+  
+  protected;
+
+  def seconds_to_whole_days(seconds)
+    # Given some number of seconds, computer how many
+    # entire days are contained in that number of seconds.
+    # Fractions of days don't count!
+
+    if (seconds < 0)
+      raise(ArgumentException.new('Negative seconds?'))
+    end
+
+    return (seconds / SECONDS_PER_DAY).floor
+  end
+  
+  public;
+  
+  # Get a list of all the temporary directories that
+  # we'd like to clean out.
+  def get_temporary_directories(remove_duplicates = true)
+    temp_dirs = []
+
+    # Get the PHP temp dirs if we've been given a vhosts path
+    if !@apache_vhosts_directory.nil? && File.directory?(@apache_vhosts_directory)
+      Dir.glob(@apache_vhosts_directory + '/*.conf').each do |conf_file|
+        acf = ApacheConfFile.new(conf_file)
+        
+        # Don't remove the duplicates here since we have
+        # to do it afterwards anyway.
+        temp_dirs += acf.get_php_temporary_directory_list(false)
+      end
+    end
+
+    # We expect these to have been strip()ed already
+    if remove_duplicates
+      return temp_dirs.uniq
+    else
+      return temp_dirs
+    end
+  end
+
+
+  def clean_directory(directory, max_age, file_pattern = '*')
+    glob_pattern = File.join(directory, file_pattern)
+
+    Dir.glob(glob_pattern).each do |f|
+      # Obviously, we should bail if it's not a file.
+      next if not File.file?(f)
+
+      # Calculate how old it is
+      days_old = seconds_to_whole_days(Time.now - File.mtime(f))
+
+      # And then delete the file (with a notice) if it's too old.
+      if days_old > max_age
+        puts "Deleting file: #{f} (#{days_old} days old)"
+        File.delete(f)
+      end
+    end
+    
+  end
+end
diff --git a/test/apache_conf_file_test.rb b/test/apache_conf_file_test.rb
new file mode 100644 (file)
index 0000000..c3af6ae
--- /dev/null
@@ -0,0 +1,82 @@
+require('src/apache_conf_file')
+require('test/unit')
+
+class ApacheConfFileTest < Test::Unit::TestCase
+  
+  def test_bad_conf_file_path_raises_error
+    assert_raise(ArgumentError) do
+      acf = ApacheConfFile.new(nil)
+    end
+
+    assert_raise(ArgumentError) do
+      acf = ApacheConfFile.new('/does/not/exist')
+    end
+
+    assert_raise(ArgumentError) do
+      # This raises a "different" error than the one above,
+      # but quit giving me empty files!
+      acf = ApacheConfFile.new('test/fixtures/empty_file.txt')
+    end
+  end
+
+  
+  def test_good_conf_file_loads
+    acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf')
+    # That's all..
+  end
+
+
+  def test_conf_file_with_no_directives
+    acf = ApacheConfFile.new('test/fixtures/example.com-boring.conf')
+    temp_files = acf.get_php_temporary_directory_list()
+    assert_kind_of(Array, temp_files)
+    assert_equal(0, temp_files.length)
+  end
+
+
+  def test_get_php_temporary_dirs_non_unique
+    acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf')    
+    # There should be two (identical) directories.
+    temp_files = acf.get_php_temporary_directory_list(false)
+    assert_equal(2, temp_files.length)
+    assert_equal(temp_files[0], temp_files[1])
+  end
+
+  
+  def test_get_php_temporary_dirs_unique
+    acf = ApacheConfFile.new('test/fixtures/example.com-typical.conf')    
+    # There should be just one directory, since the
+    # upload_tmp and session.save paths are identical.
+    temp_files = acf.get_php_temporary_directory_list()
+    assert_equal(1, temp_files.length)
+    assert_not_equal(0, temp_files[0].length)
+  end
+
+
+  def test_get_php_temporary_dirs_different_unique
+    acf = ApacheConfFile.new('test/fixtures/example.com-different.conf')    
+    temp_files = acf.get_php_temporary_directory_list()
+    assert_equal(2, temp_files.length)
+    assert_not_equal(temp_files[0], temp_files[1])
+  end
+
+
+  def test_get_php_temporary_dirs_multi_unique
+    # Two different vhosts (one SSL), both configured
+    # with the same two (different) tmp directories.
+    acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf')    
+    temp_files = acf.get_php_temporary_directory_list()
+    assert_equal(2, temp_files.length)
+    assert_not_equal(temp_files[0], temp_files[1])
+  end
+
+
+  def test_get_php_temporary_dirs_multi_non_unique
+    # Two different vhosts (one SSL), both configured
+    # with the same two (different) tmp directories.
+    acf = ApacheConfFile.new('test/fixtures/example.com-multi.conf')    
+    temp_files = acf.get_php_temporary_directory_list(false)
+    assert_equal(4, temp_files.length)
+  end
+  
+end
diff --git a/test/fixtures/example.com-boring.conf b/test/fixtures/example.com-boring.conf
new file mode 100644 (file)
index 0000000..dcdb4bb
--- /dev/null
@@ -0,0 +1,5 @@
+<VirtualHost 127.0.0.1:80>
+    ServerName www.example.com
+    ServerAlias example.com
+    DocumentRoot "/var/www/example.com/www/public"
+</VirtualHost>
diff --git a/test/fixtures/example.com-different.conf b/test/fixtures/example.com-different.conf
new file mode 100644 (file)
index 0000000..7705693
--- /dev/null
@@ -0,0 +1,27 @@
+<VirtualHost 127.0.0.1:80>
+    ServerName www.example.com
+    ServerAlias example.com
+    DocumentRoot "/var/www/example.com/www/public"
+
+    php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+    php_admin_value upload_tmp_dir /tmp
+    php_admin_value session.save_path /var/www/example.com/www/tmp
+    
+    LogLevel warn
+    ErrorLog /var/log/apache2/example.com/www/error.log
+    CustomLog /var/log/apache2/example.com/www/access.log combined
+
+    <Directory "/var/www/example.com/www/public">
+        Options IncludesNoExec
+        AllowOverride None
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+    DirectoryIndex default.shtml
+
+    Include /etc/apache2/vhosts.d/include/php.conf
+
+    DirectoryIndex default.php index.php
+
+</VirtualHost>
diff --git a/test/fixtures/example.com-multi.conf b/test/fixtures/example.com-multi.conf
new file mode 100644 (file)
index 0000000..f907b8d
--- /dev/null
@@ -0,0 +1,55 @@
+<VirtualHost 127.0.0.1:80>
+    ServerName www.example.com
+    ServerAlias example.com
+    DocumentRoot "/var/www/example.com/www/public"
+
+    php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+    php_admin_value upload_tmp_dir /tmp
+    php_admin_value session.save_path /var/www/example.com/www/tmp
+    
+    LogLevel warn
+    ErrorLog /var/log/apache2/example.com/www/error.log
+    CustomLog /var/log/apache2/example.com/www/access.log combined
+
+    <Directory "/var/www/example.com/www/public">
+        Options IncludesNoExec
+        AllowOverride None
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+    DirectoryIndex default.shtml
+
+    Include /etc/apache2/vhosts.d/include/php.conf
+
+    DirectoryIndex default.php index.php
+
+</VirtualHost>
+
+
+<VirtualHost 127.0.0.1:443>
+    ServerName secure.example.com
+    DocumentRoot "/var/www/example.com/www/secure"
+
+    php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+    php_admin_value upload_tmp_dir /tmp
+    php_admin_value session.save_path /var/www/example.com/www/tmp
+    
+    LogLevel warn
+    ErrorLog /var/log/apache2/example.com/www/error.log
+    CustomLog /var/log/apache2/example.com/www/access.log combined
+
+    <Directory "/var/www/example.com/www/secure">
+        Options IncludesNoExec
+        AllowOverride None
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+    DirectoryIndex default.shtml
+
+    Include /etc/apache2/vhosts.d/include/php.conf
+
+    DirectoryIndex default.php index.php
+
+</VirtualHost>
diff --git a/test/fixtures/example.com-typical.conf b/test/fixtures/example.com-typical.conf
new file mode 100644 (file)
index 0000000..d6e76c3
--- /dev/null
@@ -0,0 +1,27 @@
+<VirtualHost 127.0.0.1:80>
+    ServerName www.example.com
+    ServerAlias example.com
+    DocumentRoot "/var/www/example.com/www/public"
+
+    php_admin_value open_basedir /var/www/example.com/www/:/usr/share/php/
+    php_admin_value upload_tmp_dir /var/www/example.com/www/tmp
+    php_admin_value session.save_path /var/www/example.com/www/tmp
+    
+    LogLevel warn
+    ErrorLog /var/log/apache2/example.com/www/error.log
+    CustomLog /var/log/apache2/example.com/www/access.log combined
+
+    <Directory "/var/www/example.com/www/public">
+        Options IncludesNoExec
+        AllowOverride None
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+    DirectoryIndex default.shtml
+
+    Include /etc/apache2/vhosts.d/include/php.conf
+
+    DirectoryIndex default.php index.php
+
+</VirtualHost>
diff --git a/test/janitor_test.rb b/test/janitor_test.rb
new file mode 100644 (file)
index 0000000..f3831ac
--- /dev/null
@@ -0,0 +1,57 @@
+require('src/janitor')
+require('test/unit')
+
+class JanitorTest < Test::Unit::TestCase
+  
+  def test_new_files_dont_get_cleaned
+    j = Janitor.new()
+    
+    File.open('test/fixtures/dummy/dummy-1.txt', 'w') do |f|
+      f << 'dfhdsgfhsdywerhdf'
+    end
+
+    File.open('test/fixtures/dummy/dummy-2.txt', 'w') do |f|
+      f << 'gjkh983yfhufg'
+    end
+
+    assert(true, File.file?('test/fixtures/dummy/dummy-1.txt'))
+    assert(true, File.file?('test/fixtures/dummy/dummy-2.txt'))
+
+    j.clean_directory('test/fixtures/dummy', 1)
+
+    assert(true, File.file?('test/fixtures/dummy/dummy-1.txt'))
+    assert(true, File.file?('test/fixtures/dummy/dummy-2.txt'))
+
+    File.delete('test/fixtures/dummy/dummy-1.txt')
+    File.delete('test/fixtures/dummy/dummy-2.txt')
+  end
+
+  
+  def test_get_temporary_directories_unique
+    j = Janitor.new()
+    j.apache_vhosts_directory = 'test/fixtures/'
+    tmp_dirs = j.get_temporary_directories()
+    assert(true, tmp_dirs.include?('/tmp'))
+    assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp'))
+    assert_equal(2, tmp_dirs.length)
+  end
+
+
+  def test_get_temporary_directories_non_unique
+    j = Janitor.new()
+    j.apache_vhosts_directory = 'test/fixtures/'
+    tmp_dirs = j.get_temporary_directories(false)
+    assert(true, tmp_dirs.include?('/tmp'))
+    assert(true, tmp_dirs.include?('/var/www/example.com/www/tmp'))
+    assert_equal(8, tmp_dirs.length)
+  end
+
+
+  def test_non_directory_returns_no_paths
+    j = Janitor.new()
+    j.apache_vhosts_directory = 'test/fixtures/WHARRGHARBL'
+    tmp_dirs = j.get_temporary_directories()
+    assert_equal(0, tmp_dirs.length)
+  end
+  
+end
diff --git a/test/test_suite.rb b/test/test_suite.rb
new file mode 100644 (file)
index 0000000..af3eb2b
--- /dev/null
@@ -0,0 +1,2 @@
+require 'test/apache_conf_file_test'
+require 'test/janitor_test'