#!/usr/bin/perl -w

###########################################################################
#
# Baracus build and boot management framework
#
# Copyright (C) 2010 Novell, Inc, 404 Wyman Street, Waltham, MA 02451, USA.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the Artistic License 2.0, as published
# by the Perl Foundation, or the GNU General Public License 2.0
# as published by the Free Software Foundation; your choice.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  Both the Artistic
# Licesnse and the GPL License referenced have clauses with more details.
#
# You should have received a copy of the licenses mentioned
# along with this program; if not, write to:
#
# FSF, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110, USA.
# The Perl Foundation, 6832 Mulderstraat, Grand Ledge, MI 48837, USA.
#
###########################################################################

use strict;
use Getopt::Long qw( :config pass_through );
use Pod::Usage;
use File::Find;
use File::Basename;

use lib "/usr/share/baracus/perl";

use BaracusREPO   qw( :subs :vars );
use BaracusConfig qw( :subs :vars );
use BaracusCore   qw( :subs );
use BaracusSource qw( :subs :vars );

=pod

=head1 NAME

B<barepo> - baracus tools to manage user created YUM repositories

=head1 SYNOPSIS
 
B<barepo> E<lt>commandE<gt> [options]

Where E<lt>commandE<gt> is

    create  <...>  Create new repo.
    add     <...>  Add more packages to repo.
    update  <...>  Regenerate metafiles after add.
    remove  <...>  Remove repo.
    verify  <...>  Run some checks on repo health.
    list    <...>  List all, or matching, repos.
    detail  <...>  List the files in named repo.

    help    This 'Usage' summary message.
    man     Detailed man page.

Use 'man' or 'help <command>' for more details.

=head1 DESCRIPTION

This tool provides assistance to users wanting to create their own network install sources in  YUM software repositories.  These repos make included RPMs available for install/update over the network.

=head1 OPTIONS

=over 4

=item -v --verbose  Be verbose with output

=item -q --quiet  Be as quiet as possible

=item -n --nolabels  Do not print headers/footers

=back

=cut

my $man      = 0;
my $help     = 0;

my $cmds = {
            'create'   => \&create,
            'add'      => \&add,
            'update'   => \&update,
            'remove'   => \&remove,
            'verify'   => \&verify,
            'list'     => \&list,
            'detail'   => \&detail,
            'help'     => \&help,
            'man'      => \&man,
            };

my $opts = {
            verbose    => 0,
            quiet      => 0,
            all        => 0,
            nolabels   => 0,
            debug      => 0,
            execname   => "",
            LASTERROR  => "",
            };

# Build command line for help, historical reference and point-in-time rebuilding
$opts->{execname} = $0;
$opts->{execname} = $1 if ( $0 =~ m|^.*/([^/].+)| );

GetOptions(
           'verbose'   => \$opts->{verbose},
           'quiet'     => \$opts->{quiet},
           'all'       => \$opts->{all},
           'nolabels'  => \$opts->{nolabels},
           'debug+'    => \$opts->{debug},
           'man'       => \$man,
           'help|?'    => \$help,
           );

&man()  if $man;
&help( $opts, $cmds ) if $help;
&help( $opts, $cmds ) unless ( scalar @ARGV );
if ($ARGV[0] eq "man"  ) { &man(); }
if ($ARGV[0] eq "help" ) { shift @ARGV; &help( $opts, $cmds, @ARGV ); }

$opts->{baXML}    = &baxml_load_distros( $opts );

my $status;
$status = &main( $opts, $cmds, @ARGV );

print $opts->{LASTERROR} if $status;

exit $status;

die "DOES NOT EXECUTE";

###########################################################################

=head1 COMMANDS

=head2 create --repo <name> --distro <distro> [ --package="rpm list" ]

Creates the repository with <name> and populates it with any rpms listed provided in space seperated [rpm_list]

=cut

