lots of documentation

This commit is contained in:
John Bintz 2010-05-10 15:57:34 -04:00
parent 71681c7336
commit faa6654b3f
13 changed files with 275 additions and 17 deletions

2
.gitignore vendored
View File

@ -1,4 +1,6 @@
.loadpath
.project
coverage/*
docs/*

1
CHANGELOG Normal file
View File

@ -0,0 +1 @@
v0.1. Initial release, support for many of the Apache config basics.

View File

@ -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

View File

@ -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 #=> <IfModule mod_php5>
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 #=> <LocationMatch "^/my/location/[a-z0-9]+\.html">
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

View File

@ -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

View File

@ -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

View File

@ -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)}"

View File

@ -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"

View File

@ -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)

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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)