/* global __appdir */ const path = require('path') const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends')) var axios = require('axios') class IdoitBackend extends ExternalBackends { /* * Returns the credential structure / fields, defined in the backends. */ getCredentials () { return [ { type: 'text', id: 1, name: 'API URL', icon: 'link' }, { type: 'password', id: 2, name: 'API Token', icon: 'vpn_key', show: false }, { type: 'switch', id: 3, name: 'Login', icon: 'lock_open', elements: [ { type: 'text', id: 4, name: 'Username', icon: 'person_outline' }, { type: 'password', id: 5, name: 'Password', icon: 'lock', show: false } ] } ] } /* * Checks the connection of a given backend. * idoIT: Checks if we can get a valid session id. If so the connection must be successfull. * * return: true / { error: , message: } */ async checkConnection (credentials) { const c = this.mapCredentials(credentials) const body = this.getBody('idoit.version', { 'apikey': c.apikey }, 'version_check') const headers = await this.getHeaders(c) if (headers.error) return headers // Axios request const result = await this.axiosRequest(c.url, [body], headers) if (result.error) return { error: 'IDOIT_ERROR', message: result.message } // Check if it actually returned some legit idoit stuff and not just a valid response. if (result.length === 1 && result[0].id === 'version_check') return true else return { error: 'IDOIT_ERROR', message: 'The request returned an invalid response!' } } // Return the list of object types created in iDoIT. async getObjectTypes (credentials) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.object_types', { 'apikey': c.apikey }, 'object_types') const headers = await this.getHeaders(c) if (headers.error) return headers var result = {} result.types = await this.axiosRequest(c.url, [body], headers) result.types = result.types[0].result var types = [] result.types.forEach(type => { types.push({ id: type.id, title: type.title }) }) return types } getSyncTypes () { return ['None'] // ['None', 'Two-Way', 'Upload Only', 'Upload Then Delete', 'Upload Mirror', 'Download Only', 'Download Then Delete', 'Download Mirror'] } /* * Gets all objects and searches for the uuid to get the requested client. * */ async getClient (credentials, client) { const c = this.mapCredentials(credentials) const paramsObjects = { 'apikey': c.apikey, 'filter': { 'type': 10 }, 'categories': ['C__CATG__MODEL'] } const body = this.getBody('cmdb.objects', paramsObjects, 'objects') const headers = await this.getHeaders(c) if (headers.error) return headers // Get all client objects. const clients = await this.axiosRequest(c.url, [body], headers) let result = clients[0].result.filter(x => x.categories.C__CATG__MODEL.length > 0 && x.categories.C__CATG__MODEL[0].productid === client.uuid) return result.length >= 1 ? result.map(x => x.id) : { error: 'NO_CLIENT_FOUND', msg: 'There is no client matching with the provided uuid.' } } async getObjects (credentials) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.objects', { 'apikey': c.apikey }, 'objects') const headers = await this.getHeaders(c) if (headers.error) return headers const objects = await this.axiosRequest(c.url, [body], headers) return objects[0].result } async getObject (credentials, oid) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.object.read', { 'apikey': c.apikey, 'id': oid }, 'object_read') const bodyChilds = this.getBody('cmdb.location_tree', { 'apikey': c.apikey, 'id': oid }, 'object_location_tree') const headers = await this.getHeaders(c) if (headers.error) return headers var result = {} result.object = await this.axiosRequest(c.url, [body], headers) result.childs = await this.axiosRequest(c.url, [bodyChilds], headers) result.object = result.object[0].result result.childs = result.childs[0].result return result } async deleteObjects (credentials, objectIds) { const c = this.mapCredentials(credentials) const headers = await this.getHeaders(c) if (headers.error) return headers let bodies = [] bodies.push(this.getBody('cmdb.object.quickpurge', { 'id': objectIds, 'apikey': c.apikey }, 'batch_quickpurge')) let deleteRequest = await this.axiosRequest(c.url, bodies, headers) const errorRegex = /(?<=\s#)[0-9]+/g const failedDeletions = deleteRequest[0].result.message.match(errorRegex) let result = [] if (failedDeletions) for (let failedObjId of failedDeletions) result.push({ error: true, message: `Deletion of object ${failedObjId} was skipped.`, id: failedObjId }) return result } // Function to use the same session for multiple requests async getDataTree (credentials, objects) { const c = this.mapCredentials(credentials) const headers = await this.getHeaders(c) if (headers.error) return headers let bodies = [] let gids = {} // Prepare all the batch request bodies. for (let element of objects) { bodies.push(this.getBody('cmdb.location_tree', { 'id': element.eid, 'apikey': c.apikey }, element.eid)) gids[element.eid] = element.gid } // Send all the batch request and post proccess the result into one array. let requestResult = await this.axiosRequest(c.url, bodies, headers) // Post process the data. let result = [] for (let res of requestResult) { result.push({ gid: gids[res.id], childs: res.result }) } return result } /* * Adds the client to the backend. * * credentials: * The client parameters are all optional. * If given, the values will get updated after the client is createdö */ async addClient (credentials, client) { const c = this.mapCredentials(credentials) const headers = await this.getHeaders(c) if (headers.error) return headers let params = { 'apikey': c.apikey, 'title': client.name, 'purpose': client.purpose === 'Pool PC' ? 7 : undefined, 'categories': {} } client.parentId = parseInt(client.parentId) // Hardcoded id from the Freiburg idoit, should be set in the dynamic backend settings if (client.type === 'CLIENT') { params['type'] = 10 } else if (client.type === 'SERVER') { params['type'] = 5 } // Send the create request. const body = this.getBody('cmdb.object.create', params, 'client_create') const requestCreate = await this.axiosRequest(c.url, [body], headers) // Error handling if (requestCreate.error) return { error: requestCreate.errno, message: 'Connection was refused.' } else if (requestCreate[0].error) return { error: 'IDOIT_ERROR', message: requestCreate[0].error.message } // Add id to the client and call the update method for all the other hardware information client.id = requestCreate[0].result.id const update = await this.updateClient(credentials, client) // Purpose for Clients: // 1 = Production | 5 = PVS // 2 = Test | 7 = Pool PC // 3 = Quality Assurance | 8 = Mitarbeiter Arbeitsplatz return { succes: true, id: requestCreate[0].result.id, type: params.type, message: requestCreate[0].result.message, responses: [requestCreate, ...update.response] } } /* * Updates the client information in the backend. * * credentials: * The client parameters are all optional. * client: { * id: , * title: , * parentId: , * system: { * model: , * manufacturer: , * serialnumber: * }, * cpu: { * model: , * manufacturer: , * type: , * frequency: , * cores: * }, * ram: [ * { * title: , * manufacturer: , * type: , * capacity: , * unit: * }, * ... * ], * drives: [ * { * model: , * serial: , * capacity: , * unit: , * type: , * formfactor: , * connection: * }, * ... * ] * } */ async updateClient (credentials, client) { const c = this.mapCredentials(credentials) const headers = await this.getHeaders(c) if (headers.error) return headers // Parse needed because db values are strings. (Other backends need strings as external ids) client.id = parseInt(client.id) client.parentId = parseInt(client.parentId) let bodies = [] // Helper function for creating categorie update requests let createCategorieRequest = (categorie, method, additionalParams, idCounter = 0) => { // 'object' should be 'objID' but there is a bug in the idoit api. // Soo let's add both parameters because else it will break when they fix it. -.-i let params = { 'object': client.id, 'objID': client.id, 'category': categorie, 'apikey': c.apikey, ...additionalParams } return this.getBody(`cmdb.category.${method}`, params, `${method.toUpperCase()}_${categorie}_${idCounter}`) } let createUpdateCategorieRequest = (categorie, data, additionalParams, idCounter) => { return createCategorieRequest(categorie, 'save', { data: data, ...additionalParams }, idCounter) } // Update title of the object if (client.name) { const params = { 'id': client.id, 'title': client.name, 'apikey': c.apikey } bodies.push(this.getBody('cmdb.object.update', params, 'update_title')) } // Update the productid to the uuid and model/system data. if (client.uuid || client.system) { const modelData = { ...(client.uuid && { 'productid': client.uuid }), ...(client.system && { ...(client.system.manufacturer && { 'manufacturer': client.system.manufacturer }), ...(client.system.model && { 'title': client.system.model }), ...(client.system.serialnumber && { 'serial': client.system.serialnumber }) }) } bodies.push(createUpdateCategorieRequest('C__CATG__MODEL', modelData)) } // For all objects, where a client can have mutiple of, we need to check if we need to update a similar entrie or if it's a new one // Get all data, where the clients wants to update const getObjectsBodies = [] if (client.networks && client.networks.length > 0) { // Get all ips and macs to check if an update is needed getObjectsBodies.push(createCategorieRequest('C__CATG__IP', 'read')) getObjectsBodies.push(createCategorieRequest('C__CATG__NETWORK_PORT', 'read')) } if (client.cpus && client.cpus.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__CPU', 'read')) if (client.gpus && client.gpus.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__GRAPHIC', 'read')) if (client.ram && client.ram.modules && client.ram.modules.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__MEMORY', 'read')) if (client.drives && client.drives.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__STORAGE_DEVICE', 'read')) if (client.monitors && client.monitors.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__CUSTOM_FIELDS_MONITOR', 'read')) if (!client.parentId && client.location && client.location.slot) getObjectsBodies.push(createCategorieRequest('C__CATG__LOCATION', 'read')) if (client.contacts && client.contacts.length > 0) { // Get the already attached objects getObjectsBodies.push(createCategorieRequest('C__CATG__CONTACT', 'read')) // Get the whole persons list getObjectsBodies.push( this.getBody( 'cmdb.objects.read', { 'apikey': c.apikey, 'categories': ['C__CATS__PERSON'], 'filter': { 'type': 'C__OBJTYPE__PERSON' } }, 'READ_C__OBJTYPE__PERSON' ) ) } const boundObjects = await this.axiosRequest(c.url, getObjectsBodies, headers) // Add contact assignment to the object. if (client.contacts && client.contacts.length > 0) { // Get the persons ids. const persons = boundObjects.filter(response => response.id.startsWith('READ_C__OBJTYPE__PERSON'))[0].result let counter = 0 for (let username of client.contacts) { const contactPerson = persons.filter(person => { // Check for the username if (person.categories.C__CATS__PERSON[0].title === username) return true return false }) let contactId if (contactPerson.length > 0) contactId = contactPerson[0].id // Check if the contact is already assigned const bContacts = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CONTACT')) let bContactExists = false if (bContacts.length > 0) { bContactExists = bContacts[0].result.filter(c => parseInt(c.contact.id) === contactId).length > 0 } if (contactId && !bContactExists) { const contactData = { 'contact': contactId, 'primary': counter === 1 ? 1 : 0 } bodies.push(createUpdateCategorieRequest('C__CATG__CONTACT', contactData, {}, counter)) counter++ } } } // Update networks & Check if there is at least one network which will get updated. if (client.networks && client.networks.length > 0) { // Returns an array with the existing objects const ips = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__IP'))[0].result .map(ip => { return { id: ip.id, ip: ip.ipv4_address.ref_title, hostname: ip.hostname, domain: ip.domain } }) const macs = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__NETWORK_PORT'))[0].result .map(mac => { return { id: mac.id, name: mac.title, mac: mac.mac, ip: mac.addresses.length > 0 ? mac.addresses[0] : undefined } }) let counter = 0 for (let network of client.networks) { let updateIp = true const networkObject = { ip: network.ip, hostname: network.hostname, domain: network.domain } // Check if there is an existing object with a similar entrie const similarObjects = [] for (let ipObj of ips) { const simplifiedIpObject = { ip: ipObj.ip, hostname: ipObj.hostname ? ipObj.hostname : undefined, domain: ipObj.domain ? ipObj.domain : undefined } // Check weather there is already an item which has the same attributes if (JSON.stringify(networkObject) === JSON.stringify(simplifiedIpObject)) { updateIp = false break } if (network.ip && network.ip === ipObj.ip) similarObjects.push(ipObj) else if (network.hostname && network.hostname !== '' && network.hostname === ipObj.hostname) similarObjects.push(ipObj) // else if (network.ipv6 === ip.ipv6_address.ref_title) similarObjects.push(ipObj) // else if (network.domain && network.domain === ipObj.domain) similarObjects.push(ipObj) } let ipEntryIds = [] if (updateIp) { const ipData = { ...(network.ip && { 'ipv4_address': network.ip, 'primary': network.primary ? 1 : 0 }), ...(network.hostname && { 'hostname': network.hostname }), ...(network.domain && { 'domain': network.domain }) // TODO: ipv6 needs a different host address type. Not possible to set both? // ...(network.ipv6 && { // 'ipv6_address': network.ipv6 // }), } let addParams = {} // For now update the first one if there are more if (similarObjects.length > 0) { addParams.entry = parseInt(similarObjects[0].id) } if (Object.keys(ipData).length !== 0) { // If we update an existing entry, we don't need the entry id, else we need to create the object to get the entry id for the mac object. const ipBody = createUpdateCategorieRequest('C__CATG__IP', ipData, addParams, counter) if (addParams.entry) { bodies.push(ipBody) ipEntryIds.push(addParams.entry) } else { const createIpResponse = await this.axiosRequest(c.url, [ipBody], headers) ipEntryIds.push(createIpResponse[0].result.entry) } } } // Check for similar mac objects let updateMac = true const similarMacs = [] for (let mac of macs) { const simplifiedMacObject = { name: mac.name, mac: mac.mac } if (JSON.stringify({ name: network.name, mac: network.mac }) === JSON.stringify(simplifiedMacObject)) { updateMac = false break } if (network.name && network.name === mac.title) similarMacs.push(mac) // Can't go with mac because two diffrent interface could have the same mac // else if (network.mac && network.mac === mac.mac) return true } let addParams = {} if (similarMacs.length > 0) { addParams.entry = parseInt(similarMacs[0].id) } if (updateMac) { // Update Mac-Address const macData = { 'title': network.name, 'mac': network.mac, 'addresses': ipEntryIds } bodies.push(createUpdateCategorieRequest('C__CATG__NETWORK_PORT', macData, addParams, counter)) } counter++ } } // Update the object. CPU data. if (client.cpus && client.cpus.length > 0) { const boundedCpus = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CPU'))[0].result .map(cpu => { return { id: cpu.id, title: cpu.title, manufacturer: cpu.manufacturer.title, type: cpu.type.title, frequency: cpu.frequency.title, frequency_unit: parseInt(cpu.frequency_unit.id), cores: parseInt(cpu.cores) } }) let counter = 0 for (let cpu of client.cpus) { if (cpu.unit === 'MHz') cpu.unit = 2 else if (cpu.unit === 'GHz') cpu.unit = 3 const cpuData = { 'title': cpu.model, 'manufacturer': cpu.manufacturer, 'type': cpu.type, 'frequency': parseFloat(cpu.frequency), 'frequency_unit': cpu.unit, 'cores': parseInt(cpu.cores) } let updateCpu = true for (let bCpu of boundedCpus) { let simplifiedCpuObject = { ...bCpu } delete simplifiedCpuObject.id if (JSON.stringify(cpuData) === JSON.stringify(simplifiedCpuObject)) { updateCpu = false break } } if (updateCpu) { bodies.push(createUpdateCategorieRequest('C__CATG__CPU', cpuData, undefined, counter)) counter++ } } } // GPUS if (client.gpus && client.gpus.length > 0) { const boundedGpus = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__GRAPHIC'))[0].result .map(gpu => { return { id: gpu.id, title: gpu.title, manufacturer: gpu.manufacturer.title, ...(gpu.memory && { 'memory': gpu.memory }), ...(gpu.unit && { 'unit': gpu.unit }) } }) let counter = 0 for (let gpu of client.gpus) { const gpuData = { 'title': gpu.model, 'manufacturer': gpu.manufacturer, ...(gpu.memory && { 'memory': gpu.memory }), ...(gpu.unit && { 'unit': gpu.unit }) } let updateGpu = true for (let bGpu of boundedGpus) { let simplifiedGpuObject = { ...bGpu } delete simplifiedGpuObject.id if (JSON.stringify(gpuData) === JSON.stringify(simplifiedGpuObject)) { updateGpu = false break } } if (updateGpu) { bodies.push(createUpdateCategorieRequest('C__CATG__GRAPHIC', gpuData, undefined, counter)) counter++ } } } // Update the object. Ram data. if (client.ram && client.ram.modules && client.ram.modules.length > 0) { const boundedRams = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__MEMORY'))[0].result .map(bRam => { return { id: bRam.id, title: bRam.title.title, manufacturer: bRam.manufacturer.title, capacity: bRam.capacity.title, unit: bRam.unit.id, type: bRam.type.title, description: bRam.description } }) let counter = 0 for (let module of client.ram.modules) { // Add KB and TB if (module.unit === 'MB') module.unit = '2' else if (module.unit === 'GB') module.unit = '3' let ramData = { 'title': module.model, 'manufacturer': module.manufacturer, 'capacity': parseFloat(module.capacity), 'unit': module.unit, 'type': module.type, 'description': JSON.stringify({ serialnumber: module.serialnumber, formfactor: module.formfactor, speed: module.speed }) } let updateRam = true for (let bRam of boundedRams) { let simplifiedRamObject = { ...bRam } delete simplifiedRamObject.id if (JSON.stringify(ramData) === JSON.stringify(simplifiedRamObject)) { updateRam = false break } } if (updateRam) { bodies.push(createUpdateCategorieRequest('C__CATG__MEMORY', ramData, undefined, counter)) counter++ } } } // Update the object. Drive data. if (client.drives && client.drives.length > 0) { const boundedDrives = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__STORAGE_DEVICE'))[0].result .map(bDrive => { return { id: bDrive.id, title: bDrive.title, type: bDrive.type.title, ...(bDrive.firmware && { firmware: bDrive.firmware }), ...(bDrive.capacity && { capacity: bDrive.capacity.title }), ...(bDrive.unit && { unit: parseInt(bDrive.unit.id) }), ...(bDrive.serial && { serial: bDrive.serial }), ...(bDrive.connected && { connected: bDrive.connected.title }) } }) let counter = 0 for (let index in client.drives) { const drive = client.drives[index] // UNIT if (drive.unit === 'GB') drive.unit = 3 else if (drive.unit === 'MB') drive.unit = 2 else if (drive.unit === 'TB') drive.unit = 4 else if (drive.unit === 'KB') drive.unit = 1 else if (drive.unit === 'B') drive.unit = 0 if (drive.type === 'Solid State Device') drive.type = 'SSD' else if (drive.type === '5400 rpm') drive.type = 'Hard disk' else if (drive.type === '7200 rpm') drive.type = 'Hard disk' else if (drive.type === '') drive.type = 'Hard disk' let driveData = { 'title': drive.model, 'type': drive.type, ...(drive.firmware && { 'firmware': drive.firmware }), ...(drive.capacity && { 'capacity': parseFloat(drive.capacity) }), ...(drive.unit && { 'unit': drive.unit }), ...(drive.serial && { 'serial': drive.serial }), ...(drive.connection && { 'connected': drive.connection }) } let updateDrive = true for (let bDrive of boundedDrives) { let simplifiedDriveObject = { ...bDrive } delete simplifiedDriveObject.id if (JSON.stringify(driveData) === JSON.stringify(simplifiedDriveObject)) { updateDrive = false break } } if (updateDrive) { bodies.push(createUpdateCategorieRequest('C__CATG__STORAGE_DEVICE', driveData, undefined, counter)) counter++ } } } // Add Monitors if (client.monitors && client.monitors.length > 0) { // Monitor is a custom object, therefore the fieldnames can be looked up in the idoit interface / api const boundedMonitors = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CUSTOM_FIELDS_MONITOR'))[0].result .map(bMonitor => { return { id: bMonitor.id, f_text_c_1618324877787: bMonitor.f_text_c_1618324877787, f_text_c_1618324891521: bMonitor.f_text_c_1618324891521, f_text_c_1618328160730: bMonitor.f_text_c_1618328160730, f_text_c_1618328167672: bMonitor.f_text_c_1618328167672, f_text_c_1620145359196: bMonitor.f_text_c_1620145359196, f_text_c_1622557969975: bMonitor.f_text_c_1622557969975 } }) let counter = 0 for (let monitor of client.monitors) { const monitorData = { 'f_text_c_1618324877787': monitor.model, // Model 'f_text_c_1618324891521': monitor.vendor, // Manufacturer 'f_text_c_1618328160730': `${monitor.resolution.width}x${monitor.resolution.height}`, // Resolution 'f_text_c_1618328167672': monitor.serialnumber, // Serial Number 'f_text_c_1620145359196': `${monitor.dimensions.inch}"`, // Display Size (Inch) 'f_text_c_1622557969975': monitor.port } let updateMonitor = true for (let bMonitor of boundedMonitors) { let simplifiedMonitorObject = { ...bMonitor } delete simplifiedMonitorObject.id if (JSON.stringify(monitorData) === JSON.stringify(simplifiedMonitorObject)) { updateMonitor = false break } } if (updateMonitor) { bodies.push(createUpdateCategorieRequest('C__CATG__CUSTOM_FIELDS_MONITOR', monitorData, undefined, counter)) counter++ } } } /* OPERTATING SYSTEM REWORK TODO // Add operating system information. if (client.runtime && client.runtime.operating_system) { // Get the operating system ids. const getOSParam = { 'apikey': c.apikey, 'filter': { 'type': 35, // 35 = Operating System 'title': client.runtime.operating_system.name } } const getOSBody = this.getBody('cmdb.objects.read', getOSParam, 'get_os') const requestGetOS = await this.axiosRequest(c.url, [getOSBody], headers) // Extra request for getting the id of the version number const getOSVersionParam = { 'apikey': c.apikey, 'objID': requestGetOS[0].result[0].id, 'catgID': 'C__CATG__VERSION' } const getOSVersionBody = this.getBody('cmdb.category.read', getOSVersionParam, 'get_os_version') const requestGetOSVersion = await this.axiosRequest(c.url, [getOSVersionBody], headers) const osVersion = requestGetOSVersion[0].result.filter(x => x.title === client.runtime.operating_system.version) // Add the result of the OS request (ids) to the create request. if (requestGetOS[0].result) { params.categories.C__CATG__OPERATING_SYSTEM = { 'data': { 'application': requestGetOS[0].result[0].id, 'assigned_version': osVersion[0].id } } } } */ // Update the object location. if (client.parentId && (!client.location || (client.location && !client.location.slot))) { // Either client or no slot was set const locationData = { 'parent': client.parentId } bodies.push(createUpdateCategorieRequest('C__CATG__LOCATION', locationData)) } else { // Create segments and prepare bay // Rack segmentation if (!!client.location && !!client.location.slot) { // Get the parentId if none was given if (!client.parentId) { let boundedLocation = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__LOCATION')) if (boundedLocation.length > 0) boundedLocation = boundedLocation[0].result if (boundedLocation.length > 0) client.parentId = parseInt(boundedLocation[0].location_path) } // Call method for getting the chassis id, can be either an existing one or a new created one. const chassisId = await this.getChassisId(c, client, headers) // If chassis id is set, assign the object to the chassis bay if (chassisId) { // Read bay ids const paramsSlots = { 'object': parseInt(chassisId), 'objID': parseInt(chassisId), 'category': 'C__CATS__CHASSIS_SLOT', 'apikey': c.apikey } const readSlotsParam = this.getBody('cmdb.category.read', paramsSlots, 'read_slots') const readSlots = await this.axiosRequest(c.url, [readSlotsParam], headers) const bays = readSlots[0].result const assignToSlotData = { 'assigned_device': client.id, 'assigned_slots': [parseInt(bays[client.location.bay].id)] } bodies.push(createUpdateCategorieRequest('C__CATS__CHASSIS_DEVICES', assignToSlotData, { 'object': parseInt(chassisId), 'objID': parseInt(chassisId) })) } } } // Only server have formfactor categories. if (client.formfactor) { const formfactorData = { ...(client.formfactor.formfactor && { 'formfactor': client.formfactor.formfactor }), ...(client.formfactor.rackunits && { 'rackunits': client.formfactor.rackunits }) } bodies.push(createUpdateCategorieRequest('C__CATG__FORMFACTOR', formfactorData)) } let requestUpdate = [] // Send the batch request to idoit if (bodies.length > 0) { requestUpdate = await this.axiosRequest(c.url, bodies, headers) if (requestUpdate.error) return requestUpdate } // 10 is the idoit object id for clients. // 5 is the idoit object id for servers. let type = 0 if (client.type === 'CLIENT') type = 10 else if (client.type === 'SERVER') type = 5 const result = { success: true, id: client.id, type: type, response: requestUpdate } return result } /* * Rack -> Rack obeject * Slot -> Rack Segment object (contains multiple bays) * Bay -> Slot objects inside the Rack Segment object * */ async getChassisId (credentials, client, headers) { // Check if parentId is still missing. If so skip bay assignment. if (!client.parentId) return undefined // Get all assigned objects of the rack (parentid) to check for existing rack segments at slot position. const rackobjectsBody = this.getBody( 'cmdb.category.read', { 'apikey': credentials.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__OBJECT' }, 'get_rack_objects' ) // Get the name of the rack const rackBody = this.getBody( 'cmdb.category.read', { 'apikey': credentials.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__GLOBAL' }, 'get_rack' ) const rackRequest = await this.axiosRequest(credentials.url, [rackobjectsBody, rackBody], headers) const rackObjects = rackRequest[0] const rack = rackRequest[1] if (!rackObjects.result || rackObjects.result.length === 0 || !rack.result || rack.result.length === 0) return undefined // Check if the parent object is a rack else return this function (Type C__OBJTYPE__ENCLOSURE) if (rack.result[0].type.const !== 'C__OBJTYPE__ENCLOSURE' || rack.result[0].type.title !== 'Rack') return undefined const rackName = rack.result[0].title let rackSegmentId // For each segmentation object in the rack get the slot number for (let object of rackObjects.result) { if (object.assigned_object.type !== 'C__OBJTYPE__RACK_SEGMENT') continue if (object.assigned_object.title === `${rackName} Slot ${client.location.slot}`) { // This is the slot where it should get added rackSegmentId = object.assigned_object.id break } } // There should only be one segment object for the rack slot if so set it as parent let chassisId = rackSegmentId if (!chassisId) { // Create a new rack segment const createSegmentParamObject = { 'apikey': credentials.apikey, 'type': 92, 'title': rackName + ' Slot ' + client.location.slot, 'categories': { 'C__CATS__CHASSIS': { 'data': { 'front_x': 2, 'front_y': 1, 'rear_x': 0, 'rear_y': 0 } }, 'C__CATS__CHASSIS_SLOT': [ { 'title': 'Bay 1', 'insertion': 'front', 'from_x': 0, 'to_x': 0, 'from_y': 0, 'to_y': 0 }, { 'title': 'Bay 2', 'insertion': 'front', 'from_x': 1, 'to_x': 1, 'from_y': 0, 'to_y': 0 } ], 'C__CATG__LOCATION': { 'data': { 'parent': client.parentId, 'option': client.location.option ? client.location.option : 'Horizontal', 'insertion': client.location.insertion ? client.location.insertion : 'Front and backside', 'pos': client.location.slot } } } } const createSegmentParam = this.getBody('cmdb.object.create', createSegmentParamObject, 'create_segment') const createSegment = await this.axiosRequest(credentials.url, [createSegmentParam], headers) chassisId = createSegment[0].result.id /* // Set the new rack units height. (Needs an extra request, why? I DONT KNOW... idoit...) const setSegmentSizeParams = this.getBody('cmdb.category.save', { 'apikey': c.apikey, 'object': chassisId, 'objID': chassisId, 'category': 'C__CATG__FORMFACTOR', 'data': { 'rackunits': client.formfactor.rackunits } }, 'set_segment_size') await this.axiosRequest(c.url, [setSegmentSizeParams], headers) */ } return chassisId } async getFileList (credentials, externalId) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.category.read', { 'objID': externalId, 'category': 'C__CATG__FILE', apikey: c.apikey }, 'filelist') const headers = await this.getHeaders(c) if (headers.error) return headers const result = await this.axiosRequest(c.url, [body], headers) return result[0].result } async getFile (credentials, externalId, filename) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.category.read', { 'objID': externalId, 'category': 'C__CATG__FILE', apikey: c.apikey }, 'filelist') const headers = await this.getHeaders(c) if (headers.error) return headers const files = await this.axiosRequest(c.url, [body], headers) let fileObjId = files[0].result.find(x => x.file.title === filename).file.id const body2 = this.getBody('cmdb.category.read', { 'objID': fileObjId, 'category': 'C__CMDB__SUBCAT__FILE_VERSIONS', apikey: c.apikey }, 'file') let result = await this.axiosRequest(c.url, [body2], headers) result = result[0].result[0] return { filename: result.file_title, value: result.file_content.value, md5: result.md5_hash } } async uploadFiles (credentials, externalId, files) { const c = this.mapCredentials(credentials) const headers = await this.getHeaders(c) if (headers.error) return headers let result = [] for (var key in files) { const filename = files[key].name /* eslint-disable */ const buffer = new Buffer.from(files[key].data) /* eslint-enable */ // Create the fileObject where the file gets uplaoded to. const body = this.getBody('cmdb.object.create', { 'type': 29, 'title': files[key].name, 'apikey': c.apikey }, 'createFileObject_' + key) const fileObject = await this.axiosRequest(c.url, [body], headers) // Upload file to fileobject. const paramsUploadFile = { 'object': fileObject[0].result.id, // <-- workaround for the idoit api bug (wrong mapping of objID) 'objID': fileObject[0].result.id, 'data': { 'file_content': buffer.toString('base64'), 'file_physical': filename, 'file_title': filename, 'version_description': key }, 'category': 'C__CMDB__SUBCAT__FILE_VERSIONS', 'apikey': c.apikey } const bodyUploadFile = this.getBody('cmdb.category.save', paramsUploadFile, 'uploadFile_' + key) const uploadFile = await this.axiosRequest(c.url, [bodyUploadFile], headers) // Connect the file with the client (object). const paramsConnectFile = { 'object': externalId, 'objID': externalId, 'data': { 'file': fileObject[0].result.id }, 'category': 'C__CATG__FILE', 'apikey': c.apikey } const bodyConnect = this.getBody('cmdb.category.save', paramsConnectFile, 'uploadFile_' + key) const connectFile = await this.axiosRequest(c.url, [bodyConnect], headers) result.push({ createObject: fileObject, uploadFile: uploadFile, connectObjectFile: connectFile }) } return result } // Helper function, to map the array of credential objects into a single js object. mapCredentials (credentials) { const c = JSON.parse(credentials) const login = c.find(x => x.id === 3) var mapped = { url: c.find(x => x.id === 1).value, apikey: c.find(x => x.id === 2).value, login: login.value } if (mapped.login) { mapped.username = login.elements.find(x => x.id === 4).value mapped.password = login.elements.find(x => x.id === 5).value } return mapped } // Method for making the axios request and error handling. async axiosRequest (url, bodies, headers, batchRequestSize = 400) { let config = { timeout: 180000, headers: headers } // Split batchrequest in multiple request let batchRequests = [] // For spliceing the array a while loop seems to be the most efficient: // https://ourcodeworld.com/articles/read/278/how-to-split-an-array-into-chunks-of-the-same-size-easily-in-javascript while (bodies.length) { batchRequests = [...batchRequests, bodies.splice(0, batchRequestSize)] } // Make all batch request and return the result of all. let results = [] let requestCounter = 1 for (let i = 0; i < batchRequests.length; i++) { // Axios error handling try { console.log(requestCounter + '/' + batchRequests.length + ' requests sent') requestCounter++ const responses = await axios.post(url, batchRequests[i], config) if (Array.isArray(responses.data)) results = [...results, ...responses.data] else results = [...results, responses.data] } catch (error) { let errorResponse = { error: true } if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx errorResponse['data'] = error.response.data errorResponse['status'] = error.response.status errorResponse['headers'] = error.response.headers } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js let errorMsg = '' if (error.code) errorMsg += '[' + error.code + '] ' if (error.errno) errorMsg += '[' + error.errno + '] ' if (error.message) errorMsg += error.message errorResponse['message'] = errorMsg } else { // Something happened in setting up the request that triggered an Error errorResponse['message'] = error.message } return errorResponse } } return results } // Body wrapper for the requests to remove duplicated code. getBody (method, params, id) { return { 'version': '2.0', 'method': method, 'params': { ...params, 'language': 'en' }, 'id': id } } // Returns the header. Sets the session id if needed. async getHeaders (credentials = {}) { let headers = { 'Content-Type': 'application/json' } // Get a session id for the header if (credentials.login && credentials.username && credentials.password) { const header = { ...headers, 'X-RPC-Auth-Username': credentials.username, 'X-RPC-Auth-Password': credentials.password } const sessionRequest = await this.axiosRequest(credentials.url, [this.getBody('idoit.login', { 'apikey': credentials.apikey, 'language': 'en' }, 'login')], header) // Axios errors if (sessionRequest.error) return sessionRequest // Idoit error if (sessionRequest[0].error) return { error: 'IDOIT_ERROR', message: sessionRequest[0].error.message } headers = { ...headers, 'X-RPC-Auth-Session': sessionRequest[0].result['session-id'] } } return headers } } module.exports = IdoitBackend