sub create() {

    my $command = 'create';
    my $status;

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $repo     = "";
    my $packages = "";
    my $distro   = "";

    @ARGV = @_;

    GetOptions(
               'repo=s'      => \$repo,
               'packages=s'  => \&multiarg_handler,
               'distro=s'    => \$distro,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    unless ( defined $repo ) {
        printf "\nMissing reponame <name>\n";
        &help( $opts, $cmds, $command );
    }

    &check_distro( $opts, $distro );

    $packages = $multiarg{ packages } if ( defined $multiarg{ packages } );

    my $dh =  &baxml_distro_gethash( $opts, $distro );
    unless ( defined $dh->{pkgtype} and defined $barepoType{ $dh->{pkgtype} } ) {
        $opts->{LASTERROR} = "provided repo package type not supported";
        return 1;
    }
    my $type = $barepoType{ $dh->{pkgtype} };

    print "Creating $repo \n" if ( $opts->{verbose} );
    $status = &create_repo_yum( $opts, $repo, $distro, $packages ) if ( $type == BA_REPO_YUM );
    $status = &create_repo_apt( $opts, $repo, $distro, $packages ) if ( $type == BA_REPO_APT );
    if ( $status >= 1 ) {
        return 1;
    }

    # enable_service is clever will start if needed or restart
    return &enable_service( $opts, "http");
}

=head2 add --repo <name> --distro <distro> --packages "<rpm list>"

Add the rpms listed in space seperated <rpm_list> to the repository with <name> for the distributuion <distro>. Also updates the repo metadata.

=cut

sub add() {

    my $command = 'add';
    my $status;

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $repo     = "";
    my $distro   = "";
    my $packages = "";

    @ARGV = @_;

    GetOptions(
               'repo=s'      => \$repo,
               'distro=s'    => \$distro,
               'packages=s'  => \&multiarg_handler,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    unless ( defined $repo ) {
        printf "\nMissing reponame <name>\n";
        &help( $command );
    }

    my $repodir = "$baDir{'byum'}/$repo";

    unless ( -d $repodir ) {
        $opts->{LASTERROR} = "Repo not found. Create with: barepo create --repo $repo --distro $distro\n";
        return 1;
    }

    &check_distro( $opts, $distro );

    $packages = $multiarg{ packages } if ( defined $multiarg{ packages } );

    my $dh =  &baxml_distro_gethash( $opts, $distro );
    unless ( defined $dh->{pkgtype} and defined $barepoType{ $dh->{pkgtype} } ) {
        $opts->{LASTERROR} = "provided repo package type not supported";
        return 1;
    }
    my $type = $barepoType{ $dh->{pkgtype} };

    print "Adding packages to $repo \n" if ( $opts->{verbose} );

    $status = &add_packages_yum( $opts, $repo, $distro, $packages ) if ( $type == BA_REPO_YUM );
    $status = &add_packages_apt( $opts, $repo, $distro, $packages ) if ( $type == BA_REPO_APT );
    if ( $status >= 1 ) {
        return 1;
    }

    return 0;

}

=head2 remove --repo <name>

Remove the repo directory and all related contents (repo distros and pkgs).

=cut

sub remove() {

    my $command = 'remove';
    my $status;

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }
    my $repo = "";

    @ARGV = @_;

    GetOptions(
               'repo=s' => \$repo,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    unless ( defined $repo ) {
        printf "\nMissing reponame <name>\n";
        &help( $opts, $cmds, $command );
    }

    if ( $opts->{verbose} ) { print "Removing $repo \n"; }
    $status = &remove_repo( $opts, $repo );
    if ( $status >= 1 ) {
        return 1;
    }

    return &enable_service( $opts, "http");
}

=head2 verify --repo <name>

Sanity check some repo files and states.

=cut

sub verify {

    my $command = 'verify';

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $repo = "";

    @ARGV = @_;

    GetOptions(
               'repo=s' => \$repo,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    unless ( defined $repo ) {
        printf "\nMissing reponame <name>\n";
        &help( $opts, $cmds, $command );
    }

    # barepo create related issues
    print "Verify directory and apache config... ";
    if ( &verify_repo_creation( $opts, $repo ) ) {
        print "\n$opts->{LASTERROR}\n";
        $opts->{LASTERROR} = "";
    } else {
        print "good.\n";
    }

    # barepo update related issues
    print "Verify metadata present... ";
    if ( &verify_repo_repodata( $opts, $repo ) ) {
        print "\n$opts->{LASTERROR}\n";
        $opts->{LASTERROR} = "";
    } else {
        print "good.\n";
    }

    # barepo keysign related issues
    print "Verify repodata gpg signed... ";
    if ( &verify_repo_sign( $opts, $repo ) ) {
        print "\n$opts->{LASTERROR}\n";
        $opts->{LASTERROR} = "";
    } else {
        print "good.\n";
    }

    return 0;
}

=head2 list [match]

list available repositories

=cut

sub list {

    my $command = 'list';

    my $repo = "";

    @ARGV = @_;

    GetOptions(
               'repo=s' => \$repo,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    printf "List repositories %s\n",
        defined $repo ? "matching $repo" : "" if ( $opts->{verbose} );

    if (defined $repo) {
        # shell to regex modification - if '*' passed prefix with '.'
        $repo =~ s|\*|.*|g;
    } else {
        $repo = "*";
    }
    print "repo $repo\n" if ( $opts->{debug} );

    unless ( $opts->{quiet} || $opts->{nolabels} ) {
        print "--------------------------------------------------------------------------------\n";
        printf "%-32s conf checks\n", "repository";
        print "--------------------------------------------------------------------------------\n";
    }

    # no repos is not really an error
    unless (-d $baDir{'byum'}) {
        return 0;
    }

    my ($create, $data);
    opendir(DIR, $baDir{'byum'});
    foreach my $mydir ( grep(/\b$repo\b/o, grep(!/^\.+$/, readdir(DIR))) ) {
        $create = &verify_repo_creation( $opts, $mydir );
        $data   = &verify_repo_repodata( $opts, $mydir );
        print "repo $mydir create $create data $data\n" if ( $opts->{debug} );
        printf "%-32s ", $mydir;
        unless ( $opts->{quiet} ) {
            printf "%-4s %-4s", $create ? 0 : 1, $data ? 0 : 1;
        }
        print "\n";
    }

    return 0;
}

=head2 detail --repo <name>

Display detailed information about the named repo

=cut

sub detail {

    my $command = 'detail';

    unless ( scalar @_ ) {
        &help( $opts, $cmds, $command );
    }

    my $repo = "";

    @ARGV = @_;

    GetOptions(
               'repo=s' => \$repo,
               );

    if ( scalar @ARGV ) {
        printf "\nUnknown arg(s): %s\n", join(" ", @ARGV);
        &help( $opts, $cmds, $command );
    }

    unless ( defined $repo ) {
        printf "\nMissing reponame <name>\n";
        &help( $opts, $cmds, $command );
    }

    my $repodir = "$baDir{'byum'}/$repo";

    unless ( -d $repodir ) {
        $opts->{LASTERROR} = "Unable to find repo named $repo\n";
        return 1;
    }

    unless ( $opts->{quiet} || $opts->{nolabels} ) {
        print "--------------------------------------------------------------------------------\n";
        print "File listing for repo '$repo'\n";
        print "--------------------------------------------------------------------------------\n";
    }

   find ( { wanted =>
                 sub {
                     if ( $_ =~ m/^.*\.(deb|rpm)\z/si ) {
                         print basename($_) . "\n";
                     }
                  },
                  follow =>1
          },
          "$repodir" );

    return 0;
}

die "ABSOLUTELY DOES NOT EXECUTE";

__END__
