Skip to content

Commit

Permalink
ObjectsController: support JSON exports (and REST)
Browse files Browse the repository at this point in the history
fixes #511
  • Loading branch information
Thomas-Gelf committed Apr 3, 2023
1 parent 71d793c commit cda9170
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 31 deletions.
9 changes: 9 additions & 0 deletions application/controllers/DatastoresController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,17 @@ public function indexAction()
$table = new DatastoreTable($this->db(), $this->url());
(new AdditionalTableActions($table, Auth::getInstance(), $this->url()))
->appendTo($this->actions());
if ($this->params->get('format') === 'json' || $this->getRequest()->isApiRequest()) {
$this->downloadTable($table, $this->translate('Datastores'));
return;
}
$this->showTable($table, 'vspheredb/datastores', $this->translate('Datastores'));
$summaries = new Summaries($table, $this->db(), $this->url());
$this->content()->prepend($summaries);
}

public function exportAction()
{
$this->sendExport('datastore');
}
}
9 changes: 9 additions & 0 deletions application/controllers/HostsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public function indexAction()
$table = new HostsTable($this->db(), $this->url());
(new AdditionalTableActions($table, Auth::getInstance(), $this->url()))
->appendTo($this->actions());
if ($this->params->get('format') === 'json' || $this->getRequest()->isApiRequest()) {
$this->downloadTable($table, $this->translate('Host Systems'));
return;
}
$this->showTable($table, 'vspheredb/hosts', $this->translate('Hosts'));
// Hint: handleSortUrl MUST be done AFTER showTable, otherwise
// eventuallyFilter and similar will not be applied
Expand All @@ -35,4 +39,9 @@ public function indexAction()
$summaries = new Summaries($table, $this->db(), $this->url());
$this->content()->prepend($summaries);
}

public function exportAction()
{
$this->sendExport('host_system');
}
}
9 changes: 9 additions & 0 deletions application/controllers/VmsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,20 @@ public function indexAction()
$table = new VmsTable($this->db(), $this->url());
(new AdditionalTableActions($table, Auth::getInstance(), $this->url()))
->appendTo($this->actions());
if ($this->params->get('format') === 'json' || $this->getRequest()->isApiRequest()) {
$this->downloadTable($table, $this->translate('Virtual Machines'));
return;
}
$this->showTable($table, 'vspheredb/vms', $this->translate('Virtual Machines'));
$summaries = new Summaries($table, $this->db(), $this->url());
$this->content()->prepend($summaries);
}

public function exportAction()
{
$this->sendExport('virtual_machine');
}

