######################################################################## # # use-ed by ETL code to set the standard library path. # putting this in a module standardizes the sanity checks # and puts the literal path for /home/ETLprod in one place. # # >> NOTE: DIRECTORIES USED HERE MUST BE UPDATED TO MATCH REALITY! << # # if the ETL module library directory is moved then this # is where to change things to avoid everything failing. # # the pvcs wrappers used in development live in a separate tree to # avoid bootstrap issues checking out the code. anything that # touches PVCS lives under /home/pvcs. # ######################################################################## package ETL; require 5.6.0; use strict; use Carp 'croak'; use Cwd 'abs_path'; # we keep the PVCS wrappers and related modules in a separate # area, outside ETL, to save ourselves from chicken-and-egg # issues in checkout. # # block if syntax is required for the use to work properly. BEGIN { if( -d '/home/pvcs/ETL' ) { use lib '/home/pvcs' } } # hmmm... # # - when we send out the archive of a release it contains some # code used for the installation. this means that we need to # use lib based on the user's PWD. # # - every other module already does its own test for running # within a ./ETL directory. # # pushing the test and extraction of the ./ETL directory up # here makes it possible to use libs in the extracted # archive and saves us from re-writing the same test in # each module. BEGIN { # if this is not called w/in an ETL directory tree then # it is meaningless. # # all remaining work paths in subsequent modules are # relative to $ENV{ETL}. adding the '/' makes it easier # to avoid grabbing dir's like "ETL_TEST" without messy # regexen. keeping the trailing slash also protects # further code from accidentally combining dir names. ( $ENV{ETL} ) = "$ENV{PWD}/" =~ m{(.+?/ETL/)} or croak "No './ETL' directory in current path: $ENV{PWD}"; # where the ETL directory containing ETL-specific .pm's lives. # it may be useful in testing to override where the libs are # found, hence $ENV{ETLLIBDIR}. it is not kosher, however, # to override the standard path on QA or Production. # # exporting an empty ETLLIBDIR won't cause us to croak # here, nor will '0'. changes to the development cluster # system names must be reflected here. croak 'ETLLIBDIR can only be used on development systems' if $ENV{ETLLIBDIR} && $ENV{HOSTNAME} !~ /^devETL\d+/; $ENV{ETLLIBDIR} ||= "$ENV{ETL}/sys"; # at this point if $ENV{ETLLIBDIR} is defaulted to a # standard path under $ENV{ETL}. -e $ENV{ETLLIBDIR} || croak "Missing directory $ENV{ETLLIBDIR}"; -r _ || croak "Un-Readable directory $ENV{ETLLIBDIR}"; } # whatever it is, use it. use lib $ENV{ETLLIBDIR}; # at this point all ETL::Blah.pm modules should be available # via "use ETL::Blah". 1 ######################################################################## # ETL::Env.pm # # set standard environment for sub-processes. # # normal use: # # grab a default environment: # # my $env = ETL::Env->default; # # set program-specific values: # # $env->{foo} = 'bar'; # # display the current settings: # # $env->set; # # export the values for use by sub-proc's: # # $env->export; # # each of these methods passes its object argument to allow for # daisychaining the operations. # # e.g., q&d subshell runner: # # exec ETL::Env->default->export->{SHELL}; # # to modify values: # # my $a = ETL::Env->default; # # $a->{foo} = 'bar'; # # exec $a->{SHELL}; # die "Failed to exec $a->{SHELL}: $!"; # # # catch: PATH has caused us major pain. people play with their path # to get things working in development and end up breaking the make # with nonstandard commands. we cannot ignore PATH, since execing # with an empty path also breaks things and don't want to blindly # inherit it. # # fix is to generate a default path here where it can be documented. # # verbosity can be turned on via $ETL::Env::verbose = truevalue; # # we define four environment variabled to locate code w/in the # ETL file system. all of them are derived from $ENV{PWD} and # $0 (i.e., path to the running item) in pretty much the same # way as ETL::PVCS does it. # # the shell environment variables are: # # $ETL = pwd up to first ./ETL # $ETLCODE = $ENV{ETL} . '/code'; # $ETLVAR = $ENV{ETL} . '/var'; # $ETLDOC = $ENV{ETL} . '/doc'; # # $ETLAPP = abs_path direname $0; # ######################################################################## package ETL::Env; use strict; use Carp qw( &croak ); use Cwd qw( &abs_path ); use File::Basename qw( &dirname ); # used to pretty-print the environment hash. use Data::Dumper; $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 1; $Data::Dumper::Deepcopy = 1; our $verbose = 1; ######################################################################## # globals ######################################################################## # variables which are inherited from the shell, these take # precidence over any assignments made prior to calling export. # these should be limted to items which confuse the shell if # they are not present (e.g., HOME). # # individual programs should not have to play with this, they # can set things explicitly as necessary. # # note: export uses these items to override any assignments made # to the $env object in the defaults or by programs. # # note: @inerhit should most certianly NOT contain PATH! # # this does not need to include anything that the shell sets # properly for itself (e.g., PWD). once way to check this is # to leave @inherit blank, exec a shell and use "set" at the # shell level to see what is available (see note after __END__ # for example shell output). # # inheriting the current build number simplifies daisy chain makes. # if BUILD is not set in the caller's environmet then it will come # across as defined to an empty string ('') in the shell. this also # makes tests in the shell simpler: # # [ $BUILD = '' ] && exit -1; # # will blow out of the shell script if the build variable isn't set. # # ETL and ETLLIBDIR are assigned in ETL.pm and must always be # inherited on all systems. my @inherit = qw( BUILD HOME MAIL LOGNAME HOSTNAME TERM ); # unless we get a legit value for the environment vile $ETLENV # will be empty in any subshell. korn shell code can use: # # [ "$ETLENV" eq "" ] && exec eshell $0 $@; # # to have itself run in a preset environment. since eshell exec's # its arguments this leaves the pid unchanged. # # this obviously assumes that noone loads ETLENV into their login # shell and modifies @inherit to include it. my %defaultenv = ( SHELL => '/bin/ksh', PAGER => 'less', ETLENV => '', # path to the ETL environment file. PATH => '', # this will be set via blah.env files. # syntatic sugar to avoid quite so many ./.. in paths. # # ETLVAR is writable during execution. # ETLCODE is read-only during execution. # ETLDOC is for symmetry, used in doc-bundling operations of build. # # .env files can override these, but they are built into the basic # file structure so there shouldn't be any reason to. ETLVAR => $ENV{ETL} . '/var', ETLCODE => $ENV{ETL} . '/code', ETLDOC => $ENV{ETL} . '/doc', # absolute path to the directory from which the code # is running. good place to look for config files, etc. ETLAPP => $0 eq '-e' ? $ENV{PWD} : abs_path dirname $0, ); ######################################################################## # subroutines ######################################################################## # constructor for $env "environment object". # # note: it may be probematic to call this multiple times, since # some values may depend on things which are replaced in the # normal processing. e.g., $foo = BLAH->default->default could # lead to offball settings depending on the contents of our # $cluster.env file. sub default { local $\ = "\n"; # first item is class, remainder are assignments made after # the defaults are set up. my $class = shift; # expand %defaultenv into a a list and make an anon hash out of it, # then bless it for the caller. if the caller used an object to # access this (i.e., for inheritence) then we will use the object's # class, otherwise we have this class name and will use that. my $env = bless { %defaultenv }, ref $class || $class; # if ETL.pm wasn't called before this we are in trouble... croak 'Missing ETL in environment, please "use ETL"' unless $ENV{ETL}; croak 'Missing ETLLIBDIR in environment, please "use ETL"' unless $ENV{ETLLIBDIR}; # at this point the default environment has been set up. it will # be modified by cluster-specific code. print STDERR "\nDefault env:", Dumper $env if $verbose; # next: exceptions for groups of systems: development, qa, production. # # whole point of "CLUSTER" setting is to allow for simple tests in # shell scripts -- and provide a placeholder for the if logic for # offball host names. # envfile names look like "/foo/bar/devblah.env" or # "/foo/bar/blahqa.env" and live in a directory adjacent to this # module. different directories allow simpler file protections # for maintinence. # # if the expected file is not present, is empty or unreadable # then we have a real problem... my ( $moduledir ) = dirname __FILE__; # this can be used in shell scripts to test whether we have # passed through this code (see notes in header comment block). # also helps in figuring out how the environment got this # screwed up. $env->{ETLENV} = ''; # we apply environment files in the order specified # below. first the hostname is checked, then the username, # # items passed in are handled after this and override the # environment files. for ( "$moduledir/../env/$ENV{HOSTNAME}.env", "$moduledir/../env/$ENV{LOGNAME}.env" ) { next unless -e; croak "Empty $_" unless -s _; croak "Unreadable $_" unless -r _; # at this point we have reasonable odds of using the thing... $env->{ETLENV} .= $_; # the cluster's envronment file was produced by # Data::Dumper -- or similar. it will look something # like: # # { # foo => 'bar', # bletch => 'blort', # } # # notice the lack of assignment and closing ';'. # # braces isolate possible side effects from the eval, local $/ and # auto-close lexical $file handle on way out. # # @keyz is mainly for debugging -- makes it simpler to # eyeball what got defined. { # localize $/, my vars. local $/ = undef; open my $file, "< $_" or croak "< $_: $!"; my $dump = <$file> or croak "Failed reading $_: $!"; # convert $dump to a hash reference. # %blah in a scalar context will be true if the hash has # any entrys, false if the hash is empty. my $hash = eval $dump; %$hash or croak "Empty hash on eval from $file"; my @keyz = sort keys %$hash; # use hash slice to assign the relavant portions of $hash # into $env. @{$env}{@keyz} = @{$hash}{@keyz}; } # at this point the environment has been modified to suit # the host, user and command line. print STDERR "\nEnv after $_:", Dumper $env if $verbose; } # next step is to add in whatever the user passed in as # arguments. assigning the hash slice gets it all done # in one step. # # this overrides any default settings. # # it also does no sanity checking whatsoever and should be # used with care... # # note: @_ works at this point because we used shift to # grab the class. if( @_ ) { my %opt = @_; my @keyz = keys %opt; print "\nAdding:", @keyz if $verbose; @{$env}{@keyz} = @opt{@keyz}; print STDERR "\nAdded env:", Dumper $env if $verbose; } # final step is to carry over inherited values. # hash slice assignment gets it all done in one step. @{$env}{@inherit} = @ENV{@inherit}; print STDERR "\nInherited env:", Dumper $env if $verbose; # send it back to whence we came... $env } # load %ENV with the contents of $env. # pass $env back the object for daisychaining. sub export { my $env = shift; %ENV = %$env; print STDERR "\nExporting:", (Dumper \%ENV), "\n" if $verbose; $env } # use Data::Dumper to pretty-print %env. # probably should be called by anything whose logs are examined. # e.g., # # exec ETL::Env->default->export->set->{SHELL}; # # will create an anonymous object, export the environment, # show what has been set and then exec the SHELL. # # passes back $env for use in daisychaining. sub set { local $\ = ''; local $, = ''; my $env = shift; print "\nShell Environment:\n\n", Dumper $env, "\n"; $env } # keep the use pragma happy 1 __END__ contents of ksh exec-ed w/ empty @inherit. note that this does not include HOME, LOGNAME, MAIL, or HOSTNAME, which should probably be put into @inherit. $ set CLUSTER=DEV ERRNO=25 FCEDIT=/bin/ed IFS=' ' LINENO=1 MAILCHECK=600 OPTIND=1 PATH=/usr/ccs/bin:/usr/pvcs/vm/solaris/bin:/bin:/usr/bin:/usr/ucb:/usr/bin/X11:/usr/local/bin PPID=20605 PS1='$ ' PS2='> ' PS3='#? ' PS4='+ ' PWD=/data/workareas/lembark/perl-modules RANDOM=5938 SECONDS=2 SHELL=/bin/ksh TMOUT=0 #!/usr/bin/perl ######################################################################## # esh # pass a list of commands to a single pre-configured subshell. # # default is to create a subshell. # # e.g., # # esh 'make foobar'; # # or # # eshell 'cd /tmp; find . -type f | cpio -ov | gzip --fast --verbose'; # eshell -c /tmp 'find . -type f | cpio -ov | gzip --fast --verbose'; # # the -c is mainly useful for while/for loops that iterate the command # over a set of directories. # # notes: # # tokens are interpreted once by the shell in calling this executable # once more by the shell when we invoke the command. this may cause # some wierdness in quoting char's, etc. # ######################################################################## # CPAN modules use Getopt::Long; # homegrown modules use ETL; use ETL::Env; ######################################################################## # handle the command line ######################################################################## my @optionz = qw( cd|c=s debug! verbose! help|h|? ); my $cmdline = {}; GetOptions( $cmdline, @optionz ); if( $cmdline->{help} ) { my ($base) = $0 =~ m{(?:.+/)?(.+)}; print STDERR <{cd} || ''; $ETL::Env::verbose = $cmdline->{debug} ? 1 : 0; my $verbose = $cmdline->{verbose} ? 1 : $ETL::Env::verbose; ######################################################################## # real work begins here ######################################################################## # chdir to the requested location or die trying... chdir $dir or die "Unable to chdir $dir: $!" if $dir; # by this time getopt will have stripped @ARGV down to the # program arguments, of which there should be some... my $cmd = ''; if( @ARGV ) { $cmd = join ' ', @ARGV; # paste @ARGV together with spaces. # if all we have left are spaces then give up. die "Empty command line" unless $cmd =~ /\w/; $cmd = " -c '$cmd'"; print "About to run:\n\n\t$cmd\n" if $verbose; } else { print "No Command Argument Given, Exec-ing Shell"; print STDERR "\n\n\tUse 'exit' to leave the shell.\n"; } # export the environment, display it if requested to and # then exec the shell "-c" and the command in literal quotes. # the "-c" assumes ksh or bash for $env->{SHELL}. my $env = ETL::Env->default->export; $env->set if $verbose; exec "$env->{SHELL}$cmd"; # if we get this far then the exec failed; complain about it # and exit non-zero. die "Failed exec $env->{SHELL}: $!"; __END__ #!/bin/ksh -x : 'This could be an AbInitio script, or any other ksh'; : 'Could be bash, for that matter...'; : 'The program was called with arguments:'; : '---------------------------------------------------------'; : $@; : '---------------------------------------------------------'; : 'What matters to us here is whether it has ETLENV defined:'; : '---------------------------------------------------------'; : "ETLENV = '$ETLENV'"; : '---------------------------------------------------------'; : 'At this point if ETLENV is empty we re-execute the the same file'; : 'with the same arguments under a shell forked for us by esh'; [ "$ETLENV" = '' ] && exec /data/sw/ETL/tools/esh $0 $@; : ''; : 'At this point ETL::Env has been used to configure %ENV'; : 'and esh has done the deed of re-execing our shell.'; : 'Neat thing is that we never had more than one process'; : 'running at once: the #! for this, perl to set the env'; : 'and the #! again when esh re-execed the program file.'; : ''; : "If this were AbInitio it would be Running With Environment:\n\n$(set)\n"; exit 0; #!/usr/bin/perl # newsh. # exec a new shell with default environment settings. use ETL; use ETL::Env; $ETL::Env::verbose = 1; exec ETL::Env->default->export->{SHELL}; die "Failed to exec: $!"; __END__ # devbox.env # # called from ETL::Env for additions to the default environment # on host "devbox". # # this gets evaled and should result in an anon. hash reference, # notice the lack of closing ';' and enclosing curly-braces ('}'). # # note: it is probably a bad idea to modify anything with # this code, just return the hash ref and go along with it. # # note: the PATH assignment needs to be last on the list to # avoid join swallowing all the arguments following it. # # the tools directory is where executables that go onto # production live. this entry should stay in all of the # .env files. # # qw() can be neater than a long list of foo:bar:blah entrys. # where there isn't much thinking going on. in this case the # entry will look like: { qw( varame value varname value ) } # # notice also the lack of a trailing ';'. # # $ENV{ETL} was set in the ETL.pm module and is a way of # localizing the location of things w/o having to insert # everything into the perl lib directory in a development # environment. { TZ => 'CST6CDT', FOO => 'BAR', PATH => join ':', $ENV{ETL} .'/tools', qw( /home/pvcs/bin /usr/ccs/bin /usr/pvcs/vm/solaris/bin /bin /usr/bin /usr/ucb /usr/bin/X11 /usr/local/bin ) } # perl lexer stops reading here. __END__