#!/usr/bin/perl

use strict;
use warnings;

use lib './lib';
use setup;

use English qw(-no_match_vars) ;
use Getopt::Long;
use Pod::Usage;
use UNIVERSAL::require;
use File::Temp;

use GLPI::Agent::Task::ESX;
use GLPI::Agent::Logger;
use GLPI::Agent::Config;
use GLPI::Agent::Target::Local;
use GLPI::Agent::Version;

my $options = {
    path    => '-'
};

GetOptions(
    $options,
    'host=s',
    'user=s',
    'password=s',
    'directory=s', # deprecated in favour of path or stdout
    'path=s',
    't|timeout=i',
    'stdout',
    'tag=s',
    'debug+',
    'help',
    'version',
    'dump',
    'dumpfile=s',
    'json',
    'glpi-version=s',
    'esx-itemtype=s',
) or pod2usage(-verbose => 0);

pod2usage(-verbose => 0, -exitstatus => 0) if $options->{help};

if ($options->{version}) {
    my $PROVIDER = $GLPI::Agent::Version::PROVIDER;
    map { print $_."\n" }
        "glpi-esx $GLPI::Agent::Task::ESX::VERSION",
        "based on $PROVIDER Agent v$GLPI::Agent::Version::VERSION",
        @{$GLPI::Agent::Version::COMMENTS};
    exit 0;
}

my $config = GLPI::Agent::Config->new(
    options => { map { $_ => $options->{$_} } qw{debug tag json glpi-version esx-itemtype} }
);

my $logger = GLPI::Agent::Logger->new(config => $config);

my $esx = GLPI::Agent::Task::ESX->new(
    logger => $logger,
    config => $config,
    target => GLPI::Agent::Target::Local->new(
        logger     => $logger,
        path       => $options->{path},
        json       => $config->{json},
        basevardir => File::Temp->newdir(CLEANUP => 1),
    ),
);

# Set timeout to use during first http connection attempt
$esx->timeout($config->{timeout}) if $options->{timeout};

inventory_from_dump($options->{dumpfile})
    if $options->{dumpfile};

$options->{path} = '-' if $options->{stdout};
$options->{path} = $options->{directory} unless $options->{path};

pod2usage(-verbose => 0) unless
    $options->{host}      &&
    $options->{user}      &&
    $options->{password};

if ($options->{dump}) {
    die "Data::Dumper perl module needed for --dump option. Please install it.\n"
        unless Data::Dumper->require();
}

if (!$esx->connect(
    host     => $options->{host},
    user     => $options->{user},
    password => $options->{password},
)) {
    die "Connection failure: ".$esx->lastError()."\n";
}

# Reset timeout to a minimum value for inventory related requests
my $safe_timeout = $config->{'backend-collect-timeout'} // 60;
$esx->timeout($safe_timeout) if $options->{timeout} && $options->{timeout} < $safe_timeout;

$esx->serverInventory($options->{path}, $options->{dump} ? \&dump_from_hostfullinfo : undef);

exit($esx->lastError ? 1 : 0);

sub dump_from_hostfullinfo {
    my ($hostId, $file) = @_
        or return;

    return unless $esx;

    my $host = $esx->{vpbs}->getHostFullInfo($hostId);
    my $dumper = Data::Dumper->new([$host],["host"]);
    if (!$file || $file eq '-') {
        $file = $hostId."-hostfullinfo.dump";
    } else {
        $file =~ s/\.(xml|json)$/-hostfullinfo.dump/;
    }

    if (open my $handle, '>', $file) {
        print $handle $dumper->Dump();
        close $handle;
        print "ESX Host full info dump saved in $file\n";
    } else {
        $esx->lastError("Can't save host full info dump");
        print STDERR "Can't write to $file: $ERRNO\n" ;
    }
}

sub inventory_from_dump {
    my ($file) = @_;

    my $format = $options->{json} ? 'json' : 'xml';

    $file =~ s/\-hostfullinfo.dump(.gz)?$/.$format/;

    die "dumpfile must be a *-hostfullinfo.dump or *-hostfullinfo.dump.gz file\n"
        if $file eq $options->{dumpfile};

    our $host ;

    my $fh;

    # Support gzip compressed hostfullinfo.dump
    my $mode = $options->{dumpfile} =~ /\.gz$/ ? "-|" : "<";
    my $expr = $options->{dumpfile} =~ /\.gz$/ ? "gunzip -c $options->{dumpfile}" : $options->{dumpfile};
    open $fh, $mode, $expr
        or die "Can't open $options->{dumpfile}: $!\n";
    my $dump = join("", <$fh>);
    close($fh);

    {
        ## no critic (ProhibitStringyEval)
        local ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} = 0;
        eval($dump)
            or die "Can't load $options->{dumpfile}: $!\n";
    }

    die "Can't load host full infos from $options->{dumpfile}\n"
        unless $host;

    $esx->{vpbs} = GLPI::Agent::Task::ESX::Dump->new($host);

    $esx->serverInventory($options->{path} // $file);

    exit 0;
}

package
    GLPI::Agent::Task::ESX::Dump;

use parent qw(GLPI::Agent::SOAP::VMware::Host);

sub new {
    my ($class, $fullinfo) = @_;
    return bless { _fullinfo => $fullinfo }, $class;
}

sub getHostFullInfo {
    my ($self) = @_;
    return $self->{_fullinfo};
}

sub getHostIds {
    my ($self) = @_;
    return [ $self->getHostname() ];
}

__END__

=head1 NAME

glpi-esx - vCenter/ESX/ESXi remote inventory from command line

=head1 SYNOPSIS

glpi-esx --host <host> --user <user> --password <password> --path <directory or file>

  Options:
    --help                 this menu
    --debug                debug mode (false)
    --host hostname        ESX server hostname
    --user username        user name
    --password xxxx        user password
    --path path            output directory or file
    -t --timeout sec       connection attempt timeout in seconds (defaults to 180)
    --stdout               dump inventory on stdout (enabled by default if no path is set)
    --tag tag              tag for the inventoried machine
    --json                 use json as format for generated inventories
    --glpi-version VERSION set targeted glpi version to enable supported features
    --esx-itemtype=TYPE    set ESX asset type for target supporting genericity like GLPI 11+

  Advanced options:
    --dump                 also dump esx host full info datas in a *-hostfullinfo.dump file
    --dumpfile file        generate one inventory from a *-hostfullinfo.dump file

=head1 EXAMPLES

    % glpi-esx --host myesx --user foo --password bar --path /tmp --json

You can import the .json file in your inventory server with the glpi-injector tool.

    % glpi-injector -v --file /tmp/*.json -u https://example/

=head1 DESCRIPTION

F<glpi-esx> creates inventory of remote ESX/ESXi and vCenter VMware.
It uses the SOAP interface of the remote server.

Supported systems start from:

=over 4

=item F<ESX and ESXi 3.5>

=item F<ESX and ESXi 4.1>

=item F<ESXi 5.0>

=item F<vCenter 4.1>

=item F<vCenter 5.0>

=back


Active Directory users, please note the AD authentication doesn't work. You must
create a account on the VMware server.

=head1 LIMITATION

So far, ESX serial number are not collected.

=head1 SECURITY

The SSL hostname check of the server is disabled.
