summaryrefslogblamecommitdiffstats
path: root/server/lib/external-backends/backends/idoit-backend.js
blob: 9325ad5873ed19a27f8a18f5bf1bcaf23e8db9ea (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.errno }
    return true
  }

  // 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', '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]
  }

  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 = []
    objectIds.forEach(oid => {
      bodies.push(this.getBody('cmdb.object.quickpurge', { 'id': oid, 'apikey': c.apikey }, oid))
    })

    let deleteRequest = await this.axiosRequest(c.url, bodies, headers)
    return deleteRequest
  }

  // 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.
   * client: {
   *            title: <CLIENT_TITLE>, parentId: <PARENT_ID>,
   *            network: { mac: <MAC_ADDRESS>, ip: <IP_ADDRESS> }
   *         }
   */
  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.title,
      'purpose': client.purpose === 'Pool PC' ? 7 : undefined,
      'categories': {}
    }

    if (client.type === 'CLIENT') params['type'] = 10
    else if (client.type === 'SERVER') {
      params['type'] = 5

      if (client.location) params.categories.C__CATG__LOCATION = { 'data': { 'option': client.location.assembly, 'insertion': client.location.insertion, 'pos': 32 } }
      // TODO: Assign to rack with the given rackid
    }

    // Add categories to the object
    if (client.uuid) params.categories.C__CATG__MODEL = { 'data': { 'productid': client.uuid } }
    if (client.network) {
      // TOOD:
      // First read if there are current entries.
      // Delete the previous entries.
      // Finally create the new entry.
      if (client.network.mac) params.categories.C__CATG__NETWORK_PORT = { 'data': { 'category_id': 1, 'mac': client.network.mac } }
      if (client.network.ip) params.categories.C__CATG__IP = { 'data': { 'category_id': 1, 'ipv4_address': client.network.ip } }
    }
    if (client.parentId) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId } }

    // Send the create request.
    const body = this.getBody('cmdb.object.create', params, 'client_create')
    const requestCreate = await this.axiosRequest(c.url, [body], headers)

    // 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 }
  }

  /*
   * 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

    let bodies = []

    // Update title of the object
    if (client.title) bodies.push(this.getBody('cmdb.object.update', { 'id': client.id, 'title': client.title, 'apikey': c.apikey }, 'update_title'))

    // 'object' should be 'objID' but there is a fucking bug in the idoit api. Soo let's add both parameters because else it will break when they fix it.
    // Update the productid to the uuid.
    if (client.uuid) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__MODEL',
        'data': {
          'productid': client.uuid
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_uuid'))
    }

    // Update the object. Location
    if (client.parentId) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__LOCATION',
        'data': {
          'parent': client.parentId
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_parent'))
    }

    // Update the object. Model data.
    if (client.system) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__MODEL',
        'data': {
          'manufacturer': client.system.manufacturer,
          'title': client.system.model,
          'serial': client.system.serialnumber
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_model'))
    }

    // Update the object. CPU data.
    // TODO: Delete cpu if exists?
    if (client.cpu) {
      let params = {
        'object': client.id,
        'objID': client.id,
        'category': 'C__CATG__CPU',
        'data': {
          'category_id': 1,
          'manufacturer': client.cpu.manufacturer,
          'title': client.cpu.model,
          'type': client.cpu.type,
          'frequency': client.cpu.frequency,
          'frequency_unit': 3,
          'cores': client.cpu.cores
        },
        'apikey': c.apikey
      }
      bodies.push(this.getBody('cmdb.category.save', params, 'update_cpu'))
    }

    // Update the object. Ram data.
    if (client.ram) {
      let counter = 1
      for (var memory in client.ram) {
        var mem = client.ram[memory]
        var ramId = 'create_memory_' + counter
        if (mem.unit === 'MB') mem.capacity = mem.capacity / 1024

        // 2 = MB
        // 3 = GB
        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__MEMORY',
          'data': {
            'title': mem.title,
            'manufacturer': mem.manufacturer,
            'type': mem.type,
            'capacity': mem.capacity,
            'unit': 3
          },
          'apikey': c.apikey
        }
        counter++
        bodies.push(this.getBody('cmdb.category.save', params, ramId))
      }
    }

    // Update the object. Drive data.
    if (client.drives) {
      let counter = 1
      for (var drive in client.drives) {
        var d = client.drives[drive]
        var driveId = 'create_drive_' + counter

        // UNIT
        var unit = 0
        if (d.unit === 'GB') unit = 3
        else if (d.unit === 'TB') unit = 4
        else if (d.unit === 'MB') unit = 2
        else if (d.unit === 'KB') unit = 1
        else if (d.unit === 'B') unit = 0

        let params = {
          'object': client.id,
          'objID': client.id,
          'category': 'C__CATG__STORAGE_DEVICE',
          'data': {
            'title': d.model,
            'type': d.type,
            // 'manufacturer': ,
            // 'model': ,
            'capacity': d.capacity,
            'unit': unit,
            'serial': d.serial,
            'connected': d.connection
          },
          'apikey': c.apikey
        }
        bodies.push(this.getBody('cmdb.category.save', params, driveId))
      }
    }

    const requestUpdate = await this.axiosRequest(c.url, bodies, headers)
    // 10 is the idoit object id for clients.
    var result = {
      success: true,
      id: client.id,
      type: 10,
      response: requestUpdate
    }

    return result
  }

  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 send')
        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
          errorResponse['errno'] = error.errno
        } 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