App-CELL

 view release on metacpan or  search on metacpan

lib/App/CELL/Load.pm  view on Meta::CPAN

# 
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 
# 3. Neither the name of SUSE LLC nor the names of its contributors may be
# used to endorse or promote products derived from this software without
# specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ************************************************************************* 

package App::CELL::Load;

use strict;
use warnings;
use 5.012;

use App::CELL::Config qw( $meta $core $site );
use App::CELL::Log qw( $log );
use App::CELL::Message;
use App::CELL::Status;
use App::CELL::Test qw( cmp_arrays );
use App::CELL::Util qw( stringify_args is_directory_viable );
use Data::Dumper;
use File::Next;
use File::ShareDir;
use Params::Validate qw( :all );

=head1 NAME

App::CELL::Load -- find and load message files and config files



=head1 SYNOPSIS
 
    use App::CELL::Load;

    # Load App::CELL's internal messages and config params and then
    # attempt to load the application's messages and config params
    $status = App::CELL::Load::init();
    return $status if $status->not_ok;

    # attempt to determine the site configuration directory
    my $resulthash = App::CELL::Load::get_sitedir();

    # get a reference to a list of configuration files (full paths) of a
    # given type under a given directory
    my $metafiles = App::CELL::Load::find_files( '/etc/CELL', 'meta' );
   
    # load messages from all message file in a given directory and all its
    # subdirectories
    $status = message_files( '/etc/CELL' );

    # load meta, core, and site params from all meta, core, and site
    # configuration files in a given directory and all its subdirectories
    $status = meta_core_site_files( '/etc/CELL' );



=head1 DESCRIPTION

The purpose of the App::CELL::Load module is to provide message and config
file finding and loading functionality to the App::CELL::Message and
App::CELL::Config modules.



=head1 PACKAGE VARIABLES

This module provides the following package variables

=over 

=item C<$sharedir> - the full path of the sharedir

=item C<$sharedir_loaded> - whether it has been loaded or not

=item C<@sitedir> - the full path of the site configuration directory

=back

=cut

our $sharedir = '';
our $sharedir_loaded = 0;
our @sitedir = ();


=head1 MODULES

=head2 init

Idempotent initialization function.

Optionally takes a PARAMHASH. The following arguments are recognized:

=over

=item C<sitedir> -- full path to the/a site dir

=item C<enviro> -- name of environment variable containing sitedir path

=item C<verbose> -- increase logging verbosity of the load routine

=back

E.g.: 

    my $status = App::CELL::Load::init( 
                                         sitedir => '/etc/foo', 
                                         verbose => 1 
                                      );

See L<App::CELL::Guide> for details.

=cut