public function diskusageAction()
{
$this->handleTabs();
Expand Down
45 changes: 45 additions & 0 deletions doc/55-REST-API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<a id="REST_API">REST API</a>
=============================

In case you want to fetch data via REST API, the following endpoints have been
implemented:

vspheredb/vms/export
vspheredb/hosts/export
vspheredb/datastores/export

URL Parameters
--------------

| Parameter | Description |
|-----------------|-----------------------------------------------------------------|
| vcenter=<uuid> | UUID, e.g. de037ec0-20b7-4f63-a341-124f9977f15e |
| parent=<uuid> | Parent (folder) UUID, e.g. de037ec0-20b7-4f63-a341-124f9977f15e |
| showDescendants | Boolean, default true: whether to show all descendants of the |
| | specified parent, or just directly attached objects |
| | Syntax: `&showDescendants`, `&!showDescendants` |
| | Also supported: `&showDescendants=1`, `&showDescendants=0` |

This exportes a fixed set of properties, corresponding to those which are also
being exported to the Icinga Director. State-related properties do not make part
of this property set.

Main table URLs
---------------

If above export doesn't fit your needs, you could also check our main Host,
Virtual Machine or Datastore tables. They allow for custom columns, and provide
a related "Download" action, which is accessible via REST too:

vspheredb/vms
vspheredb/hosts
vspheredb/datastores

To get a REST/JSON response for those endpoints, please use `Accept: application/json`
in your request header. In addition to the columns for our `vspheredb/*/export` URLs,
they also support the following:

| Parameter | Description |
|-------------------------|-----------------------------------------------------------|
| columns=<col1>[,<colX>] | comma-separated list of column names. Please check our UI |
| | for a list of allowed values. They might change over time |
6 changes: 6 additions & 0 deletions doc/84-Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
v1.8.0 (unreleased)
-------------------

### UI
* FEATURE: two different download links for our main tables (#511)

### REST API
* Hosts, VirtualMachines and DataStores can now be exported via REST API (#511)

### Fixed issues
* You can find issues and feature requests related to this release on our
[roadmap](https://github.com/Icinga/icingaweb2-module-vspheredb/milestone/16?closed=1)
Expand Down
99 changes: 79 additions & 20 deletions library/Vspheredb/ProvidedHook/Director/ImportSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
use Icinga\Module\Director\Web\Form\QuickForm;
use Icinga\Module\Vspheredb\Db;
use Icinga\Module\Vspheredb\Db\DbUtil;
use Icinga\Module\Vspheredb\DbObject\VCenter;
use Icinga\Module\Vspheredb\Web\Table\TableWithParentFilter;
use Icinga\Module\Vspheredb\Web\Table\TableWithVCenterFilter;
use Ramsey\Uuid\Uuid;
use Zend_Db_Adapter_Abstract as ZfDb;
use function array_keys;
Expand All @@ -18,7 +21,7 @@
*
* This is where we provide an Import Source for the Icinga Director
*/
class ImportSource extends ImportSourceHook
class ImportSource extends ImportSourceHook implements TableWithVCenterFilter, TableWithParentFilter
{
protected $hostColumns = [
'object_name' => 'o.object_name',
Expand Down Expand Up @@ -81,6 +84,11 @@ class ImportSource extends ImportSourceHook
'tags' => 'o.tags',
];

/** @var ?array */
protected $parentFilterUuids = null;
/** @var ?array */
protected $vCenterFilterUuids = null;

public function getName()
{
return 'VMware vSphereDB';
Expand Down Expand Up @@ -168,6 +176,8 @@ public function fetchData(): array
default:
return [];
}
$this->applyOptionalVCenterFilter($db, $query);
$this->applyOptionalParentFilter($query);
$result = $db->fetchAll(
$this->eventuallyFilterVCenter($this->joinVCenter($query))
);
Expand All @@ -177,30 +187,35 @@ public function fetchData(): array
}

foreach ($result as $row) {
$row->uuid = Uuid::fromBytes(DbUtil::binaryResult($row->uuid))->toString();
if (in_array($objectType, ['host_system', 'virtual_machine'])) {
if ($row->custom_values !== null) {
$row->custom_values = JsonString::decode($row->custom_values);
}
}
if ($objectType === 'virtual_machine') {
$row->template = $row->template === 'y';
if ($row->guest_ip_addresses !== null) {
$addresses = [];
foreach ((array) JsonString::decode($row->guest_ip_addresses) as $if) {
foreach ($if->addresses as $info) {
if ($info->state !== 'unknown') {
$addresses[] = $info->address . '/' . $info->prefixLength;
}
}
static::convertDbRowToJsonData($row);
}

return $result;
}

public static function convertDbRowToJsonData($row)
{
$row->uuid = Uuid::fromBytes(DbUtil::binaryResult($row->uuid))->toString();
if (isset($row->custom_values)) {
$row->custom_values = JsonString::decode($row->custom_values);
}
if (isset($row->template)) {
$row->template = $row->template === 'y';
}
if (isset($row->guest_ip_addresses)) {
$addresses = [];
foreach ((array) JsonString::decode($row->guest_ip_addresses) as $if) {
foreach ($if->addresses as $info) {
if ($info->state !== 'unknown') {
$addresses[] = $info->address . '/' . $info->prefixLength;
}
$row->guest_ip_addresses = $addresses;
}
}
$row->guest_ip_addresses = $addresses;
}
if (isset($row->tags)) {
$row->tags = JsonString::decode($row->tags);
}

return $result;
}

protected function prepareVmQuery(ZfDb $db)
Expand Down Expand Up @@ -298,4 +313,48 @@ public static function getDefaultKeyColumnName(): ?string
{
return 'object_name';
}

public function filterVCenter(VCenter $vCenter): self
{
return $this->filterVCenterUuids([$vCenter->getUuid()]);
}

public function filterVCenterUuids(?array $uuids): self
{
$this->vCenterFilterUuids = $uuids;
return $this;
}

public function filterParentUuids(?array $uuids)
{
$this->parentFilterUuids = $uuids;
}

protected function applyOptionalParentFilter($query)
{
if ($this->parentFilterUuids === null) {
return;
}

$query->where('o.parent_uuid IN (?)', $this->parentFilterUuids);
}

protected function applyOptionalVCenterFilter(ZfDb $db, $query)
{
$uuids = $this->vCenterFilterUuids;
if ($uuids === null) {
return;
}
if (empty($uuids)) {
$query->where('1 = 0');
return;
}

$column = 'vc.instance_uuid';
if (count($uuids) === 1) {
$query->where("$column = ?", DbUtil::quoteBinaryCompat(array_shift($uuids), $db));
} else {
$query->where("$column IN (?)", DbUtil::quoteBinaryCompat($uuids, $db));
}
}
}
42 changes: 37 additions & 5 deletions library/Vspheredb/Web/Controller/ObjectsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
namespace Icinga\Module\Vspheredb\Web\Controller;

use gipfl\IcingaWeb2\Link;
use gipfl\IcingaWeb2\Url;
use Icinga\Module\Vspheredb\DbObject\VCenter;
use Icinga\Module\Vspheredb\ProvidedHook\Director\ImportSource;
use Icinga\Module\Vspheredb\Util;
use Icinga\Module\Vspheredb\Web\Form\FilterVCenterForm;
use Icinga\Module\Vspheredb\Web\Table\TableWithParentFilter;
use Icinga\Module\Vspheredb\Web\Table\TableWithVCenterFilter;
use ipl\Html\Html;
use Icinga\Module\Vspheredb\PathLookup;
use Icinga\Module\Vspheredb\Web\Controller;
Expand All @@ -14,6 +18,8 @@

class ObjectsController extends Controller
{
use RestApi;

protected $otherTabActions = [];

/** @var PathLookup */
Expand Down Expand Up @@ -60,9 +66,12 @@ protected function addTreeViewToggle()
}
}

protected function eventuallyFilterByParent(ObjectsTable $table, $url, $defaultTitle = null)
protected function eventuallyFilterByParent(TableWithParentFilter $table, $url, $defaultTitle = null)
{
$parent = $this->params->get('uuid');
$parent = $this->params->get('parent');
if ($parent === null) {
$parent = $this->params->get('uuid');
}
if ($parent !== null) {
$parent = Uuid::fromString($parent)->getBytes();
}
Expand All @@ -87,7 +96,7 @@ protected function eventuallyFilterByParent(ObjectsTable $table, $url, $defaultT
}
}

protected function eventuallyFilterByVCenter(ObjectsTable $table)
protected function eventuallyFilterByVCenter(TableWithVCenterFilter $table)
{
$this->getRestrictionHelper()->restrictTable($table);
$this->getVCenterFilterForm();
Expand Down Expand Up @@ -135,6 +144,29 @@ protected function renderTableWithCount(ObjectsTable $table, $title = null)
}
}

protected function downloadTable(ObjectsTable $table, string $title)
{
$this->eventuallyFilterByParent($table, Url::fromPath(''), $title);
$this->eventuallyFilterByVCenter($table);
$query = $table->getQuery();
$rows = $this->db()->getDbAdapter()->fetchAll($query);
foreach ($rows as $row) {
ImportSource::convertDbRowToJsonData($row);
}
$this->downloadJson($this->getResponse(), $rows, "$title.json");
}

protected function sendExport($type)
{
$import = new ImportSource();
$import->setSettings([
'object_type' => $type
]);
$this->eventuallyFilterByVCenter($import);
$this->eventuallyFilterByParent($import, $this->url());
$this->downloadJson($this->getResponse(), $import->fetchData(), $type . 's.json');
}

protected function addPathTo($parent, $url)
{
$lookup = $this->pathLookup();
Expand All @@ -147,7 +179,7 @@ protected function addPathTo($parent, $url)
$path->add(' > ');
}
$path->add(Link::create($name, $url, [
'uuid' => Util::niceUuid($uuid),
'parent' => Util::niceUuid($uuid),
'showDescendants' => true,
]));
}
Expand Down Expand Up @@ -196,7 +228,7 @@ protected function getParentParamsToPreserve()
return $urlParams;
}

protected function pathLookup()
protected function pathLookup(): PathLookup
{
if ($this->pathLookup === null) {
$this->pathLookup = new PathLookup($this->db()->getDbAdapter());
Expand Down
Loading

0 comments on commit cda9170

Please sign in to comment.