You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
14 KiB
432 lines
14 KiB
<?php declare(strict_types = 0);
|
|
/*
|
|
** Zabbix
|
|
** Copyright (C) 2001-2023 Zabbix SIA
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** 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. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
|
|
class CTemplateImporter extends CImporter {
|
|
|
|
/**
|
|
* @var array A list of template IDs which were created or updated.
|
|
*/
|
|
protected $processed_templateids = [];
|
|
|
|
/**
|
|
* Import templates.
|
|
*
|
|
* @throws Exception
|
|
*
|
|
* @param array $templates
|
|
*/
|
|
public function import(array $templates) {
|
|
$templates = zbx_toHash($templates, 'host');
|
|
|
|
$this->checkCircularTemplateReferences($templates);
|
|
|
|
if (!$this->options['templateLinkage']['createMissing']
|
|
&& !$this->options['templateLinkage']['deleteMissing']) {
|
|
foreach ($templates as $name => $template) {
|
|
unset($templates[$name]['templates']);
|
|
}
|
|
}
|
|
|
|
do {
|
|
$independent_templates = $this->getIndependentTemplates($templates);
|
|
$templates_api_params = array_flip(['uuid', 'groups', 'macros', 'templates', 'host', 'status', 'name',
|
|
'description', 'tags', 'vendor_name', 'vendor_version'
|
|
]);
|
|
|
|
$templates_to_create = [];
|
|
$templates_to_update = [];
|
|
$valuemaps = [];
|
|
$template_linkage = [];
|
|
$templates_to_unlink = [];
|
|
|
|
foreach ($independent_templates as $name) {
|
|
$template = $templates[$name];
|
|
unset($templates[$name]);
|
|
|
|
$template = $this->resolveTemplateReferences($template);
|
|
|
|
/*
|
|
* Save linked templates for 2 purposes:
|
|
* - save linkages to add in case if 'create new' linkages is checked;
|
|
* - calculate missing linkages in case if 'delete missing' is checked.
|
|
*/
|
|
if (array_key_exists('templates', $template) && $template['templates']) {
|
|
$template_linkage[$template['host']] = $template['templates'];
|
|
}
|
|
unset($template['templates']);
|
|
|
|
if (array_key_exists('templateid', $template)
|
|
&& ($this->options['templates']['updateExisting'] || $this->options['process_templates'])) {
|
|
$templates_to_update[] = array_intersect_key($template,
|
|
$templates_api_params + array_flip(['templateid'])
|
|
);
|
|
}
|
|
elseif ($this->options['templates']['createMissing']) {
|
|
if (array_key_exists('templateid', $template)) {
|
|
throw new Exception(_s('Template "%1$s" already exists.', $template['host']));
|
|
}
|
|
|
|
$templates_to_create[] = array_intersect_key($template, $templates_api_params);
|
|
}
|
|
|
|
if (array_key_exists('valuemaps', $template)) {
|
|
$valuemaps[$template['host']] = $template['valuemaps'];
|
|
}
|
|
}
|
|
|
|
if ($templates_to_update) {
|
|
// Get template linkages to unlink.
|
|
if ($this->options['templateLinkage']['deleteMissing']) {
|
|
// Get already linked templates.
|
|
$db_template_links = API::Template()->get([
|
|
'output' => ['templateid'],
|
|
'selectParentTemplates' => ['templateid'],
|
|
'templateids' => array_column($templates_to_update, 'templateid'),
|
|
'preservekeys' => true
|
|
]);
|
|
|
|
foreach ($db_template_links as &$db_template_link) {
|
|
$db_template_link = array_column($db_template_link['parentTemplates'], 'templateid');
|
|
}
|
|
unset($db_template_link);
|
|
|
|
foreach ($templates_to_update as $template) {
|
|
if (array_key_exists($template['host'], $template_linkage)) {
|
|
$templates_to_unlink[$template['templateid']] = array_diff(
|
|
$db_template_links[$template['templateid']],
|
|
array_column($template_linkage[$template['host']], 'templateid')
|
|
);
|
|
}
|
|
else {
|
|
$templates_to_unlink[$template['templateid']] = $db_template_links[$template['templateid']];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->options['templates']['updateExisting']) {
|
|
API::Template()->update($templates_to_update);
|
|
}
|
|
|
|
foreach ($templates_to_update as $template) {
|
|
$this->referencer->setDbTemplate($template['templateid'], $template);
|
|
$this->processed_templateids[$template['templateid']] = $template['templateid'];
|
|
|
|
// Drop existing template linkages if 'delete missing' is selected.
|
|
if (array_key_exists($template['templateid'], $templates_to_unlink)
|
|
&& $templates_to_unlink[$template['templateid']]) {
|
|
$template['templates'] = [];
|
|
|
|
API::Template()->update($template);
|
|
}
|
|
|
|
// Make new template linkages.
|
|
if ($this->options['templateLinkage']['createMissing']
|
|
&& array_key_exists($template['host'], $template_linkage)) {
|
|
API::Template()->massAdd([
|
|
'templates' => array_intersect_key($template, array_flip(['templateid'])),
|
|
'templates_link' => $template_linkage[$template['host']]
|
|
]);
|
|
}
|
|
|
|
$db_valuemaps = API::ValueMap()->get([
|
|
'output' => ['valuemapid', 'uuid', 'name'],
|
|
'hostids' => [$template['templateid']]
|
|
]);
|
|
|
|
if ($this->options['valueMaps']['createMissing']
|
|
&& array_key_exists($template['host'], $valuemaps)) {
|
|
$valuemaps_to_create = [];
|
|
|
|
$db_valuemap_uuids = array_column($db_valuemaps, 'uuid');
|
|
$db_valuemap_names = array_column($db_valuemaps, 'name');
|
|
|
|
foreach ($valuemaps[$template['host']] as $valuemap) {
|
|
if (!in_array($valuemap['uuid'], $db_valuemap_uuids)
|
|
&& !in_array($valuemap['name'], $db_valuemap_names)) {
|
|
$valuemaps_to_create[] = $valuemap + ['hostid' => $template['templateid']];
|
|
}
|
|
}
|
|
|
|
if ($valuemaps_to_create) {
|
|
API::ValueMap()->create($valuemaps_to_create);
|
|
}
|
|
}
|
|
|
|
if ($this->options['valueMaps']['updateExisting']
|
|
&& array_key_exists($template['host'], $valuemaps)) {
|
|
$valuemaps_to_update = [];
|
|
|
|
foreach ($db_valuemaps as $db_valuemap) {
|
|
foreach ($valuemaps[$template['host']] as $valuemap) {
|
|
if ($db_valuemap['uuid'] === $valuemap['uuid']
|
|
|| $db_valuemap['name'] === $valuemap['name']) {
|
|
$valuemaps_to_update[] = $valuemap + ['valuemapid' => $db_valuemap['valuemapid']];
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($valuemaps_to_update) {
|
|
API::ValueMap()->update($valuemaps_to_update);
|
|
}
|
|
}
|
|
|
|
if ($this->options['valueMaps']['deleteMissing'] && $db_valuemaps) {
|
|
$valuemapids_to_delete = [];
|
|
|
|
if (array_key_exists($template['host'], $valuemaps)) {
|
|
$valuemap_uuids = array_column($valuemaps[$template['host']], 'uuid');
|
|
$valuemap_names = array_column($valuemaps[$template['host']], 'name');
|
|
|
|
foreach ($db_valuemaps as $db_valuemap) {
|
|
if (!in_array($db_valuemap['uuid'], $valuemap_uuids)
|
|
&& !in_array($db_valuemap['name'], $valuemap_names)) {
|
|
$valuemapids_to_delete[] = $db_valuemap['valuemapid'];
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$valuemapids_to_delete = array_column($db_valuemaps, 'valuemapid');
|
|
}
|
|
|
|
if ($valuemapids_to_delete) {
|
|
API::ValueMap()->delete($valuemapids_to_delete);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($this->options['templates']['createMissing'] && $templates_to_create) {
|
|
$created_templates = API::Template()->create($templates_to_create);
|
|
|
|
foreach ($templates_to_create as $index => $template) {
|
|
$templateid = $created_templates['templateids'][$index];
|
|
|
|
$this->referencer->setDbTemplate($templateid, $template);
|
|
$this->processed_templateids[$templateid] = $templateid;
|
|
|
|
if ($this->options['templateLinkage']['createMissing']
|
|
&& array_key_exists($template['host'], $template_linkage)) {
|
|
API::Template()->massAdd([
|
|
'templates' => ['templateid' => $templateid],
|
|
'templates_link' => $template_linkage[$template['host']]
|
|
]);
|
|
}
|
|
|
|
if ($this->options['valueMaps']['createMissing']
|
|
&& array_key_exists($template['host'], $valuemaps)) {
|
|
$valuemaps_to_create = [];
|
|
|
|
foreach ($valuemaps[$template['host']] as $valuemap) {
|
|
$valuemap['hostid'] = $templateid;
|
|
$valuemaps_to_create[] = $valuemap;
|
|
}
|
|
|
|
if ($valuemaps_to_create) {
|
|
API::ValueMap()->create($valuemaps_to_create);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while ($independent_templates);
|
|
|
|
// if there are templates left in $templates, then they have unresolved references
|
|
foreach ($templates as $template) {
|
|
$unresolved_references = [];
|
|
|
|
foreach ($template['templates'] as $linked_template) {
|
|
if (!$this->referencer->findTemplateidByHost($linked_template['name'])) {
|
|
$unresolved_references[] = $linked_template['name'];
|
|
}
|
|
}
|
|
throw new Exception(_n('Cannot import template "%1$s", linked template "%2$s" does not exist.',
|
|
'Cannot import template "%1$s", linked templates "%2$s" do not exist.',
|
|
$template['host'], implode(', ', $unresolved_references), count($unresolved_references)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a list of created or updated template IDs.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getProcessedTemplateids(): array {
|
|
return $this->processed_templateids;
|
|
}
|
|
|
|
/**
|
|
* Check if templates have circular references.
|
|
*
|
|
* @see checkCircularRecursive
|
|
*
|
|
* @param array $templates
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected function checkCircularTemplateReferences(array $templates): void {
|
|
foreach ($templates as $name => $template) {
|
|
if (empty($template['templates'])) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($template['templates'] as $linked_template) {
|
|
$checked = [$name];
|
|
|
|
if ($circular_templates = $this->checkCircularRecursive($linked_template, $templates, $checked)) {
|
|
throw new Exception(
|
|
_s('Circular reference in templates: %1$s.', implode(' - ', $circular_templates))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursive function for searching for circular template references.
|
|
* If circular reference exist it return array with template names with circular reference.
|
|
*
|
|
* @param array $linked_template Template element to inspect on current recursive loop.
|
|
* @param array $templates All templates where circular references should be searched.
|
|
* @param array $checked Template names that already were processed,
|
|
* should contain unique values if no circular references exist.
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function checkCircularRecursive(array $linked_template, array $templates, array $checked): array {
|
|
$linked_template_name = $linked_template['name'];
|
|
|
|
// If current template name is already in list of checked template names, circular reference exists.
|
|
if (!in_array($linked_template_name, $checked)) {
|
|
$checked[] = $linked_template_name;
|
|
}
|
|
else {
|
|
// To have nice result containing only templates that have circular reference,
|
|
// remove everything that was added before repeated template name.
|
|
$checked = array_slice($checked, array_search($linked_template_name, $checked));
|
|
// Add repeated name to have nice loop like m1->m2->m3->m1.
|
|
$checked[] = $linked_template_name;
|
|
return $checked;
|
|
}
|
|
|
|
// We need to find template that current element reference to and if it has linked templates
|
|
// check all them recursively.
|
|
if (array_key_exists($linked_template_name, $templates)) {
|
|
foreach ($templates[$linked_template_name]['templates'] as $template) {
|
|
$circular_templates = $this->checkCircularRecursive($template, $templates, $checked);
|
|
|
|
if ($circular_templates) {
|
|
return $circular_templates;
|
|
}
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Get templates that don't have not existing linked templates i.e. all templates that must be linked to these
|
|
* templates exist. Returns array with template names (host).
|
|
*
|
|
* @param array $templates
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function getIndependentTemplates(array $templates): array {
|
|
foreach ($templates as $index => $template) {
|
|
if (!array_key_exists('templates', $template)) {
|
|
continue;
|
|
}
|
|
|
|
foreach ($template['templates'] as $linked_template) {
|
|
if ($this->referencer->findTemplateidByHost($linked_template['name']) === null) {
|
|
unset($templates[$index]);
|
|
continue 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return array_column($templates, 'host');
|
|
}
|
|
|
|
/**
|
|
* Change all references in template to database IDs.
|
|
*
|
|
* @param array $template
|
|
*
|
|
* @return array
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
protected function resolveTemplateReferences(array $template): array {
|
|
$templateid = $this->referencer->findTemplateidByUuid($template['uuid']);
|
|
|
|
if ($templateid === null) {
|
|
$templateid = $this->referencer->findTemplateidByHost($template['host']);
|
|
}
|
|
|
|
if ($templateid !== null) {
|
|
$template['templateid'] = $templateid;
|
|
|
|
// If we update template, existing macros should have hostmacroid.
|
|
if (array_key_exists('macros', $template)) {
|
|
foreach ($template['macros'] as &$macro) {
|
|
$hostmacroid = $this->referencer->findTemplateMacroid($templateid, $macro['macro']);
|
|
|
|
if ($hostmacroid !== null) {
|
|
$macro['hostmacroid'] = $hostmacroid;
|
|
}
|
|
}
|
|
unset($macro);
|
|
}
|
|
}
|
|
|
|
if (!$this->options['templates']['createMissing'] && !$this->options['templates']['updateExisting']) {
|
|
return $template;
|
|
}
|
|
|
|
foreach ($template['groups'] as $index => $group) {
|
|
$groupid = $this->referencer->findTemplateGroupidByName($group['name']);
|
|
|
|
if ($groupid === null) {
|
|
throw new Exception(_s('Group "%1$s" does not exist.', $group['name']));
|
|
}
|
|
|
|
$template['groups'][$index] = ['groupid' => $groupid];
|
|
}
|
|
|
|
if (array_key_exists('templates', $template)) {
|
|
foreach ($template['templates'] as $index => $parent_template) {
|
|
$parent_templateid = $this->referencer->findTemplateidByHost($parent_template['name']);
|
|
|
|
if ($parent_templateid === null) {
|
|
throw new Exception(_s('Cannot import template "%1$s", linked template "%2$s" does not exist.',
|
|
$template['host'], $parent_template['name']));
|
|
}
|
|
|
|
$template['templates'][$index] = [
|
|
'templateid' => $parent_templateid
|
|
];
|
|
}
|
|
}
|
|
|
|
return $template;
|
|
}
|
|
}
|