sub init {
    my %ARGS = validate( @_, {
        enviro => { type => SCALAR, optional => 1 },
        sitedir => { type => SCALAR, optional => 1 },
        verbose => { type => SCALAR, default => 0 },
    } );

    # determine verbosity level
    my $args_string;
    if ( %ARGS ) {
        $args_string = "with arguments: " . stringify_args( \%ARGS );
    } else {
        $args_string = "without arguments";
    }
    $meta->set('CELL_META_LOAD_VERBOSE', $ARGS{'verbose'} || 0);

    $log->info(
        "Entering App::CELL::Load::init from " . (caller)[0] . " $args_string",
        cell => 1
    ) if $meta->CELL_META_LOAD_VERBOSE;

    # check for taint mode
    if ( ${^TAINT} != 0 ) {
        return App::CELL::Status->new( level => "FATAL",
            code => "Attempt to load while in taint mode (-T)" );
    }

    # look up sharedir
    if ( not $sharedir ) {
        my $tmp_sharedir = File::ShareDir::dist_dir('App-CELL');
        if ( ! is_directory_viable( $tmp_sharedir ) ) {
            return App::CELL::Status->new( 
                level => 'ERR', 
                code => 'CELL_SHAREDIR_NOT_VIABLE',
                args => [ $tmp_sharedir, $App::CELL::Util::not_viable_reason ],
            );
        } 
        $log->info( "Found viable CELL configuration directory " . 
            $tmp_sharedir . " in App::CELL distro", cell => 1 ) if $meta->CELL_META_LOAD_VERBOSE;
        $site->set( 'CELL_SHAREDIR_FULLPATH', $tmp_sharedir );
        $sharedir = $tmp_sharedir;
    }

    # walk sharedir
    if ( $sharedir and not $sharedir_loaded ) {
        my $status = message_files( $sharedir );
        my $load_status = _report_load_status( $sharedir, 'sharedir', 'message', $status );
        return $load_status if $load_status->not_ok;
        $status = meta_core_site_files( $sharedir );
        $load_status = _report_load_status( $sharedir, 'sharedir', 'config params', $status );
        return $load_status if $load_status->not_ok;
        $site->set( 'CELL_SHAREDIR_LOADED', 1 );
        $sharedir_loaded = 1;
    }

    if ( $meta->CELL_META_LOAD_VERBOSE ) {
        if ( @sitedir ) {
            $log->debug( "sitedir package variable contains ->" . 
                         join( ':', @sitedir ) . "<-", cell => 1 );
        } else {
            $log->debug( "sitedir package variable is empty", cell => 1 );
        }
    }

    # get sitedir from args or environment
    my $status = get_sitedir( %ARGS );
    return $status unless $status->ok;
    my $sitedir_candidate = $status->payload;

    # walk sitedir
    if ( $sitedir_candidate ) {
        my $status = message_files( $sitedir_candidate );
        my $messages_loaded = _report_load_status( $sitedir_candidate, 'sitedir', 'message', $status );
        $status = meta_core_site_files( $sitedir_candidate );
        my $params_loaded = _report_load_status( $sitedir_candidate, 'sitedir', 'config params', $status );
        #
        # sitedir candidate is accepted only if something is actually
        # loaded
        #
        if ( $messages_loaded->ok or $params_loaded->ok ) {
            $meta->set( 'CELL_META_SITEDIR_LOADED', 
                        ( $meta->CELL_META_SITEDIR_LOADED + 1 ) );
            push @sitedir, $sitedir_candidate;
            $meta->set( 'CELL_META_SITEDIR_LIST', \@sitedir );
        }
    }

    # check that at least sharedir has really been loaded
    SANITY: {
        my $results = [];

        # remember, message constructor returns a status object
        my $status = App::CELL::Message->new( code => 'CELL_LOAD_SANITY_MESSAGE' );

        if ( $status->ok ) {
            my $msgobj = $status->payload;
            push @$results, (
                $meta->CELL_LOAD_SANITY_META,
                $core->CELL_LOAD_SANITY_CORE,
                $site->CELL_LOAD_SANITY_SITE,
                $msgobj->text(),
                        );
            my $cmp_arrays_result = cmp_arrays( 
                $results, 
                [ 'Baz', 'Bar', 'Foo', 'This is a sanity testing message' ],
            );
            last SANITY if $cmp_arrays_result;
        }
        return App::CELL::Status->new(
            level => 'ERR',
            code => 'CELL_LOAD_FAILED_SANITY',

lib/App/CELL/Load.pm  view on Meta::CPAN


=cut

sub meta_core_site_files {

    my $confdir = shift;
    my %reshash;
    $reshash{quantfiles} = 0;
    $reshash{quantitems} = 0;

    foreach my $type ( 'meta', 'core', 'site' ) {
        my $fulltype = 'App::CELL::Config::' . $type;
        #$log->debug( "\$fulltype is $fulltype", cell => 1 );
        my $file_list = find_files( $type, $confdir );
        foreach my $file ( @$file_list ) {
            no strict 'refs';
            $reshash{quantfiles} += 1;
            $reshash{quantitems} += parse_config_file( 
                File => $file,
                Dest => $$fulltype,
            );
        }
    }

    return App::CELL::Status->new(
        level => 'OK',
        payload => \%reshash,
    );
}


=head2 get_sitedir

This function implements the algorithm described in
L<App::CELL::Guide/Sitedir search algorithm> to find a sitedir candidate.
configuration directory. 

On success -- i.e., as soon as the algorithm finds a viable sitedir
candidate -- the sitedir (full path) is added to CELL_META_SITEDIR_LIST and
an OK status object is returned, with the sitedir in the payload.

On failure, the function returns an ERR or WARN status object containing
a description of what went wrong.

=cut

sub get_sitedir {

    my %paramhash = @_;
    my $reason;

    my ( $sitedir, $log_message, $status );
    GET_CANDIDATE_DIR: {

        # look in paramhash for sitedir
        $log->debug( "SITEDIR SEARCH, ROUND 1 (sitedir parameter):", cell => 1 );
        if ( $sitedir = $paramhash{sitedir} ) {
            $log_message = "Viable sitedir passed as argument";
            last GET_CANDIDATE_DIR if is_directory_viable( $sitedir );
            $reason = "CELL load routine received 'sitedir' argument ->$sitedir<- " .
                      "but this is not a viable directory ($App::CELL::Util::not_viable_reason)";
            $log->err( $reason, cell => 1 );
            return App::CELL::Status->new( level => 'ERR', code => $reason );
        }
        $log->debug( "looked at function arguments but they do not " .
                     "contain a literal site dir path", cell => 1 );

        # look in paramhash for name of environment variable
        $log->debug( "SITEDIR SEARCH, ROUND 2 (enviro parameter):", cell => 1 );
        if ( $paramhash{enviro} ) 
        {
            if ( $sitedir = $ENV{ $paramhash{enviro} } ) {
                $log_message = "Found viable sitedir in " . $paramhash{enviro}
                               . " environment variable";
                last GET_CANDIDATE_DIR if is_directory_viable( $sitedir );
                $reason = "CELL load routine received 'enviro' argument ->$paramhash{enviro}<- " .
                      "which expanded to ->$sitedir<- but this is not a viable directory " . 
                      "($App::CELL::Util::not_viable_reason)";
                return App::CELL::Status->new( level => 'ERR', code => $reason );
            } else {
                $reason = "CELL load routine: enviro argument contained ->$paramhash{enviro}<- " .
                      "but no such variable found in the environment";
                return App::CELL::Status->new( level => 'ERR', code => $reason );
            }
        }

        # fall back to hard-coded environment variable
        $log->debug( "SITEDIR SEARCH, ROUND 3 (fallback to CELL_SITEDIR " .
                     "environment variable):", cell => 1 );
        $sitedir = undef;
        if ( $sitedir = $ENV{ 'CELL_SITEDIR' } ) {
            $log_message = "Found viable sitedir in CELL_SITEDIR environment variable";
            last GET_CANDIDATE_DIR if is_directory_viable( $sitedir );
            $reason = "CELL load routine: no 'sitedir', 'enviro' arguments specified; " . 
                "fell back to CELL_SITEDIR environment variable, which exists " .
                "with value ->$sitedir<- but this is not a viable directory" .
                "($App::CELL::Util::not_viable_reason)";
            if ( $meta->CELL_META_SITEDIR_LOADED ) {
                $log->warn( $reason, cell => 1 );
                $log->notice( "The following sitedirs have been loaded already " .
                              join( ' ', @{ $meta->CELL_META_SITEDIR_LIST }), 
                              cell => 1 );
                return App::CELL::Status->ok;
            }
            return App::CELL::Status->new( level => 'WARN', code => $reason );
        }
    
        # failed to find a sitedir
        $reason = "CELL load routine gave up (no sitedir argument, no enviro " . 
                  "argument, no CELL_SITEDIR environment variable)";
        if ( $meta->CELL_META_SITEDIR_LOADED ) {
            $log->warn( $reason, cell => 1 );
            $log->notice( "The following sitedirs have been loaded already " .
                          join( ' ', @{ $meta->CELL_META_SITEDIR_LIST } ),
                          cell => 1 );
            return App::CELL::Status->ok;
        }
        return App::CELL::Status->new( level => 'WARN', code => $reason );
    }

    # SUCCEED
    $log->info( $log_message, cell => 1 );
    return App::CELL::Status->ok( $sitedir );
}


=head2 find_files

Takes two arguments: full directory path and config file type.

Always returns an array reference. On "failure", the array reference will
be empty.

How it works: first, the function checks a state variable to see if the
"work" of walking the configuration directory has already been done.  If
so, then the function simply returns the corresponding array reference from
its cache (the state hash C<%resultlist>). If this is the first invocation
for this directory, the function walks the directory (and all its
subdirectories) to find files matching one of the four regular expressions
corresponding to the four types of configuration files('meta', 'core',
'site', 'message'). For each matching file, the full path is pushed onto
the corresponding array in the cache.

Note that there is a ceiling on the number of files that will be considered
while walking the directory tree. This ceiling is defined in the package
variable C<$max_files> (see below).

=cut

# regular expressions for each file type
our $typeregex = {
       'meta'    => qr/^.+_MetaConfig.pm$/ ,
       'core'    => qr/^.+_Config.pm$/     ,
       'site'    => qr/^.+_SiteConfig.pm$/ ,
       'message' => qr/^.+_Message(_[^_]+){0,1}.conf$/ ,
};



( run in 0.795 second using v1.01-cache-2.11-cpan-39bf76dae61 )