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.

481 lines
16 KiB

<?php
/*
** 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 containing methods for operations with configuration.
*/
class CConfiguration extends CApiService {
public const ACCESS_RULES = [
'export' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'import' => ['min_user_type' => USER_TYPE_ZABBIX_USER],
'importcompare' => ['min_user_type' => USER_TYPE_ZABBIX_USER]
];
/**
* @param array $params
*
* @return string
*/
private function exportCompare(array $params) {
$this->validateExport($params, true);
return $this->exportForce($params);
}
/**
* @param array $params
*
* @return string
*/
public function export(array $params) {
$this->validateExport($params);
return $this->exportForce($params);
}
/**
* Validate input parameters for export() and exportCompare() methods.
*
* @param array $params
* @param bool $with_unlinked_parent_templates
*
* @throws APIException if the input is invalid.
*/
private function validateExport(array &$params, bool $with_unlinked_parent_templates = false): void {
$api_input_rules = ['type' => API_OBJECT, 'fields' => [
'format' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [CExportWriterFactory::YAML, CExportWriterFactory::XML, CExportWriterFactory::JSON, CExportWriterFactory::RAW])],
'prettyprint' => ['type' => API_BOOLEAN, 'default' => false],
'options' => ['type' => API_OBJECT, 'flags' => API_REQUIRED, 'fields' => [
'hosts' => ['type' => API_IDS],
'images' => ['type' => API_IDS],
'maps' => ['type' => API_IDS],
'mediaTypes' => ['type' => API_IDS],
'template_groups' => ['type' => API_IDS],
'host_groups' => ['type' => API_IDS],
'templates' => ['type' => API_IDS]
]]
]];
if ($with_unlinked_parent_templates) {
$api_input_rules['fields'] += ['unlink_parent_templates' => ['type' => API_OBJECTS, 'flags' => API_ALLOW_NULL, 'default' => [], 'fields' => [
'templateid' => ['type' => API_ID],
'unlink_templateids' => ['type' => API_IDS]
]]];
}
if (!CApiInputValidator::validate($api_input_rules, $params, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
if ($params['format'] === CExportWriterFactory::XML) {
$lib_xml = (new CFrontendSetup())->checkPhpLibxml();
if ($lib_xml['result'] == CFrontendSetup::CHECK_FATAL) {
self::exception(ZBX_API_ERROR_INTERNAL, $lib_xml['error']);
}
$xml_writer = (new CFrontendSetup())->checkPhpXmlWriter();
if ($xml_writer['result'] == CFrontendSetup::CHECK_FATAL) {
self::exception(ZBX_API_ERROR_INTERNAL, $xml_writer['error']);
}
}
}
/**
* @param array $params
*
* @return string
*/
private function exportForce(array $params) {
$params['unlink_parent_templates'] = array_key_exists('unlink_parent_templates', $params)
? $params['unlink_parent_templates']
: [];
$export = new CConfigurationExport($params['options'], $params['unlink_parent_templates']);
$export->setBuilder(new CConfigurationExportBuilder());
$writer = CExportWriterFactory::getWriter($params['format']);
$writer->formatOutput($params['prettyprint']);
$export->setWriter($writer);
$export_data = $export->export();
if ($export_data === false) {
self::exception(ZBX_API_ERROR_PERMISSIONS, _('No permissions to referred object or it does not exist!'));
}
return $export_data;
}
/**
* Validate input parameters for import() and importcompare() methods.
*
* @param array $params
*
* @throws APIException if the input is invalid.
*/
protected function validateImport(array &$params): void {
$api_input_rules = ['type' => API_OBJECT, 'fields' => [
'format' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED, 'in' => implode(',', [CImportReaderFactory::YAML, CImportReaderFactory::XML, CImportReaderFactory::JSON])],
'source' => ['type' => API_STRING_UTF8, 'flags' => API_REQUIRED],
'rules' => ['type' => API_OBJECT, 'flags' => API_REQUIRED, 'fields' => [
'discoveryRules' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'graphs' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'host_groups' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'template_groups' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'hosts' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'httptests' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'images' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'items' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'maps' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'mediaTypes' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'templateLinkage' => ['type' => API_OBJECT, 'default' => [], 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'templates' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false]
]],
'templateDashboards' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'triggers' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]],
'valueMaps' => ['type' => API_OBJECT, 'fields' => [
'createMissing' => ['type' => API_BOOLEAN, 'default' => false],
'updateExisting' => ['type' => API_BOOLEAN, 'default' => false],
'deleteMissing' => ['type' => API_BOOLEAN, 'default' => false]
]]
]]
]];
if (!CApiInputValidator::validate($api_input_rules, $params, '/', $error)) {
self::exception(ZBX_API_ERROR_PARAMETERS, $error);
}
if (mb_substr($params['source'], 0, 1) === pack('H*', 'EFBBBF')) {
$params['source'] = mb_substr($params['source'], 1);
}
if (array_key_exists('maps', $params['rules']) && !self::checkAccess(CRoleHelper::ACTIONS_EDIT_MAPS)
&& ($params['rules']['maps']['createMissing'] || $params['rules']['maps']['updateExisting'])) {
self::exception(ZBX_API_ERROR_PARAMETERS, _s('Incorrect value for field "%1$s": %2$s.', 'rules',
_('no permissions to create and edit maps')
));
}
if ($params['format'] === CImportReaderFactory::XML) {
$lib_xml = (new CFrontendSetup())->checkPhpLibxml();
if ($lib_xml['result'] == CFrontendSetup::CHECK_FATAL) {
self::exception(ZBX_API_ERROR_INTERNAL, $lib_xml['error']);
}
$xml_reader = (new CFrontendSetup())->checkPhpXmlReader();
if ($xml_reader['result'] == CFrontendSetup::CHECK_FATAL) {
self::exception(ZBX_API_ERROR_INTERNAL, $xml_reader['error']);
}
}
}
/**
* @param array $params
*
* @return bool
*/
public function import($params) {
$this->validateImport($params);
$import_reader = CImportReaderFactory::getReader($params['format']);
$data = $import_reader->read($params['source']);
$import_validator_factory = new CImportValidatorFactory($params['format']);
$import_converter_factory = new CImportConverterFactory();
$validator = new CXmlValidator($import_validator_factory, $params['format']);
$data = $validator
->setStrict(true)
->validate($data, '/');
foreach ($import_converter_factory::getSequentialVersions() as $version) {
if ($data['zabbix_export']['version'] !== $version) {
continue;
}
$data = $import_converter_factory
->getObject($version)
->convert($data);
$data = $validator
// Must not use XML_INDEXED_ARRAY key validation for the converted data.
->setStrict(false)
->validate($data, '/');
}
// Get schema for converters.
$schema = $import_validator_factory
->getObject(ZABBIX_EXPORT_VERSION)
->getSchema();
// Convert human readable import constants to values Zabbix API can work with.
$data = (new CConstantImportConverter($schema))->convert($data);
// Add default values in place of missed tags.
$data = (new CDefaultImportConverter($schema))->convert($data);
// Normalize array keys and strings.
$data = (new CImportDataNormalizer($schema))->normalize($data);
$adapter = new CImportDataAdapter();
$adapter->load($data);
$configuration_import = new CConfigurationImport(
$params['rules'],
new CImportReferencer(),
new CImportedObjectContainer()
);
return $configuration_import->import($adapter);
}
/**
* Preview changes that would be done to templates.
*
* @param array $params Same params, as for import.
*
* @return array
*
* @throws APIException
* @throws Exception
*/
public function importcompare(array $params): array {
$this->validateImport($params);
$import_reader = CImportReaderFactory::getReader($params['format']);
$data = $import_reader->read($params['source']);
$import_validator_factory = new CImportValidatorFactory($params['format']);
$import_converter_factory = new CImportConverterFactory();
$validator = new CXmlValidator($import_validator_factory, $params['format']);
$data = $validator
->setStrict(true)
->setPreview(true)
->validate($data, '/');
foreach ($import_converter_factory::getSequentialVersions() as $version) {
if ($data['zabbix_export']['version'] !== $version) {
continue;
}
$data = $import_converter_factory
->getObject($version)
->convert($data);
$data = $validator
// Must not use XML_INDEXED_ARRAY key validation for the converted data.
->setStrict(false)
->setPreview(true)
->validate($data, '/');
}
// Get schema for converters.
$schema = $import_validator_factory
->getObject(ZABBIX_EXPORT_VERSION)
->getSchema();
// Normalize array keys and strings.
$data = (new CImportDataNormalizer($schema))->normalize($data);
$adapter = new CImportDataAdapter();
$adapter->load($data);
$import = $adapter->getData();
$imported_entities = [];
$entities = [
'host_groups' => 'name',
'template_groups' => 'name',
'templates' => 'template'
];
foreach ($entities as $entity => $name_field) {
if (array_key_exists($entity, $import)) {
$imported_entities[$entity]['uuid'] = array_column($import[$entity], 'uuid');
$imported_entities[$entity][$name_field] = array_column($import[$entity], $name_field);
}
}
$imported_ids = [];
foreach ($imported_entities as $entity => $data) {
switch ($entity) {
case 'host_groups':
$imported_ids['host_groups'] = API::HostGroup()->get([
'output' => [],
'filter' => [
'uuid' => $data['uuid'],
'name' => $data['name']
],
'preservekeys' => true,
'searchByAny' => true
]);
$imported_ids['host_groups'] = array_keys($imported_ids['host_groups']);
break;
case 'template_groups':
$imported_ids['template_groups'] = API::TemplateGroup()->get([
'output' => [],
'filter' => [
'uuid' => $data['uuid'],
'name' => $data['name']
],
'preservekeys' => true,
'searchByAny' => true
]);
$imported_ids['template_groups'] = array_keys($imported_ids['template_groups']);
break;
case 'templates':
$options = [
'output' => ['templateid', 'uuid', 'name'],
'filter' => [
'uuid' => $data['uuid'],
'host' => $data['template']
],
'preservekeys' => true,
'searchByAny' => true
];
if ($params['rules']['templateLinkage']['deleteMissing']) {
$options['selectParentTemplates'] = ['templateid', 'name'];
}
$db_templates = API::Template()->get($options);
$imported_ids['templates'] = array_keys($db_templates);
break;
default:
break;
}
}
$unlink_templates_data = [];
if ($params['rules']['templateLinkage']['deleteMissing']) {
$import_tmp_parent_tmp_names = [];
foreach ($import['templates'] as $template) {
if (array_key_exists('templates', $template)) {
$parent_tmp = array_column($template['templates'], 'name');
$import_tmp_parent_tmp_names[$template['name']] = $parent_tmp;
$import_tmp_parent_tmp_names[$template['uuid']] = $parent_tmp;
}
else {
$import_tmp_parent_tmp_names[$template['name']] = [];
$import_tmp_parent_tmp_names[$template['uuid']] = [];
}
}
foreach ($db_templates as $db_template) {
$db_parent_tmp_names = array_column($db_template['parentTemplates'], 'name', 'templateid');
if ($db_parent_tmp_names) {
$unlink_templateids = array_key_exists($db_template['uuid'], $import_tmp_parent_tmp_names)
? array_diff($db_parent_tmp_names, $import_tmp_parent_tmp_names[$db_template['uuid']])
: array_diff($db_parent_tmp_names, $import_tmp_parent_tmp_names[$db_template['name']]);
if ($unlink_templateids) {
$unlink_templates_data[$db_template['templateid']] = [
'templateid' => $db_template['templateid'],
'unlink_templateids' => array_keys($unlink_templateids)
];
}
}
}
}
// Get current state of templates in same format, as import to compare this data.
$export = API::Configuration()->exportCompare([
'format' => CExportWriterFactory::RAW,
'prettyprint' => false,
'options' => $imported_ids,
'unlink_parent_templates' => $unlink_templates_data
]);
// Normalize array keys and strings.
$export = (new CImportDataNormalizer($schema))->normalize($export);
$export = $export['zabbix_export'];
$importcompare = new CConfigurationImportcompare($params['rules']);
return $importcompare->importcompare($export, $import);
}
}