feat: add template and template group management via GraphQL
- Implemented GraphQL endpoints for importing, querying, and deleting Zabbix templates and template groups. - Added support for full template data import, including items, preprocessing steps, tags, and linked templates. - Implemented dependent item support by deferred creation logic in the template importer. - Added ability to query templates and template groups with name pattern filtering (supporting Zabbix wildcards). - Implemented batch deletion for templates and template groups by ID or name pattern. - Improved error reporting by including detailed Zabbix API error data in GraphQL responses. - Added comprehensive unit and integration tests covering all new functionality. - Provided GraphQL sample queries and mutations in the 'docs' directory for all new endpoints.
This commit is contained in:
parent
e641f8e610
commit
a3ed4886a3
22 changed files with 2450 additions and 20 deletions
220
src/execution/template_importer.ts
Normal file
220
src/execution/template_importer.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
|
||||
import {
|
||||
CreateTemplate,
|
||||
CreateTemplateGroup,
|
||||
CreateTemplateGroupResponse,
|
||||
ImportTemplateResponse,
|
||||
InputMaybe
|
||||
} from "../schema/generated/graphql.js";
|
||||
import {logger} from "../logging/logger.js";
|
||||
import {
|
||||
ZabbixCreateItemRequest,
|
||||
ZabbixCreateTemplateGroupRequest,
|
||||
ZabbixCreateTemplateRequest,
|
||||
ZabbixQueryItemRequest,
|
||||
ZabbixQueryTemplateGroupRequest,
|
||||
ZabbixQueryTemplatesRequest
|
||||
} from "../datasources/zabbix-templates.js";
|
||||
import {isZabbixErrorResult, ParsedArgs, ZabbixErrorResult} from "../datasources/zabbix-request.js";
|
||||
import {zabbixAPI} from "../datasources/zabbix-api.js";
|
||||
|
||||
export class TemplateImporter {
|
||||
|
||||
public static async importTemplateGroups(templateGroups: InputMaybe<Array<CreateTemplateGroup>> | undefined, zabbixAuthToken?: string, cookie?: string) {
|
||||
if (!templateGroups) {
|
||||
return null
|
||||
}
|
||||
let result: CreateTemplateGroupResponse[] = []
|
||||
for (let group of templateGroups) {
|
||||
let createGroupResult: { groupids: string[] } | ZabbixErrorResult | undefined = undefined;
|
||||
|
||||
// Try to find if it exists by name first
|
||||
let groups = await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
|
||||
filter_name: group.groupName
|
||||
}))
|
||||
|
||||
let groupid = 0
|
||||
let message: string | undefined = undefined
|
||||
|
||||
if (!isZabbixErrorResult(groups) && groups?.length) {
|
||||
groupid = Number(groups[0].groupid)
|
||||
message = `Template group ${group.groupName} already exists with groupid=${groupid} - skipping`
|
||||
logger.debug(message)
|
||||
} else {
|
||||
createGroupResult = await new ZabbixCreateTemplateGroupRequest(zabbixAuthToken, cookie)
|
||||
.executeRequestReturnError(zabbixAPI, new ParsedArgs({
|
||||
name: group.groupName,
|
||||
uuid: group.uuid
|
||||
}))
|
||||
|
||||
if (isZabbixErrorResult(createGroupResult)) {
|
||||
let errorMessage = createGroupResult.error.message;
|
||||
if (createGroupResult.error.data) {
|
||||
errorMessage += " " + (typeof createGroupResult.error.data === 'string' ? createGroupResult.error.data : JSON.stringify(createGroupResult.error.data));
|
||||
}
|
||||
result.push({
|
||||
groupName: group.groupName,
|
||||
message: `Unable to create template group ${group.groupName}: ${errorMessage}`,
|
||||
error: createGroupResult.error
|
||||
})
|
||||
continue
|
||||
} else if (createGroupResult?.groupids?.length) {
|
||||
groupid = Number(createGroupResult.groupids[0])
|
||||
}
|
||||
}
|
||||
|
||||
if (groupid) {
|
||||
result.push({
|
||||
groupName: group.groupName,
|
||||
groupid: groupid,
|
||||
message: message
|
||||
})
|
||||
} else {
|
||||
result.push({
|
||||
groupName: group.groupName,
|
||||
message: `Unable to create template group ${group.groupName}: Unknown error`,
|
||||
error: { message: "Unknown error - no groupid returned" }
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public static async importTemplates(templates: InputMaybe<Array<CreateTemplate>> | undefined, zabbixAuthToken?: string, cookie?: string) {
|
||||
if (!templates) {
|
||||
return null
|
||||
}
|
||||
let result: ImportTemplateResponse[] = []
|
||||
for (let template of templates) {
|
||||
// 1. Resolve Group IDs
|
||||
let groupids = template.groupids
|
||||
if (!groupids || groupids.length === 0) {
|
||||
let groups = await new ZabbixQueryTemplateGroupRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
|
||||
filter_name: template.groupNames
|
||||
}))
|
||||
|
||||
if (isZabbixErrorResult(groups) || !groups?.length) {
|
||||
result.push({
|
||||
host: template.host,
|
||||
message: `Unable to find template groups=${template.groupNames}`
|
||||
})
|
||||
continue
|
||||
}
|
||||
groupids = groups.map(g => Number(g.groupid))
|
||||
}
|
||||
|
||||
// 2. Resolve Linked Templates IDs
|
||||
let linkedTemplates: { templateid: string }[] = []
|
||||
if (template.templates && template.templates.length > 0) {
|
||||
let templateNames = template.templates.map(t => t.name)
|
||||
let queryResult = await new ZabbixQueryTemplatesRequest(zabbixAuthToken, cookie).executeRequestReturnError(zabbixAPI, new ParsedArgs({
|
||||
filter_host: templateNames
|
||||
}))
|
||||
|
||||
if (isZabbixErrorResult(queryResult)) {
|
||||
let errorMessage = queryResult.error.message;
|
||||
if (queryResult.error.data) {
|
||||
errorMessage += " " + (typeof queryResult.error.data === 'string' ? queryResult.error.data : JSON.stringify(queryResult.error.data));
|
||||
}
|
||||
result.push({
|
||||
host: template.host,
|
||||
message: `Error querying linked templates: ${errorMessage}`,
|
||||
error: queryResult.error
|
||||
})
|
||||
continue
|
||||
}
|
||||
linkedTemplates = queryResult.map(t => ({ templateid: t.templateid }))
|
||||
}
|
||||
|
||||
// 3. Create Template
|
||||
let templateCreateParams: any = {
|
||||
host: template.host,
|
||||
name: template.name || template.host,
|
||||
groups: groupids.map(id => ({ groupid: id })),
|
||||
uuid: template.uuid,
|
||||
templates: linkedTemplates,
|
||||
tags: template.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
|
||||
}
|
||||
|
||||
let templateImportResult = await new ZabbixCreateTemplateRequest(zabbixAuthToken, cookie)
|
||||
.executeRequestReturnError(zabbixAPI, new ParsedArgs(templateCreateParams))
|
||||
|
||||
if (isZabbixErrorResult(templateImportResult) || !templateImportResult?.templateids?.length) {
|
||||
let errorMessage = isZabbixErrorResult(templateImportResult) ? templateImportResult.error.message : "Unknown error";
|
||||
if (isZabbixErrorResult(templateImportResult) && templateImportResult.error.data) {
|
||||
errorMessage += " " + (typeof templateImportResult.error.data === 'string' ? templateImportResult.error.data : JSON.stringify(templateImportResult.error.data));
|
||||
}
|
||||
result.push({
|
||||
host: template.host,
|
||||
message: `Unable to import template=${template.host}: ${errorMessage}`,
|
||||
error: isZabbixErrorResult(templateImportResult) ? templateImportResult.error : undefined
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
let templateid = templateImportResult.templateids[0]
|
||||
|
||||
// 4. Create Items if any
|
||||
if (template.items && template.items.length > 0) {
|
||||
const createdItemKeyToId = new Map<string, string>();
|
||||
let itemsToCreate = [...template.items];
|
||||
let retry = true;
|
||||
|
||||
while (retry && itemsToCreate.length > 0) {
|
||||
retry = false;
|
||||
const remainingItems: typeof itemsToCreate = [];
|
||||
|
||||
for (let item of itemsToCreate) {
|
||||
if (item.master_item && !createdItemKeyToId.has(item.master_item.key)) {
|
||||
remainingItems.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
let { key, master_item, ...itemData } = item;
|
||||
let itemCreateParams: any = {
|
||||
...itemData,
|
||||
key_: key,
|
||||
hostid: templateid,
|
||||
preprocessing: item.preprocessing?.map(p => ({
|
||||
type: p.type,
|
||||
params: p.params.join("\n"),
|
||||
error_handler: p.error_handler,
|
||||
error_handler_params: p.error_handler_params
|
||||
})),
|
||||
tags: item.tags?.map(t => ({ tag: t.tag, value: t.value || "" }))
|
||||
}
|
||||
|
||||
if (master_item) {
|
||||
itemCreateParams.master_itemid = createdItemKeyToId.get(master_item.key);
|
||||
}
|
||||
|
||||
let itemResult = await new ZabbixCreateItemRequest(zabbixAuthToken, cookie)
|
||||
.executeRequestReturnError(zabbixAPI, new ParsedArgs(itemCreateParams))
|
||||
|
||||
if (isZabbixErrorResult(itemResult)) {
|
||||
let errorMessage = itemResult.error.message;
|
||||
if (itemResult.error.data) {
|
||||
errorMessage += " " + (typeof itemResult.error.data === 'string' ? itemResult.error.data : JSON.stringify(itemResult.error.data));
|
||||
}
|
||||
logger.error(`Unable to create item ${item.name} for template ${template.host}: ${errorMessage}`)
|
||||
} else if (itemResult?.itemids?.length) {
|
||||
createdItemKeyToId.set(key, itemResult.itemids[0]);
|
||||
retry = true;
|
||||
}
|
||||
}
|
||||
itemsToCreate = remainingItems;
|
||||
}
|
||||
|
||||
if (itemsToCreate.length > 0) {
|
||||
logger.error(`Unable to create ${itemsToCreate.length} items for template ${template.host} due to missing master items: ${itemsToCreate.map(i => i.name).join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
result.push({
|
||||
host: template.host,
|
||||
templateid: templateid
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue