From faa6654b3fce41b47a7619940d67a00035f1997f Mon Sep 17 00:00:00 2001 From: John Bintz Date: Mon, 10 May 2010 15:57:34 -0400 Subject: [PATCH] lots of documentation --- .gitignore | 2 + CHANGELOG | 1 + Rakefile | 13 ++++++ lib/apache/config.rb | 92 +++++++++++++++++++++++++++++++++++++-- lib/apache/directory.rb | 9 ++++ lib/apache/logging.rb | 16 +++++++ lib/apache/master.rb | 27 ++++++++++++ lib/apache/modules.rb | 14 ++++++ lib/apache/mpm_prefork.rb | 25 ++++++++--- lib/apache/performance.rb | 7 +++ lib/apache/permissions.rb | 26 +++++++++-- lib/apache/quoteize.rb | 2 + lib/apache/rewrites.rb | 58 +++++++++++++++++++++--- 13 files changed, 275 insertions(+), 17 deletions(-) create mode 100644 CHANGELOG diff --git a/.gitignore b/.gitignore index 1a53548..0c4ab5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .loadpath .project coverage/* +docs/* + diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..71af18c --- /dev/null +++ b/CHANGELOG @@ -0,0 +1 @@ +v0.1. Initial release, support for many of the Apache config basics. diff --git a/Rakefile b/Rakefile index c795371..b7383e8 100644 --- a/Rakefile +++ b/Rakefile @@ -2,6 +2,12 @@ $LOAD_PATH << 'lib' require 'apache' require 'spec/rake/spectask' +require 'sdoc' +require 'sdoc_helpers/markdown' +require 'echoe' +require 'pp' + +pp RDoc::TopLevel namespace :apache do desc "Generate the configs" @@ -21,3 +27,10 @@ namespace :spec do t.spec_opts = ['-b'] end end + +Rake::RDocTask.new do |rdoc| + rdoc.template = 'direct' + rdoc.rdoc_files.add('lib') + rdoc.main = "lib/apache/config.rb" + rdoc.rdoc_dir = 'docs' +end diff --git a/lib/apache/config.rb b/lib/apache/config.rb index 722eea5..fcfa800 100644 --- a/lib/apache/config.rb +++ b/lib/apache/config.rb @@ -3,6 +3,53 @@ require 'fileutils' Dir[File.join(File.dirname(__FILE__), '*.rb')].each { |f| require f } module Apache + # The core class of Apache Config Generator. + # + # Configuration is built by calling either build or build_if: + # + # Ex: Build a config file regardless of current environment: + # + # Apache::Config.build('my-config.conf') do + # document_root '/my/document/root' + # ... + # end + # + # Ex: Build a config file only if you're in the development environment: + # + # Apache::Config.build_if('my-config.conf', :development) do + # document_root '/my/document/root' + # ... + # end + # + # By default, methods called within the block are NerdCapsed to match the Apache config directive format: + # + # document_root #=> DocumentRoot + # options #=> Options + # allow_override #=> AllowOverride + # + # Parameters passed into the methods are quoted if they're Strings or Integers, and not quoted if they're Symbols: + # + # document_root '/my/document/root' #=> DocumentRoot "/my/document/root" + # document_root '/my/document/root'.to_sym #=> DocumentRoot /my/document/root + # accept_path_info :off #=> AcceptPathInfo off + # + # Suffixing the method name with an exclamation point turns off quoting for all parameters: + # + # document_root! '/my/document/root' #=> DocumentRoot /my/document/root + # + # Block-level directives work the same as the top-level build method: + # + # directory '/my/site' do + # allow_from_all + # satisfy :any + # end + # + # Directives that require a regular expression take a Regexp: + # + # location_match %r{^/my/site} do + # set_env 'this_is_my_site', 'yes' + # end + class Config class << self attr_accessor :line_indent, :rotate_logs_path @@ -69,6 +116,10 @@ module Apache # Apachify a string # # Split the provided name on underscores and capitalize the individual parts + # Certain character strings are capitalized to match Apache directive names: + # * Cgi => CGI + # * Ssl => SSL + # * Ldap => LDAP def apachify(name) case name when String, Symbol @@ -79,6 +130,8 @@ module Apache end # Handle options that aren't specially handled + # + # Method names are NerdCapsed and paramters are quoted, unless the method ends with ! def method_missing(method, *args) if method.to_s[-1..-1] == "!" method = method.to_s[0..-2].to_sym @@ -90,6 +143,11 @@ module Apache end # Handle creating block methods + # + # Methods created this way are: + # * virtual_host + # * location + # * files def block_methods(*methods) methods.each do |method| self.class.class_eval <<-EOT @@ -100,23 +158,44 @@ module Apache end end + # If the given module is loaded, process the directives within. + # + # The provided module name is converted into Apache module name format: + # if_module(:php5) do #=> def if_module(mod, &block) blockify(apachify('if_module'), "#{mod}_module".to_sym, &block) end + # Create a directory block, checking to see if the source directory exists. def directory(dir, &block) directory? dir blockify(apachify('directory'), dir, &block) end + # Create a LocationMatch block with the provided Regexp: + # location_match %r{^/my/location/[a-z0-9]+\.html} do #=> def location_match(regexp, &block) blockify(apachify('location_match'), regexp.source, &block) end - def if_environment(env, &block) - self.instance_eval(&block) if APACHE_ENV == env + # Create a FilesMatch block with the provied Regexp: + # files_match %r{\.html$} do #=> FilesMatch "\.html$"> + def files_match(regexp, &block) + blockify(apachify('files_match'), regexp.source, &block) end + # Only execute the provided block if APACHE_ENV matches one of the provided enviroment symbols: + # if_environment(:production) do + def if_environment(*env, &block) + self.instance_eval(&block) if env.include?(APACHE_ENV) + end + + # Blockify the second parameter of a block + # + # The name is processed differently based on input object type: + # * String - the name is quoteized + # * Array - all of the array members are quoteized + # * Symbol - the name is to_s def blockify_name(name) case name when String @@ -139,7 +218,14 @@ module Apache self << "" end + # Build a string that invokes Apache's rotatelogs command def rotatelogs(path, time) + begin + time = time.to_i + rescue + raise "Time should be an integer: #{path} #{time}" + end + "|#{@rotate_logs_path} #{path} #{time}" end @@ -157,6 +243,6 @@ module Apache end end - block_methods :virtual_host, :files_match, :location, :files + block_methods :virtual_host, :location, :files end end diff --git a/lib/apache/directory.rb b/lib/apache/directory.rb index 7ef36c3..a20c648 100644 --- a/lib/apache/directory.rb +++ b/lib/apache/directory.rb @@ -1,9 +1,18 @@ module Apache + # Methods to handle directory settings module Directories + # Create an Options directive + # + # The options passed into this method are Apachified: + # options :exec_cgi, :follow_sym_links #=> Options ExecCGI FollowSymLinks def options(*opt) create_options_list('Options', *opt) end + # Create an IndexOptions directive + # + # The options passed into this method are Apachified: + # index_options :fancy_indexing, :suppress_description #=> IndexOptions FancyIndexing SuppressDescription def index_options(*opt) create_options_list('IndexOptions', *opt) end diff --git a/lib/apache/logging.rb b/lib/apache/logging.rb index 19daf27..a442e8a 100644 --- a/lib/apache/logging.rb +++ b/lib/apache/logging.rb @@ -1,4 +1,20 @@ module Apache + # Methods to handle logging configuration are defined here. + # + # For each of the four main log types (Custom, Error, Script, and Rewrite), the following two methods are created: + # + # * (type)_log: A non-rotated log file + # * rotate_(type)_log: A rotated log file + # + # Non-rotated logs work as such: + # custom_log "/path/to/log/file.log", :common #=> CustomLog "/path/to/log/file.log" common + # + # Rotated logs work as such: + # rotate_custom_log "/path/to/log/file-%Y%m%d.log", 86400, :common + # #=> CustomLog "|/path/to/rotatelogs /path/to/log/file-%Y%m%d.jpg 86400" common + # + # Both variations check to make sure the log file diretory exists during generation. + # The rotate_ variations need @rotate_logs_path set to work. module Logging [ :custom, :error, :script, :rewrite ].each do |type| class_eval <<-EOT diff --git a/lib/apache/master.rb b/lib/apache/master.rb index 04dc31a..2024ff7 100644 --- a/lib/apache/master.rb +++ b/lib/apache/master.rb @@ -1,20 +1,32 @@ module Apache + # Options that aren't specific to a particular purpose go here. Once enough like methods for a + # particular purpose exist, break them out into a separate module. module Master + # Build a module list. + # Wraps around Modules.build def modules(*modules, &block) @config += Modules.build(*modules, &block) end + # Add a User/Group block + # runner('www', 'www-data') #=> + # User www + # Group www-data def runner(user, group = nil) user! user group! group if group end + # Enable Passenger on this server + # + # This assumes that Passenger was installed via the gem. This may or may not work for you, but it works for me. def passenger(ruby_root, ruby_version, passenger_version) load_module 'passenger_module', "#{ruby_root}/lib/ruby/gems/#{ruby_version}/gems/passenger-#{passenger_version}/ext/apache2/mod_passenger.so" passenger_root "#{ruby_root}/lib/ruby/gems/#{ruby_version}/gems/passenger-#{passenger_version}" passenger_ruby "#{ruby_root}/bin/ruby" end + # Enable gzip compression server-wide on pretty much everything that can be gzip compressed def enable_gzip! directory '/' do add_output_filter_by_type! :DEFLATE, 'text/html', 'text/plain', 'text/css', 'text/javascript', 'application/javascript' @@ -24,10 +36,12 @@ module Apache end end + # Set the TCP timeout. Defined here to get around various other timeout methods. def timeout(t) self << "Timeout #{t}" end + # Add a comment to the Apache config. Can pass in either a String or Array of comment lines. def comment(c) out = [ '' ] case c @@ -40,6 +54,7 @@ module Apache self + out.collect { |line| "# #{line.strip}".strip } end + # Create a ScriptAlias, checking to make sure the filesystem path exists. def script_alias(uri, path) directory? path self << %{ScriptAlias #{quoteize(uri, path) * ' '}} @@ -47,6 +62,9 @@ module Apache alias :script_alias! :script_alias + # Add a MIME type, potentially also adding handlers and encodings + # add_type! 'text/html', '.shtml', :handler => 'server-parsed' + # add_type! 'text/html', '.gz', :encoding => 'gzip' def add_type!(mime, extension, options = {}) self << "AddType #{mime} #{extension}" options.each do |type, value| @@ -54,14 +72,23 @@ module Apache end end + # Include other config files or directories. + # Used to get around reserved Ruby keyword. def apache_include(*opts) self << "Include #{opts * " "}" end + # Alias a URL to a directory in the filesystem. + # Used to get around reserved Ruby keyword. def apache_alias(*opts) self << "Alias #{quoteize(*opts) * " "}" end + # Set multiple headers to be delivered for a particular section + # set_header 'Content-type' => 'application/octet-stream', + # 'Content-disposition' => [ 'attachment', 'env=only-for-downloads' ] #=> + # Header set "Content-type" "application/octet-stream" + # Header set "Content-dispoaition" "attachment" env=only-for-downloads def set_header(hash) hash.each do |key, value| output = "Header set #{quoteize(key)}" diff --git a/lib/apache/modules.rb b/lib/apache/modules.rb index 75c8fab..6d31ae0 100644 --- a/lib/apache/modules.rb +++ b/lib/apache/modules.rb @@ -1,16 +1,29 @@ require 'apache/quoteize' module Apache + # Create lists of modules to load in the Apache 2.2 style (with LoadModule only) class Modules class << self include Apache::Quoteize attr_accessor :modules + # Reset the list of modules to output def reset! @modules = [] end + # Build a block of LoadModule commands + # + # Apache::Modules.build(:expires, :headers) do + # funky "/path/to/funky/module.so" + # end + # + # becomes: + # + # LoadModule "expires_module" "modules/mod_expires.so" + # LoadModule "headers_module" "modules/mod_headers.so" + # LoadModule "funky_module" "/path/to/funky/module.so" def build(*modules, &block) reset! @@ -20,6 +33,7 @@ module Apache [ '' ] + @modules + [ '' ] end + # The method name becomes the module core name def method_missing(method, *args) module_name = "#{method}_module" module_path = args[0] || "modules/mod_#{method}.so" diff --git a/lib/apache/mpm_prefork.rb b/lib/apache/mpm_prefork.rb index 98f5756..2003a56 100644 --- a/lib/apache/mpm_prefork.rb +++ b/lib/apache/mpm_prefork.rb @@ -2,16 +2,29 @@ module Apache module MPM # Set up the Prefork MPM # - # The block you pass in to this can take the following methods: - # * start(num) - StartServers - # * spares(min, max) - Min and MaxSpareServers - # * limit(num) - ServerLimit - # * clients(num) - MaxClients - # * max_requests(num) - MaxRequestsPerChild + # prefork_config do + # start 5 + # spares 5, 20 + # limit 100 + # clients 100 + # max_requests 1000 + # end + # + # becomes: + # + # StartServers 5 + # MinSpareServers 5 + # MaxSpareServers 20 + # ServerLimit 100 + # MaxClients 100 + # MaxRequestsPerChild 1000 + # def prefork_config(&block) self + Apache::MPM::Prefork.build(&block) end + # Builder for Prefork MPM + # See Apache::MPM::prefork_config for usage. class Prefork class << self def build(&block) diff --git a/lib/apache/performance.rb b/lib/apache/performance.rb index a8ed98a..3179578 100644 --- a/lib/apache/performance.rb +++ b/lib/apache/performance.rb @@ -1,5 +1,12 @@ module Apache + # Options to adjust server performance beyond MPM settings module Performance + # Activate KeepAlive, optionally tweaking max requests and timeout + # + # activate_keepalive :requests => 100, :timeout => 5 #=> + # KeepAlive on + # MaxKeepAliveRequests 100 + # KeepAliveTimeout 5 def activate_keepalive(options) self << "KeepAlive On" options.each do |option, value| diff --git a/lib/apache/permissions.rb b/lib/apache/permissions.rb index 43f4447..b183c2a 100644 --- a/lib/apache/permissions.rb +++ b/lib/apache/permissions.rb @@ -1,5 +1,7 @@ module Apache + # Configure server access permissions module Permissions + # Shortcut for denying all access to a block def deny_from_all order :deny, :allow deny :from_all @@ -7,6 +9,7 @@ module Apache alias :deny_from_all! :deny_from_all + # Shortcut for allowing all access to a block def allow_from_all order :allow, :deny allow :from_all @@ -14,14 +17,23 @@ module Apache alias :allow_from_all! :allow_from_all + # Define IP block restrictions + # + # allow_from '127.0.0.1' #=> Allow from "127.0.0.1" def allow_from(*where) self << "Allow from #{quoteize(*where) * " "}" end + # Specify default access order + # + # order :allow, :deny #=> Order allow,deny def order(*args) self << "Order #{args * ','}" end + alias :order! :order + + # Set up default restrictive permissions def default_restrictive! directory '/' do options :follow_sym_links @@ -30,15 +42,20 @@ module Apache end end + # Block all .ht* files def no_htfiles! - files_match '^\.ht' do + files_match %{^\.ht} do deny_from_all satisfy :all end end - alias :order! :order - + # Set up basic authentication + # + # Check to make sure the defined users_file exists + # + # basic_authentication "My secret", '/my.users', 'valid-user' => true + # basic_authentication "My other secret", '/my.users', :user => [ :john ] def basic_authentication(zone, users_file, requires = {}) exist? users_file auth_type :basic @@ -51,6 +68,7 @@ module Apache alias :basic_authentication! :basic_authentication + # Set up LDAP authentication def ldap_authentication(zone, url, requires = {}) auth_type :basic auth_name zone @@ -64,6 +82,8 @@ module Apache alias :ldap_authentication! :ldap_authentication + # Create an Apache require directive. + # Used to get around Ruby reserved word. def apache_require(*opts) self << "Require #{opts * " "}" end diff --git a/lib/apache/quoteize.rb b/lib/apache/quoteize.rb index 21b0f65..c94c473 100644 --- a/lib/apache/quoteize.rb +++ b/lib/apache/quoteize.rb @@ -1,5 +1,7 @@ module Apache + # Add quotes around parameters as needed module Quoteize + # Add quotes around most parameters, and don't add quotes around Symbols def quoteize(*args) args.collect do |arg| case arg diff --git a/lib/apache/rewrites.rb b/lib/apache/rewrites.rb index fee6c58..495db63 100644 --- a/lib/apache/rewrites.rb +++ b/lib/apache/rewrites.rb @@ -1,5 +1,11 @@ module Apache + # Handle the creation of RewriteRules, RewriteConds, Redirects, and RedirectMatches module Rewrites + # Enable the rewrite engine, optionally setting the logging level + # + # enable_rewrite_engine :log_level => 1 #=> + # RewriteEngine on + # RewriteLogLevel 1 def enable_rewrite_engine(options) self << '' rewrite_engine! :on @@ -12,24 +18,31 @@ module Apache self << '' end + # Pass the block to RewriteManager.build def rewrites(&block) self + indent(RewriteManager.build(&block)) self << '' end + # Create a permanent Redirect + # + # r301 '/here', '/there' #=> Redirect permanent "/here" "/there" def r301(*opt) self << "Redirect permanent #{quoteize(*opt) * " "}" end end + # Handle the creation of Rewritable things class RewriteManager class << self attr_accessor :rewrites + # Reset the current list of rewrites def reset! @rewrites = [] end + # Build rewritable things from the provided block def build(&block) reset! @@ -38,15 +51,21 @@ module Apache @rewrites.collect(&:to_a).flatten end + # Commit the latest rewritable thing to the list of rewrites def commit! @rewrites << @rewrite @rewrite = nil end + # Ensure that there's a RewriteRule to be worked with def ensure_rewrite! @rewrite = RewriteRule.new if !@rewrite end + # Create a RewriteRule with the given options + # + # rewrite %r{/here(.*)}, '/there$1', :last => true #=> + # RewriteRule "/here(.*)" "/there$1" [L] def rewrite(*opts) ensure_rewrite! @rewrite.rule(*opts) @@ -55,11 +74,19 @@ module Apache alias :rule :rewrite + # Create a RewriteCond with the given options + # + # cond "%{REQUEST_FILENAME}", "^/here" #=> + # RewriteCond "%{REQUEST_FILENAME}", "^/here" def cond(*opts) ensure_rewrite! @rewrite.cond(*opts) end + # Create a permanent RedirectMatch + # + # r301 %r{/here(.*)}, "/there$1" #=> + # RedirectMatch permanent "/here(.*)" "/there$1" def r301(*opts) redirect = RedirectMatchPermanent.new redirect.rule(*opts) @@ -67,6 +94,7 @@ module Apache @rewrites << redirect end + # Test the rewritable things defined in this block def rewrite_test(from, to, opts = {}) orig_from = from.dup @rewrites.each do |r| @@ -81,12 +109,15 @@ module Apache end end + # Common methods for testing rewritable things that use regular expressions module RegularExpressionMatcher + # Test this rewritable thing def test(from, opts = {}) from = from.gsub(@from, @to.gsub(/\$([0-9])/) { |m| '\\' + $1 }) replace_placeholders(from, opts) end + # Replace the placeholders in this rewritable thing def replace_placeholders(s, opts) opts.each do |opt, value| case value @@ -98,9 +129,11 @@ module Apache end end + # A matchable thing to be extended class MatchableThing include Apache::Quoteize + # The Apache directive tag for this thing def tag; raise 'Override this method'; end def initialize @@ -122,6 +155,7 @@ module Apache end end + # A RewriteRule definition class RewriteRule < MatchableThing include RegularExpressionMatcher @@ -133,6 +167,9 @@ module Apache @options = nil end + # Define the rule, passing in additional options + # + # rule %r{^/here}, '/there', { :last => true, :preserve_query_string => true } def rule(from, to,options = {}) super(from, to) @@ -150,6 +187,7 @@ module Apache @options = !options.empty? ? "[#{options * ','}]" : nil end + # Add a RewriteCondition to this RewriteRule def cond(from, to, *opts) rewrite_cond = RewriteCondition.new rewrite_cond.cond(from, to, *opts) @@ -162,13 +200,10 @@ module Apache end def to_a - output = @conditions.collect(&:to_s) - - output += super - - output + [ @conditions.collect(&:to_s), super ].flatten end + # Test this RewriteRule, ensuring the RewriteConds also match def test(from, opts = {}) ok = true @conditions.each do |c| @@ -183,21 +218,33 @@ module Apache end end + # A permanent RedirectMatch class RedirectMatchPermanent < MatchableThing include RegularExpressionMatcher def tag; 'RedirectMatch permanent'; end + def rule(from, to) + super(from, to) + + raise "from must be a Regexp" if !from.kind_of?(Regexp) + end + def to_s "#{tag} #{[quoteize(@from.source), quoteize(@to)].compact.flatten * " "}" end end + # A RewriteCond class RewriteCondition < MatchableThing include RegularExpressionMatcher def tag; 'RewriteCond'; end + # Define a RewriteCond + # + # rule "%{REQUEST_FILENAME}", "^/here", :case_insensitive #=> + # RewriteCond "%{REQUEST_FILENAME}" "^/here" [NC] def rule(from, to, *opts) super(from, to) @@ -226,6 +273,7 @@ module Apache "#{tag} #{[quoteize(@from), quoteize(@to), @options].compact.flatten * " "}" end + # Test this RewriteCond def test(from, opts = {}) super(from, opts) source = replace_placeholders(@from, opts)