Bivio::BConf
# Copyright (c) 2001-2013 bivio Software, Inc. All rights reserved.
# $Id$
package Bivio::BConf;
use strict;
use Cwd ();
use File::Basename ();
use Sys::Hostname ();
# C<Bivio::BConf> provides a basic configuration. You bivio.bconf file
# would look like:
#
# use Bivio::BConf;
# Bivio::BConf->merge({});
#
# Set your $BCONF variable to point to this file, e.g. for bash:
#
# export BCONF=$PWD/bivio.bconf
sub CURRENT_VERSION {
return 10;
}
sub DELEGATE_ROOT_PREFIX {
return (shift(@_) =~ /^(.*)::\w*BConf$/)[0];
}
sub IS_2014STYLE {
return $ENV{BIVIO_IS_2014STYLE} || 0;
}
sub default_merge_overrides {
my($proto) = shift;
my($args) = @_;
unless (ref($args) eq 'HASH') {
my($root, $prefix, $owner) = @_;
$args = {
version => 0,
root => $root,
prefix => $prefix,
owner => $owner,
};
}
my($uri) = $args->{uri} ||= lc(($args->{root} =~ /(\w+)$/)[0]);
my($res) = Bivio::IO::Config->merge_list({
'Bivio::Biz::File' => {
root => "/var/db/$uri",
backup_root => "/var/bkp/$uri",
},
'Bivio::Biz::Model::AcceptanceTestList' => {
root => $args->{root},
},
'Bivio::Ext::DBI' => {
database => $args->{prefix},
user => "$args->{prefix}user",
password => "$args->{prefix}pass",
connection => 'Bivio::SQL::Connection::Postgres',
template1 => {
connection => 'Bivio::SQL::Connection::Postgres',
database => 'template1',
user => 'postgres',
password => 'pgpass',
},
dbms => {
connection => 'Bivio::SQL::Connection::Postgres',
database => 'postgres',
user => 'postgres',
password => 'pgpass',
},
},
'Bivio::IO::Log' => {
directory => "/var/log/bop/$uri",
},
'Bivio::Test::Language::HTTP' => {
home_page_uri => "http://test.$uri.bivio.biz",
},
'Bivio::Test::Util' => {
nightly_output_dir => "/home/btest/$uri",
nightly_cvs_dir => "perl/$args->{root}",
},
'Bivio::UI::Facade' => {
default => $args->{root},
is_2014style => $proto->IS_2014STYLE,
},
'Bivio::Util::Release' => {
$proto->merge_projects([
[$args->{root}, $args->{prefix}, $args->{owner}],
]),
},
'Bivio::Delegate::Cookie' => {
tag => uc($args->{prefix}),
},
'Bivio::IO::Config' => {
version => $args->{version},
},
$proto->IS_2014STYLE
? (
'Bivio::Test::HTMLParser::Forms' => {
error_class => 'b_form_field_error',
error_title_class => 'alert alert-warning',
},
) : (),
'Bivio::IO::Trace' => {
config => {
# This doesn't actually exist, b/c Config is second module
# to load (after UNIVERSAL) so it can't register (ever).
# However, --trace=config is rather convenient
package_filter => '/^Bivio::IO::Config$/',
},
html_attrs => {
call_filter => '$sub =~ /vs_html_attrs_render_one/',
package_filter => '/^Bivio::UI::HTML::ViewShortcuts$/',
},
perf_time => {
call_filter => '$sub =~ /\bperf_time/',
package_filter => '/^Bivio::Agent::Request$/',
},
search => {
package_filter => '/::Search/',
},
sql => {
call_filter => '$sub =~ /_trace_sql|_commit_or_rollback/',
package_filter => '/^Bivio::SQL::Connection$/',
},
stack => {
call_filter => '$sub =~ /_print_stack/',
package_filter => '/^Bivio::Die$/',
},
bunit_case => {
call_filter => '$sub =~ /_eval$/',
package_filter => '/^Bivio::Test$/',
},
btest_mail => {
call_filter => '$sub =~ /_grep_msgs/',
package_filter => '/^Bivio::Test::Language::HTTP$/',
},
all => {
package_filter => '/./',
},
},
}, {
$args->{version} < 9 ? () : (
'Bivio::SQL::PropertySupport' => {
unused_classes => [],
},
'Bivio::Test::Language::HTTP' => {
deprecated_text_patterns => 0,
},
),
});
return $args->{version} < 2 ? %$res : $res;
}
sub dev {
my($proto, $http_port, $overrides) = @_;
$http_port ||= $ENV{BIVIO_HTTPD_PORT};
die(__PACKAGE__, '->dev(port): missing port param or set $ENV{BIVIO_HTTPD_PORT}')
unless $http_port;
print(STDERR $http_port, ": using odd numbered port not advised, will be 'secure'\n")
if $http_port % 2;
my($host) = $proto->bconf_host_name;
my($user) = eval {getpwuid($>)} || $ENV{USER} || 'nobody';
my($home) = $ENV{HOME} || (-w "/home/$user/." ? "/home/$user" : Cwd::getcwd());
my($files_root) = Bivio::IO::Config->bootstrap_package_dir($proto) . '/files';
my($perl_lib) = Bivio::IO::Config->bootstrap_package_dir(__PACKAGE__) =~ m{(.+)/Bivio$};
my($merge_overrides) = $proto->merge_overrides($host);
my($db) = ($merge_overrides->{'Bivio::Ext::DBI'} || {})->{database};
$db =~ s/(?=\w)db$//
if $db;
return _validate_config(Bivio::IO::Config->merge_list(
$overrides || {},
Bivio::IO::Config->bconf_dir_hashes,
$proto->dev_overrides($home, $host, $user, $http_port, $files_root, $perl_lib),
{
'Bivio::Agent::Request' => {
can_secure => 0,
},
'Bivio::Biz::File' => {
root => "$files_root/db",
backup_root => "$files_root/bkp",
},
'Bivio::Ext::DBI' => {
$db && $db ne 'none' ? (database => $db . $user) : (),
},
'Bivio::IO::Alert' => {
strip_bit8 => 1,
want_pid => 0,
want_time => 1,
},
'Bivio::IO::Config' => {
is_dev => 1,
},
'Bivio::IO::Log' => {
directory => "$files_root/log",
},
'Bivio::Mail::Common' => {
sendmail => 'bivio test -input - mock_sendmail',
},
'Bivio::Test::Language::HTTP' => {
home_page_uri => "http://$host:$http_port",
server_startup_timeout => 60,
mail_tries => 30,
remote_mail_host => $host,
local_mail_host => $host,
email_user => $user,
},
'Bivio::UI::FacadeComponent' => {
die_on_error => 1,
},
'Bivio::UI::Facade' => {
local_file_root => $files_root,
want_local_file_cache => 0,
http_host => "$host:$http_port",
mail_host => $host,
},
'Bivio::UI::HTML::Widget::Page' => {
show_time => 1,
},
'Bivio::UI::HTML::Widget::SourceCode' => {
source_dir => $perl_lib,
},
'Bivio::Util::HTTPLog' => {
email => '',
error_file => 'stderr.log',
pager_email => '',
},
'Bivio::Util::Release' => {
rpm_home_dir => "$home/tmp/b-release/home",
rpm_user => $user,
tmp_dir => "$home/tmp/b-release/build",
},
main => {
http => {
port => $http_port,
},
},
'Bivio::Util::HTTPD' => {
port => $http_port,
},
},
$merge_overrides,
_base($proto),
));
}
sub dev_overrides {
# Returns any overrides to the development configuration, called by
# L<dev|"dev">. Returns an empty hash by default.
return {};
}
sub bconf_host_name {
return $ENV{BIVIO_HOST_NAME} || Sys::Hostname::hostname();
}
sub merge {
my($proto, $overrides) = @_;
# Uses I<overrides> config to override default config defined in this
# module.
return Bivio::IO::Config->merge_list(
$overrides || {},
$proto->merge_overrides($proto->bconf_host_name),
_base($proto),
);
}
sub merge_class_loader {
my($proto, $overrides) = @_;
# Merges L<Bivio::IO::ClassLoader|Bivio::IO::ClassLoader> config by prefixing
# I<maps> array refs with values standard values. Other values overwritten.
# Returns the array:
#
# 'Bivio::IO::ClassLoader' => {
# merged configuration,
# },
#
# Usage in your BConf.pm
#
# ...
# $proto->merge_class_loader({
# maps => {
# Facade => ['OurSite::Facade'],
# Model => ['OurSite::Model'],
# ...,
# },
# }),
# ...
$overrides ||= {};
$overrides->{delegates} = {map(
($_ => join('::', $proto->DELEGATE_ROOT_PREFIX, 'Delegate', $_ =~ /(\w+)$/)),
@{$overrides->{delegates}},
)} if ref($overrides->{delegates}) eq 'ARRAY';
return (
'Bivio::IO::ClassLoader' => Bivio::IO::Config->merge(
$overrides,
{
delegates => {
'Bivio::Search' => 'Bivio::Search::None',
'Bivio::Agent::HTTP::Cookie' => 'Bivio::Delegate::Cookie',
'Bivio::Agent::TaskId' => 'Bivio::Delegate::TaskId',
'Bivio::Auth::Permission' => 'Bivio::Delegate::SimplePermission',
'Bivio::Auth::RealmType' => 'Bivio::Delegate::RealmType',
'Bivio::Auth::Role' => 'Bivio::Delegate::Role',
'Bivio::Auth::Support' => 'Bivio::Delegate::SimpleAuthSupport',
'Bivio::Type::ECService' => 'Bivio::Delegate::ECService',
'Bivio::Type::FailoverWorkQueueOperation' => 'Bivio::Delegate::FailoverWorkQueueOperation',
'Bivio::Type::Location' => 'Bivio::Delegate::SimpleLocation',
'Bivio::Type::MotionStatus' => 'Bivio::Delegate::SimpleMotionStatus',
'Bivio::Type::MotionType' => 'Bivio::Delegate::SimpleMotionType',
'Bivio::Type::MotionVote' => 'Bivio::Delegate::SimpleMotionVote',
'Bivio::Type::RealmDAG' => 'Bivio::Delegate::RealmDAG',
'Bivio::Type::RealmName' => 'Bivio::Delegate::SimpleRealmName',
'Bivio::Type::RowTagKey' => 'Bivio::Delegate::RowTagKey',
'Bivio::TypeError' => 'Bivio::Delegate::SimpleTypeError',
'Bivio::UI::HTML::WidgetFactory' => 'Bivio::Delegate::SimpleWidgetFactory',
},
maps => {
Action => ['Bivio::Biz::Action'],
Agent => ['Bivio::Agent'],
AgentEmbed => ['Bivio::Agent::Embed'],
AgentHTTP => ['Bivio::Agent::HTTP'],
AgentJob => ['Bivio::Agent::Job'],
Auth => ['Bivio::Auth'],
Bivio => ['Bivio'],
Biz => ['Bivio::Biz'],
CSSWidget => ['Bivio::UI::CSS::Widget', 'Bivio::UI::Text::Widget', 'Bivio::UI::Widget'],
Cache => ['Bivio::Cache'],
ClassWrapper => ['Bivio::ClassWrapper'],
Collection => ['Bivio::Collection'],
Delegate => ['Bivio::Delegate'],
Ext => ['Bivio::Ext'],
FacadeComponent => ['Bivio::UI::FacadeComponent'],
GIS => ['Bivio::GIS'],
HTML => ['Bivio::HTML'],
HTMLFormat => ['Bivio::UI::HTML::Format'],
HTMLWidget => ['Bivio::UI::HTML::Widget', 'Bivio::UI::Widget'],
IO => ['Bivio::IO'],
JavaScriptWidget => ['Bivio::UI::JavaScript::Widget', 'Bivio::UI::Widget'],
MIME => ['Bivio::MIME'],
Mail => ['Bivio::Mail'],
MailWidget => ['Bivio::UI::Mail::Widget', 'Bivio::UI::Text::Widget', 'Bivio::UI::Widget'],
MainErrors => ['Bivio::UI::XHTML::Widget::MainErrors'],
Model => ['Bivio::Biz::Model'],
SQL => ['Bivio::SQL'],
Search => ['Bivio::Search'],
SearchParser => ['Bivio::Search::Parser'],
SearchParserRealmFile => ['Bivio::Search::Parser::RealmFile'],
ShellUtil => ['Bivio::Util', 'Bivio::Biz::Util'],
Test => ['Bivio::Test'],
TestHTMLParser => ['Bivio::Test::HTMLParser'],
TestLanguage => ['Bivio::Test::Language'],
TestUnit => ['Bivio::Test::Unit'],
TextWidget => ['Bivio::UI::Text::Widget', 'Bivio::UI::Widget'],
Type => ['Bivio::Type', 'Bivio::Auth'],
UI => ['Bivio::UI'],
# CSS has no general widgets so don't put Bivio::UI in path
UICSS => ['Bivio::UI::CSS'],
UIHTML => ['Bivio::UI::HTML', 'Bivio::UI'],
UIXHTML => [
$proto->IS_2014STYLE
? 'Bivio::UI::Bootstrap'
: (),
'Bivio::UI::XHTML', 'Bivio::UI::HTML',
],
Util => ['Bivio::Util', 'Bivio::Biz::Util'],
View => ['Bivio::UI::View'],
Widget => ['Bivio::UI::Widget'],
WikiText => ['Bivio::UI::XHTML::Widget::WikiText'],
XHTMLWidget => [
$proto->IS_2014STYLE
? 'Bivio::UI::Bootstrap::Widget'
: (),
'Bivio::UI::XHTML::Widget',
'Bivio::UI::HTML::Widget',
'Bivio::UI::Widget',
],
XMLWidget => ['Bivio::UI::XML::Widget', 'Bivio::UI::XHTML::Widget', 'Bivio::UI::HTML::Widget', 'Bivio::UI::Text::Widget', 'Bivio::UI::Widget'],
},
},
1,
),
);
}
sub merge_dir {
my($proto, $overrides) = @_;
# Reads the /etc/bconf.d directory for *.bconf files. Merges in reverse
# alphabetical order. I<overrides> take precedence over dir, and dir
# takes precedence over the rest.
return Bivio::IO::Config->merge_list(
$overrides || {},
Bivio::IO::Config->bconf_dir_hashes,
$proto->merge_overrides($proto->bconf_host_name),
_base($proto));
}
sub merge_http_log {
my($proto, $overrides) = @_;
# Merges L<Bivio::Util::HTTPLog|Bivio::Util::HTTPLog> config by prefixing
# standard array refs (ignore, critical, error) with standard valus. Other
# values overwritten. Returns the array:
#
# 'Bivio::Util::HTTPLog' => {
# merged configuration,
# },
#
# Usage in your BConf.pm
#
# ...
# $proto->merge_http_log({
# ignore_list => [
# ],
# }),
# ...
return (
'Bivio::Util::HTTPLog' => Bivio::IO::Config->merge(
$overrides || {}, {
ignore_list => [
# Standard apache debug and info
'\] \[(?:info|debug)\] ',
'\[notice\] Apache.*configured -- resuming normal operations',
'\[notice\] Accept mutex',
'Dispatcher::.* JOB_(?:START|END):',
' CommonName .* does NOT match server name!',
'\[error\].*File does not exist\:/',
# Virii and such
'(?:File does not exist:|DieCode::NOT_FOUND:).*(?:robots.txt|system32|\.asp|_vti|default\.ida|/sumthin|/scripts|/cgi|root.exe|/instmsg|/favicon2|site_root/default.bview|\.php$|Assert not robot)',
'::NOT_FOUND:.*view..site_root/(\w+.html|robots.txt).bview',
'DAVList:.*::MODEL_NOT_FOUND',
'DieCode::MISSING_COOKIES',
'client sent HTTP/1.1 request without hostname',
'mod_ssl: SSL handshake timed out',
'mod_ssl: SSL handshake failed: HTTP spoken on HTTPS port',
'mod_ssl: SSL handshake interrupted by system',
'Apache2::RequestIO.*Software caused connection abort',
'request failed: URI too long',
'Invalid method in request',
'Bivio::UI::Task::.* unknown facade uri',
'access to /favicon.ico failed',
'Bivio::DieCode::FORBIDDEN',
'Invalid URI in request',
'Action::RealmFile:.*::MODEL_NOT_FOUND:.*model.*::RealmFile',
'MODEL_NOT_FOUND: model.*::RealmOwner.*task=MAIL_RECEIVE_DISPATCH',
'Directory index forbidden by rule:',
'_update_status.*DECLINED:',
'command died with non-zero status entity=>(?:catdoc|catppt|docx2txt|ldat|pdfinfo|pdftotext)',
'Init: Session Cache is not configured',
'Apache configure -- resuming normal operations',
'Bivio::DieCode::NOT_FOUND: view not found class=>Bivio::UI::View::SiteRoot',
'Software caused connection abort: cache: error returned while trying to return disk cached data',
'Directory index forbidden by Options directive',
'cannot mail to a default realm',
'reconnecting to database: pid=',
'from_literal failed:.*no-message-id',
'Cookie:.* duplicate cookie value for key',
'Bivio::Mail::Incoming::_check_message_id.* invalid ',
'handle_pre_auth_task:.*: could not parse user',
'Bivio::Biz::Model::RealmOwner.*: user is not valid',
'File does not exist: /var/www/html',
'File does not exist: /var/www/facades/',
'Bivio::Mail::Outgoing::_rewrite_from:.* from header missing email, ignoring',
'Bivio::Agent::HTTP::Form::parse:.* text/xml: unknown Content-Type',
'Attempt to serve directory: /var/www/html/',
# these no longer indiciate a problem (all errors could indicate a problem)
'form_errors=\{',
'Bivio::DieCode::CORRUPT_QUERY',
'support mail from user marked as spam, overriding',
'credit card processed:',
'UserPasswordQuery.*NOT_FOUND',
# Dreamweaver does this
'HTTP_CONFLICT mkcol',
'missing from header, ignoring',
],
error_list => [
# Don't add errors that we don't want counts on, e.g.
# login_error. Not ignored, so shows up in email, but
# never goes criticial
'Bivio::DieCode::DIE.*',
'Bivio::DieCode::CONFIG_ERROR.*',
],
critical_list => [
'Bivio::DieCode::DB_ERROR.*',
],
# These errors are not a problem unless they occur "too often"
# See ignore_unless_count_list
ignore_unless_count_list => [
'Bivio::DieCode::CLIENT_ERROR',
'Bivio::DieCode::UPDATE_COLLISION',
'Bivio::Biz::FormContext::_parse_error',
'HTTP::Query::_correct.*correcting query',
'request aborted, rolling back',
'Unable to parse address',
'Connection reset by peer',
'caught SIGTERM, shutting down',
'server reached MaxClients setting, consider raising',
],
},
1,
),
);
}
sub merge_overrides {
# Returns any overrides to the base configuration, called by
# L<merge|"merge">. Returns an empty hash by default.
return {};
}
sub merge_projects {
my($proto, $overrides) = @_;
my($seen) = {};
return (
projects => [
grep(
!$seen->{"$_->[0] $->[1]"}++,
[qw(ProjEx project), 'bivio Software, Inc.'],
[qw(Bivio b), 'Bivio Software, Inc.'],
[qw(Bivio/PetShop pet), 'Bivio Software, Inc.'],
@{$overrides || []},
),
],
);
}
sub merge_realm_role_category_map {
my($proto, $new) = @_;
return 'Bivio::Biz::Util::RealmRole' => {
category_map => sub {return [
map(
[
$_->as_realm_role_category =>
['*everybody-' . $_->as_realm_role_category_role_group
=> [qw(-MAIL_SEND -MAIL_POST)]],
['*' . $_->as_realm_role_category_role_group
=> [qw(+MAIL_SEND +MAIL_POST)]],
],
Bivio::IO::ClassLoader->map_require('Type.MailSendAccess')
->get_non_zero_list,
),
map([
"feature_$_" => ['*everybody' => uc("feature_$_")],
], qw(
blog
bulletin
calendar
dav
file
group_admin
mail
task_log
wiki
)),
[
feature_crm =>
'+mail_send_access_everybody',
['*everybody' => 'FEATURE_CRM'],
], [
feature_site_admin =>
['*everybody' => 'FEATURE_SITE_ADMIN'],
['*all_members' => [qw(ADMIN_WRITE ADMIN_READ)]],
], [
feature_tuple =>
['*everybody' => 'FEATURE_TUPLE'],
['*all_admins' => [qw(TUPLE_ADMIN)]],
['*all_members' => [qw(TUPLE_WRITE)]],
['*all_guests' => [qw(TUPLE_READ)]],
], [
#DEPRECATED: Need to fix apps which use this, and use feature_tuple instead
tuple =>
'+feature_tuple',
], [
common_results_motion =>
['*everybody' => 'FEATURE_MOTION'],
['*all_members' => 'MOTION_WRITE'],
['*all_admins' => [qw(MOTION_ADMIN MOTION_READ)]],
], [
open_results_motion =>
'+common_results_motion',
['*all_guests-all_admins' => '+MOTION_READ'],
], [
closed_results_motion =>
'+common_results_motion',
['*all_guests-all_admins' => '-MOTION_READ'],
], [
feature_motion =>
'+open_results_motion',
],
$new ? @{$new->()} : (),
]},
};
}
sub _base {
my($proto) = @_;
# Returns _base configuration.
return {
$proto->merge_realm_role_category_map(),
'Bivio::Die' => {
stack_trace_error => 1,
},
'Bivio::Ext::DBI' => {
database => 'none',
user => 'none',
password => 'none',
connection => 'Bivio::SQL::Connection::None',
none => {
database => 'none',
user => 'none',
password => 'none',
connection => 'Bivio::SQL::Connection::None',
},
},
'Bivio::IO::Alert' => {
intercept_warn => 1,
stack_trace_warn => 1,
want_pid => 0,
want_stderr => 1,
want_time => 1,
},
'Bivio::Type::Secret' => {
key => 'alphabet',
},
'Bivio::UI::Facade' => {
local_file_root => '/var/www/facades',
http_host => 'localhost.localdomain',
mail_host => 'localhost.localdomain',
},
'Bivio::Util::Release' => {
rpm_home_dir => '/usr/src/redhat/RPMS/noarch',
$proto->merge_projects,
rpm_user => 'nobody',
},
$proto->merge_http_log({
# These are defaults, which may be overriden for testing,
# which is why they are here
email => 'root',
pager_email => 'root',
error_count_for_page => 3,
ignore_unless_count => 3,
}),
main => {
http => {
port => '80',
},
},
};
}
sub _validate_config {
my($config) = @_;
# Ensures the configuration is consistent. For example, NoDbAuthSupport
# should not be present if if Bivio::Ext::DBI is defined.
# Issues warnings only for dev() configuration.
warn('WARNING: NoDbAuthSupport used with Bivio::Ext::DBI')
if ($config->{'Bivio::IO::ClassLoader'}
->{delegates}->{'Bivio::Auth::Support'}
eq 'Bivio::Delegate::NoDbAuthSupport')
&& ($config->{'Bivio::Ext::DBI'}->{database} ne 'none');
return $config;
}
1;