Skip to content

Instantly share code, notes, and snippets.

@skunkie
Last active June 17, 2022 08:38
Show Gist options
  • Save skunkie/cbaa4512c5ce7983eee3e13aa8db7624 to your computer and use it in GitHub Desktop.
Save skunkie/cbaa4512c5ce7983eee3e13aa8db7624 to your computer and use it in GitHub Desktop.
Automatic collection of info from vCenter, to GLPI
#!/usr/bin/perl -w
#
# This tool can be used to push inventory of virtual machines and ESXi hosts from a VIServer in GLPI via FusionInventory plugin
use v5.10;
use strict;
use warnings;
use VMware::VIRuntime;
use POSIX 'ceil';
use POSIX 'strftime';
use XML::TreePP;
# FusionInventory module
use Encode;
use Compress::Zlib;
use LWP::UserAgent;
use DBI;
use Data::Dumper; # for dev only
$Util::script_version = "1.0";
$ENV{"PERL_LWP_SSL_VERIFY_HOSTNAME"} = 0;
my %opts = (
fusion_url => {
type => "=s",
help => "Specify the url for GLPI FusionInventory plugin",
required => 1,
},
db_cnf => {
type => "=s",
help => "Specify the path to the config file to make connection to the GLPI database",
required => 1,
},
vm_name => {
type => "=s",
help => "Import only this virtual machine from the VIServer",
required => 0,
},
sync_location => {
type => "",
help => "Sync location by updating the table glpi_locations in the GLPI database",
required => 0,
},
test => {
type => "",
help => "Only print XML data, for debugging",
required => 0,
},
);
Opts::add_options(%opts);
# read/validate options and connect to the VIServer
Opts::parse();
Opts::validate();
Util::connect();
# declare all variables
my (
$clusters,
$clusters_names_ids,
$clusters_view,
$customFieldsMgr,
$db_cnf,
$dbh,
$dsn,
$field_vrm_owner,
$locations_ids,
$now,
$now_dev_id,
$sc,
$vihosts,
$vihosts_view,
$vm_views,
@glpi_computers,
);
# declare all subroutines
sub makeXML; # convert a perl object to XML
sub sendContent; # send the XML data to GLPI
sub dbFetchRow; # make an SQL query and return one resulting row (hashref)
sub dbFetchAll; # make an SQL query and return all resulting rows(hashref)
sub dbUpdate; # make an SQL update statement
sub syncComment; # syncronize the 'comment' field in table ' glpi_computers'
sub syncLocation; # syncronize the 'locations_id' field in table ' glpi_computers'
# path to the config file to make connection to the GLPI database
$db_cnf = Opts::get_option('db_cnf');
# connect to the GLPI database
$dsn = "DBI:mysql:mysql_read_default_file=$db_cnf";
$dbh = DBI->connect($dsn, undef, undef, {RaiseError => 1, PrintError => 0, mysql_enable_utf8 => 1})
or die("Could not connect to database: $DBI::errstr");
# get info on all locations_ids in the GLPI database
$locations_ids = dbFetchAll('name', 'SELECT name, id FROM glpi_locations');
# get info on the custom field "VRM Owner"
$sc = Vim::get_service_content() or die("Cannot get info on custom fields from VIServer");
$customFieldsMgr = Vim::get_view(mo_ref => $sc->customFieldsManager);
foreach (@{$customFieldsMgr->field}) {
if ($_->name eq "VRM Owner") {
$field_vrm_owner = $_->key;
last;
}
}
# get info on clusters in the VIServer
$clusters_view = Vim::find_entity_views(view_type => 'ClusterComputeResource',
properties => [
'name',
'host',
],
) or die("Cannot get info on clusters from the VIServer");
foreach my $cluster (@$clusters_view) {
$clusters_names_ids->{$cluster->{name}} = $locations_ids->{$cluster->{name}}->{id};
foreach my $vihost (@{$cluster->{host}}) {
$clusters->{$vihost->{value}} = $locations_ids->{$cluster->{name}};
}
}
# get info on CPUS of all hosts in the VIServer
$vihosts_view = Vim::find_entity_views(view_type => 'HostSystem',
properties => [
'config.product',
'hardware.biosInfo',
'name',
'summary.hardware',
'summary.host',
],
) or die("Cannot get info on hosts from the VIServer");
foreach my $vihost (@$vihosts_view) {
$vihosts->{$vihost->{'summary.host'}->{value}} = {
'name' => $vihost->{name},
'cpuModel' => $vihost->{'summary.hardware'}->{cpuModel},
'cpuMhz' => $vihost->{'summary.hardware'}->{cpuMhz},
};
}
# get info on all VMs, not templates
my %filter_hash = (
'config.template' => 'false',
);
if (Opts::get_option('vm_name')) {
$filter_hash{'name'} = Opts::get_option('vm_name');
}
$vm_views = Vim::find_entity_views(view_type => 'VirtualMachine',
filter => { %filter_hash },
properties => ['customValue',
'name',
'guest',
'config',
'runtime',
],
) or die("Cannot get info on virtual machines from the VIServer");
# disconnect from the VIServer
Util::disconnect();
# get current datetime
$now = strftime "%Y-%m-%d %H:%M:%S", localtime;
$now_dev_id = strftime "-%Y-%m-%d-%H-%M-%S", localtime;
foreach my $vm (@$vm_views) {
my (
$content,
$content_xml,
$db_fetch,
$description,
$deviceid,
$inventory,
$locations_id,
$nameCPU,
$speedCPU,
$vm_name,
$vm_owner,
);
$vm_name = $vm->{name};
push @glpi_computers, $vm_name;
$deviceid = $vm_name . $now_dev_id;
# START ACCESSLOG
$content->{ACCESSLOG}->{LOGDATE} = $now;
# END ACCESSLOG
# START ACCOUNTINFO
$content->{ACCOUNTINFO} = {KEYNAME => 'TAG',
KEYVALUE => $clusters->{$vm->{runtime}->{host}->{value}}->{name},
};
# END ACCOUNTINFO
# START BIOS
$content->{BIOS} = {MMODEL => '440BX Desktop Reference Platform',
MSN => 'None',
SKUNUMBER => 'Not Specified',
SMANUFACTURER => 'VMware, Inc.',
SMODEL => 'VMware Virtual Platform',
SSN => $vm->{config}->{uuid},
};
# END BIOS
# START CPUS
$nameCPU = ($vihosts->{$vm->{runtime}->{host}->{value}})->{cpuModel};
$speedCPU = ($vihosts->{$vm->{runtime}->{host}->{value}})->{cpuMhz};
push @{$content->{CPUS}}, ({CORE => 1,
NAME => $nameCPU,
SPEED => $speedCPU, }) x $vm->{config}->{hardware}->{numCPU};
# END CPUS
# START DRIVES
foreach my $disk (@{$vm->{guest}->{disk}}) {
push @{$content->{DRIVES}}, {FREE => int($disk->{freeSpace} / (1024 ** 2)),
TOTAL => int($disk->{capacity} / (1024 ** 2)),
TYPE => $disk->{diskPath},
};
}
# END DRIVES
# START HARDWARE
$description = $vm->{config}->{annotation};
$content->{HARDWARE} = {CHASSIS_TYPE => 'Virtual Server',
DESCRIPTION => $description,
IPADDR => $vm->{guest}->{ipAddress},
MEMORY => $vm->{config}->{hardware}->{memoryMB},
NAME => $vm->{name},
OSNAME => $vm->{guest}->{guestFullName},
PROCESSORN => $vm->{config}->{hardware}->{numCPU},
PROCESSORS => $speedCPU,
PROCESSORT => $nameCPU,
UUID => $vm->{config}->{uuid},
VMSYSTEM => 'VMware',
WORKGROUP => $vm->{guest}->{ipStack} ? $vm->{guest}->{ipStack}->[0]->{dnsConfig}->{domainName} : '',
};
# END HARDWARE
# START MEMORIES
$content->{MEMORIES} = {CAPTION => 'RAM slot #0',
DESCRIPTION => 'DIMM',
MEMORYCORRECTION => 'None',
NUMSLOTS => 1,
TYPE => 'DRAM',
CAPACITY => $vm->{config}->{hardware}->{memoryMB},
};
# END MEMORIES
# START NETWORKS
foreach my $net (@{$vm->{guest}->{net}}) {
my $ipaddress;
if ($net->{ipAddress}) {
if (ref($net->{ipAddress}) eq 'ARRAY') {
$ipaddress = $net->{ipAddress}[0]; # only IPv4 address
}
else {
$ipaddress = $net->{ipAddress};
}
}
push @{$content->{NETWORKS}}, {DESCRIPTION => $net->{network},
DRIVER => '',
IPADDRESS => $ipaddress,
MACADDR => $net->{macAddress},
STATUS => $net->{connected} == 1 ? 'Up' : 'Down',
TYPE => 'Ethernet',
VIRTUALDEV => 0,
};
}
# END NETWORKS
# START OPERATINGSYSTEM
# do not send the full name of the OS for RHEL as it is sent by the fusioninventory agent from within the OS
$db_fetch = dbFetchRow('SELECT go.name FROM glpi_operatingsystems AS go JOIN glpi_computers AS gc ON go.id = gc.operatingsystems_id where gc.name = ?', $vm_name);
$content->{OPERATINGSYSTEM} = {FULL_NAME => ($db_fetch->{name} and $vm->{guest}->{guestFullName} and $vm->{guest}->{guestFullName} =~ m|Red Hat Enterprise Linux|)
? $db_fetch->{name} : $vm->{guest}->{guestFullName},
KERNEL_NAME => $vm->{guest}->{guestId},
NAME => $vm->{guest}->{guestFamily},
};
# END OPERATINGSYSTEM
# START STORAGES
foreach my $device (@{$vm->{config}->{hardware}->{device}}) {
if (ref($device) eq 'VirtualDisk') {
push @{$content->{STORAGES}}, {DESCRIPTION => $device->{deviceInfo}->{label},
DISKSIZE => (int($device->{capacityInKB} / 1024)),
INTERFACE => 'SCSI',
MANUFACTURER => 'VMware',
MODEL => 'Virtualdisk ',
NAME => $device->{backing}->{fileName},
SERIALNUMBER => $device->{backing}->{uuid},
TYPE => 'disk',
};
}
}
# END STORAGES
# START USERS
### get VRM Owner of the VM
if ($field_vrm_owner and $vm->customValue) {
foreach my $customValue (@{$vm->customValue}) {
if ($customValue->key eq $field_vrm_owner) {
# domain\vpupkin aka down-level logon name
if ($customValue->value =~ m|\\(.+)$|) {
$vm_owner = $1;
}
# [email protected] aka user principal name
elsif ($customValue->value =~ m|^(.+)@|) {
$vm_owner = $1;
}
# vpupkin
else {
$vm_owner = $customValue->value;
}
last;
}
}
$content->{USERS}->{LOGIN} = $vm_owner;
}
# END USERS
# START VERSIONCLIENT
$content->{VERSIONCLIENT} = 'Perl Script for VMware';
# END VERSIONCLIENT
$inventory = {
content => $content,
deviceid => $deviceid,
};
$content_xml = makeXML($inventory);
if (Opts::get_option('test')) {
print $content_xml;
}
else {
# send the XML to the FusionInventory plugin
sendContent($content_xml);
# make changes in the table glpi_computers of the GLPI database, comment and location fields
$db_fetch = dbFetchRow('SELECT comment, locations_id FROM glpi_computers WHERE name = ?', $vm_name);
syncComment($db_fetch, $vm_name, $description);
if (Opts::get_option('sync_location')) {
$locations_id = $clusters->{$vm->{runtime}->{host}->{value}}->{id};
syncLocation($db_fetch, $vm_name, $locations_id);
}
}
}
if (!Opts::get_option('vm_name') and !Opts::get_option('test')) {
foreach my $vihost (@$vihosts_view) {
my (
$content,
$content_xml,
$db_fetch,
$description,
$deviceid,
$inventory,
$locations_name,
$locations_id,
$memoryMB,
$nameCPU,
$speedCPU,
$vihost_name,
);
if ($vihost->{name} =~ m|^([0-9]{1,3}\.){3}[0-9]{1,3}$|) {
$vihost_name = $vihost->{name};
}
else {
$vihost_name = (split(/\./, $vihost->{name}))[0];
}
push @glpi_computers, $vihost_name;
$deviceid = $vihost_name . $now_dev_id;
$locations_name = $clusters->{$vihost->{mo_ref}->{value}}->{name} ? $clusters->{$vihost->{mo_ref}->{value}}->{name} : '';
$description = 'VMware hypervisor in cluster ' . $locations_name;
$memoryMB = ceil($vihost->{'summary.hardware'}->{memorySize} / 1024 ** 2);
# START ACCESSLOG
$content->{ACCESSLOG}->{LOGDATE} = $now;
# END ACCESSLOG
# START ACCOUNTINFO
$content->{ACCOUNTINFO} = {KEYNAME => 'TAG',
KEYVALUE => $locations_name,
};
# END ACCOUNTINFO
# START BIOS
$db_fetch = dbFetchRow('SELECT serial FROM glpi_computers where name = ?', $vihost_name);
$content->{BIOS} = {BDATE => $vihost->{'hardware.biosInfo'}->{releaseDate},
BVERSION => $vihost->{'hardware.biosInfo'}->{biosVersion},
MSN => 'None',
SKUNUMBER => 'Not Specified',
SMANUFACTURER => $vihost->{'summary.hardware'}->{vendor},
SMODEL => $vihost->{'summary.hardware'}->{model},
SSN => $db_fetch->{serial},
};
# END BIOS
# START CPUS
$nameCPU = $vihost->{'summary.hardware'}->{cpuModel};
$speedCPU = $vihost->{'summary.hardware'}->{cpuMhz};
push @{$content->{CPUS}}, ({CORE => 1,
NAME => $nameCPU,
SPEED => $speedCPU, }) x $vihost->{'summary.hardware'}->{numCpuPkgs};
# END CPUS
# START HARDWARE
$content->{HARDWARE} = {CHASSIS_TYPE => $vihost->{'summary.hardware'}->{vendor},
DESCRIPTION => 'VMware hypervisor in cluster ' . $locations_name,
MEMORY => $memoryMB,
NAME => $vihost_name,
OSNAME => $vihost->{'config.product'}->{fullName},
PROCESSORN => $vihost->{'summary.hardware'}->{numCpuPkgs},
PROCESSORS => $speedCPU,
PROCESSORT => $nameCPU,
VMSYSTEM => $vihost->{'config.product'}->{licenseProductName},
};
# END HARDWARE
# START MEMORIES
$content->{MEMORIES} = {CAPTION => 'RAM slot #0',
DESCRIPTION => 'DIMM',
MEMORYCORRECTION => 'None',
NUMSLOTS => 1,
TYPE => 'DRAM',
CAPACITY => $memoryMB,
};
# END MEMORIES
# START OPERATINGSYSTEM
$content->{OPERATINGSYSTEM} = {FULL_NAME => $vihost->{'config.product'}->{fullName},
KERNEL_NAME => $vihost->{'config.product'}->{osType},
NAME => $vihost->{'config.product'}->{name},
};
# END OPERATINGSYSTEM
# START VERSIONCLIENT
$content->{VERSIONCLIENT} = 'Perl Script for VMware';
# END VERSIONCLIENT
$inventory = {
content => $content,
deviceid => $deviceid,
};
$content_xml = makeXML($inventory);
# send the XML to the FusionInventory plugin
sendContent($content_xml);
# make changes in the table glpi_computers of the GLPI database, comment and location fields
$db_fetch = dbFetchRow('SELECT comment, locations_id FROM glpi_computers WHERE name = ?', $vihost_name);
syncComment($db_fetch, $vihost_name, $description);
if (Opts::get_option('sync_location')) {
if ($locations_id = $clusters->{$vihost->{mo_ref}->{value}}->{id}) {
syncLocation($db_fetch, $vihost_name, $locations_id);
}
}
}
# set field is_deleted to 1 in the table glpi_computers for those virtual machine that are not present in the VIServer
# on condition that their field locations_id is in the ids of the VIServer clusters
my $glpi_computers = dbFetchAll('name', 'SELECT name, locations_id, is_deleted FROM glpi_computers WHERE name IS NOT NULL');
my @ids = values %$clusters_names_ids;
foreach my $glpi_computer (values %$glpi_computers) {
if ($glpi_computer->{locations_id} ~~ @ids) {
if (!($glpi_computer->{name} ~~ @glpi_computers) and $glpi_computer->{is_deleted} == 0) {
dbUpdate('UPDATE glpi_computers SET is_deleted = 1 WHERE name = ?', $glpi_computer->{name});
}
# set field is_deleted to 0 if the name of the VM is reused in the VIServer
if (($glpi_computer->{name} ~~ @glpi_computers) and $glpi_computer->{is_deleted} == 1) {
dbUpdate('UPDATE glpi_computers SET is_deleted = 0 WHERE name = ?', $glpi_computer->{name});
}
}
}
}
$dbh->disconnect();
sub makeXML {
my $inventory = shift;
my $tpp = XML::TreePP->new(indent => 2);
$tpp->write({
REQUEST => {
CONTENT => $inventory->{content},
DEVICEID => $inventory->{deviceid},
QUERY => "INVENTORY",
}
});
}
sub sendContent {
my $content = shift;
my $ua = LWP::UserAgent->new(
agent => 'FusionInventory-Injector',
parse_head => 0, # No need to parse HTML
keep_alive => 1,
requests_redirectable => ['POST', 'GET', 'HEAD']
);
my $request = HTTP::Request->new(POST => Opts::get_option('fusion_url'));
$request->header(
'Pragma' => 'no-cache',
'Content-type' => 'Application/x-compress'
);
$request->content(compress(encode_utf8($content)));
my $res = $ua->request($request);
return $res->is_success();
}
sub dbFetchRow {
my ($query, $bind_var) = @_;
my $sth = $dbh->prepare($query);
$sth->execute($bind_var);
return $sth->fetchrow_hashref();
}
sub dbFetchAll {
my $key_field = shift;
my $query = shift;
my $sth = $dbh->prepare($query);
$sth->execute();
return $sth->fetchall_hashref($key_field);
}
sub dbUpdate {
my ($update, $bind_var1, $bind_var2) = @_;
my $sth = $dbh->prepare($update);
if (defined $bind_var2) {
$sth->execute($bind_var1, $bind_var2);
} else {
$sth->execute($bind_var1);
}
}
sub syncComment {
my ($db_fetch, $computer_name, $description) = @_;
if (!$db_fetch->{comment} or ($db_fetch->{comment} and $db_fetch->{comment} ne $description)) {
dbUpdate('UPDATE glpi_computers SET comment = ? WHERE name = ?', $description, $computer_name);
}
}
sub syncLocation {
my ($db_fetch, $computer_name, $locations_id) = @_;
if ($db_fetch->{locations_id} and $db_fetch->{locations_id} ne $locations_id) {
dbUpdate('UPDATE glpi_computers SET locations_id = ? WHERE name = ?', $locations_id, $computer_name);
}
}
__END__
=head1 NAME
glpi_import_from_vc.pl - A tool to push inventory of virtual machines and ESXi hosts from a VIServer in GLPI via FusionInventory plugin.
=head1 SYNOPSIS
glpi_import_from_vc.pl [options]
Options:
--server (variable VI_SERVER, default 'localhost')
--fusion_url GLPI fusion inventory plugin URL
--db_cnf Specify the path to the config file to make connection to the GLPI database
--vm_name Import only this virtual machine from the VIServer
--sync_location Sync location by updating the table glpi_locations in the GLPI database
--test Only print XML data, for debugging
Examples:
glpi_import_from_vc.pl --server vcenter.domain.corp --credstore /opt/scripts/vicredentials.xml \
--fusion_url http://fusion.domain.corp/glpi/plugins/fusioninventory/front/plugin_fusioninventory.communication.php --db_cnf /opt/scripts/glpi_db.cnf
=head1 DESCRIPTION
This tool can be used to push inventory of virtual machines and ESXi hosts from a VIServer in GLPI via FusionInventory plugin
@kdziuba
Copy link

kdziuba commented Jun 25, 2021

hello,
great job!!!
what is vicredentials.xml and glpi_db.cnf format?

thx4help :)
Konrad

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment