summaryrefslogblamecommitdiffstats
path: root/server/lib/external-backends/backends/idoit-backend.js
blob: 9632aa8c85088a1ab2cd4da7aceb57b795b7cad3 (plain) (tree)
1
2
3
4
5
6
7
8
9


                                                                                 
                            

                                             


                                                                        
                     
            

                                                                                   

                       



                          

                                                                                  




         



                                                                                              
                                                          
     







                                                                                       
                                                                              


                                                                                              

   

                                                      



                                                                                          

                   

                                                                  








                                                    
                   
                                                                                                                                                            

   
    
                                                                            


                                         
                                              
 
                           
                         
                 
                  

                                      
     


                                                                       
 
                              
                                                                   
 

                                                                                                                                                  

   
                                  



                                                                                
 
                                                                   
                            


                                      




                                                                                                                    
 
                   



                                                                         
                 
   
 
                                                


                                              
 
                   

                                                                                                                    
 
                                                                       



                                                                             
                                                                                                                                                                          

                 

   
                                                           
                                            


                                              
 

                   
                                            

                                                                                                             

                                     
 
                                                                              





                                                                       
     
                 

   



                                       
                                            
                                                                        

                                         


                                              

                         
                           

                                                              
     
                                               
 
                                                                                          

                                   
                                          
                        

     


                                                                            

                     
                                                                                                      
                                                                                                             
 


                                                                                             
 



                                                             






                                                    







                                                   




































                                                      


                                            


                                              
 



                                                                                                
                   
 



                                                                                          


                            


                              
       
                                                                                                                 

     








                                                                                                      

                          
                                                                             

     
















                                                              
       

                                                                            

     













                                                                                                                                                   
                                                                                                                                               









                                                                               
                                              











                                                                                  
                                                        



                                                                                                                  
                                             
                                                        


                                                                                  













































































                                                                                                                                 
                                         






























                                                                                                                                                    
           
         
 











                                                                                                                 
           


                                                                                     
         
















                                                                                                         


       
                                   
                                                













                                                                                                                
                                    

                                                 
 






                                                 

         














                                                                                                
       
     
 
           
                                                











                                                                                                                    
                                    




                                                      

         














                                                                                                    


       
                                   
                                                                            













                                                                                                                   

                                              














                                                        
 













                                                                                                   
         


       
                                     
                                                    














                                                                                                                             

                                          
               









                                                                    
 




























                                                                                                             
                                                        





































































                                                                                                                                      
                   


                                                        
         


       
 
                                  
                                                                                             
                                         
                          


                                 









                                                                                                                                    




                                                                                  
                                                                                 























                                                                                                                 





                                                                        
 




                                                                                                    












                                                    
       

                                                                                      
     
 

                                      
                            
                                                                     


                                                   
 
                                             





                                               
                    
                    
                 
                             




                 





































                                                                                                            
                                                                                                                            
 

                                                                                                                       
 




                                                                   
                                                                                                                                 













                                                                                        
                                                            




                                                            
                           





                                    







                                   

                                   







                                   





                          
                               





















                                                                                                               
                                                                                              

                                                                       


                                          



                                                                               



                    
                                               






                                                                                                                                       


                                                     



                                                                                                                                       
 

                                                                                
 


                                                                                                                                                    



                                                                                                  
                                                      


                                              
 


                                      
                          
                                                     

                         


                                                                                                                                              
 
                                   







                                                                                                           
          

                                                     
       








                                                                                                      
          



                                                                                                    
 


                                                                                                       

   
















                                                                                     

















                                                                                                                         
                                                                                   















                                                                                               




                                                               






                                                                                 
     
                  

   


                                                             
                       
                       




                        
     
   
 
















                                                                                                                                                                         
     

                  



                             
/* 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: <ERROR>, message: <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: <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: <BACKEND_CREDENTIALS>
   * The client parameters are all optional.
   * client: {
   *            id: <CLIENT_ID>,
   *            title: <CLIENT_TITLE>,
   *            parentId: <PARENT_ID>,
   *            system: {
   *              model: <SYSTEM_MODEL>,
   *              manufacturer: <SYSTEM_MANUFACTURER>,
   *              serialnumber: <SYSTEM_SERIALNUMBER>
   *            },
   *            cpu: {
   *              model: <CPU_MODEL>,
   *              manufacturer: <CPU_MANUFACTURER>,
   *              type: <CPU_TYPE>,
   *              frequency: <CPU_FREQUENCY>,
   *              cores: <CPU_CORES>
   *            },
   *            ram: [
   *              {
   *                title: <RAM_TITLE>,
   *                manufacturer: <RAM_MANUFACTURER>,
   *                type: <RAM_TYPE>,
   *                capacity: <RAM_CAPACITY>,
   *                unit: <RAM_UNIT>
   *              },
   *              ...
   *            ],
   *            drives: [
   *              {
   *                model: <DRIVE_MODEL>,
   *                serial: <DRIVE_SERIAL>,
   *                capacity: <DRIVE_CAPACITY>,
   *                unit: <DRIVE_UNIT>,
   *                type: <DRIVE_TYPE>,
   *                formfactor: <DRIVE_FORMFACTOR>,
   *                connection: <DRIVE_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.bay))) {
      // Either client or no slot was set
      let locationData = {
        'parent': client.parentId
      }

      // If location slot information was given, assume it's a rack and add slot position and stuff (without bay -> no segmentation)
      if (client.location.slot) {
        locationData = {
          ...locationData,
          'option': client.location.option ? client.location.option : 'Horizontal',
          'insertion': client.location.insertion ? client.location.insertion : 'Front and backside',
          'pos': client.location.slot
        }
      }

      bodies.push(createUpdateCategorieRequest('C__CATG__LOCATION', locationData))
    } else {
      // Create segments and prepare bay

      // Rack segmentation
      if (!!client.location && !!client.location.slot && !!client.location.bay) {
        // 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

          // Check if bay is in range (only 0 and 1 are allowed)
          if (client.location.bay < bays.length) {
            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__BLADE_CHASSIS') continue // Old segmentation was: C__OBJTYPE__RACK_SEGMENT

      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': 74, // 92 = Rack Segment, 74 = Blade Chassis
        'title': rackName + ' Slot ' + client.location.slot,
        'categories': {
          'C__CATS__CHASSIS': {
            'data': {
              'front_x': 2,
              'front_y': 2,
              'rear_x': 0,
              'rear_y': 0
            }
          },
          'C__CATS__CHASSIS_SLOT': [
            {
              'title': 'Bay 0',
              'insertion': 'front',
              'from_x': 0,
              'to_x': 0,
              'from_y': 1,
              'to_y': 1
            },
            {
              'title': 'Bay 1',
              'insertion': 'front',
              'from_x': 1,
              'to_x': 1,
              'from_y': 1,
              'to_y': 1
            },
            {
              'title': 'Bay 2',
              'insertion': 'front',
              'from_x': 0,
              'to_x': 0,
              'from_y': 0,
              'to_y': 0
            },
            {
              'title': 'Bay 3',
              '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': credentials.apikey,
        'object': chassisId,
        'objID': chassisId,
        'category': 'C__CATG__FORMFACTOR',
        'data': { 'rackunits': client.formfactor.rackunits }
      }, 'set_segment_size')
      await this.axiosRequest(credentials.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