diff --git a/.gitignore b/.gitignore index 24f6c18..95569d6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ npm-debug.log package-lock.json node_modules coverage +.nyc_output +out diff --git a/.travis.yml b/.travis.yml index e89967a..c75a7b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ sudo: required language: node_js node_js: - - "6" - "8" - - "9" + - "10" + - "12" services: - docker diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 7954840..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,31 +0,0 @@ -# Contributing to zabbix-promise - -:+1::tada: First off, thanks for taking the time to contribute! :tada::+1: - -The following is a set of guidelines for contributing to zabbix-promise. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. - -## Code of Conduct - -This project and everyone participating in it is governed by the [zabbix-promise Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. - -## Where do I go from here? - -If you've noticed a bug or have a question that doesn't belong on the [issue tracker](https://github.com/sumitgoelpw/zabbix-promise/issues?q=) to see if someone else in the community has already created a ticket. If not, go ahead and [make one](https://github.com/sumitgoelpw/zabbix-promise/issues/new)! - -If this is something you think you can fix, then [fork zabbix-promise](https://help.github.com/articles/fork-a-repo/) and create a branch with a descriptive name. - -If all the tests are passing then go to GitHub and [make a Pull Request](https://help.github.com/articles/creating-a-pull-request/) - -## Setup Local Development Environment - -TODO - -## Merging a PR (maintainers only) - -A PR can only be merged into master by a maintainer if: - -1. It is passing CI. -2. It has no requested changes. -3. It is up to date with current master. - -Any maintainer is allowed to merge a PR if all of these conditions are met. diff --git a/LICENSE b/LICENSE index e0df2de..9abc8df 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Sumit Goel +Copyright (c) 2016-2019 Sumit Goel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index db726de..24453c1 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,58 @@ -# Zabbix API Client (zabbix-promise) +# Zabbix API Client -[Zabbix](https://www.zabbix.com/) is an open source monitoring software for -networks and applications. It is designed to monitor and track the status of -various network services, servers, and hardware. +Zabbix is an open source monitoring software that can monitor pretty much everything like networks, servers, applications, etc. You may learn more about Zabbix at [www.zabbix.com](https://www.zabbix.com/). -**Zabbix-promise** is an abstract module written in JavaScript for -[Node.js >= 6](https://nodejs.org/) to interface with the -[Zabbix API >= 3.0](https://www.zabbix.com/documentation/3.0/manual/api) using -ES2015 native promises. +**Zabbix-promise** is a JavaScript package for Node.js runtime environment to interact with Zabbix APIs. The package is written to support JavaScript Promise and Async/await interfaces. Zabbix-promise implements Zabbix sender protocol in pure JavaScript code, and there are no other package dependencies, just the Node.js runtime. + +The latest version of zabbix-promise supports all currently maintained Node versions, see [Node Release Schedule](https://github.com/nodejs/Release#release-schedule) and all currently supported Zabbix releases, see [Zabbix Life Cycle & Release Policy](https://www.zabbix.com/life_cycle_and_release_policy). + +**Table of Contents** + + + +- [Install](#install) +- [Usage](#usage) + - [getHost](examples/getHost.js) + - [createHost](examples/createHost.js) + - [zabbixSender](examples/zabbixSender.js) +- [Debugging](#debugging) +- [Contributing](#contributing) +- [License](#license) + + ## Install -`npm install zabbix-promise --save` +```js +$ npm install zabbix-promise --save +``` + +## Usage -## Examples +Please check the examples below to get started. - [getHost](examples/getHost.js) - [createHost](examples/createHost.js) -- [getEvents](examples/getEvents.js) -- [sendValues](examples/sendValues.js) +- [zabbixSender](examples/zabbixSender.js) + +## Debugging + +Zabbix-promsie uses [`debuglog`](https://nodejs.org/dist/latest/docs/api/util.html#util_util_debuglog_section), so just run with environmental variable `NODE_DEBUG` set to `zp*`. + +```js +$ NODE_DEBUG=zp* node getHost.js +``` + +## Contributing + +đź‘‹Thanks for thinking about contributing to zabbix-promise! There are a few ways you may contribute to the project. + +1. Use the package in your projects and report any bugs you may find by filing issues. +2. Submit examples to cover the Zabbix APIs that are not in examples already by sending pull request. +3. Submit test cases to cover all or most of the Zabbix APIs by sending pull request. + +## License + +[MIT](LICENSE) + +Copyright (c) 2016-2019 Sumit Goel. diff --git a/docker-compose.yml b/docker-compose.yml index 6ba90ad..d1a06bb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: zabbix-server: hostname: zabbix-server.local image: zabbix/zabbix-server-pgsql:${ZABTAG} + ports: + - 0.0.0.0:10051:10051/tcp environment: DB_SERVER_HOST: zabbix-database POSTGRES_USER: ${DBUSER} @@ -20,7 +22,8 @@ services: hostname: zabbix-web.local image: zabbix/zabbix-web-nginx-pgsql:${ZABTAG} ports: - - 0.0.0.0:${HOSTPORT}:80/tcp + - 0.0.0.0:8080:80/tcp + - 0.0.0.0:8443:443/tcp environment: ZBX_SERVER_HOST: zabbix-server DB_SERVER_HOST: zabbix-database @@ -31,27 +34,3 @@ services: depends_on: - zabbix-server - zabbix-database - zabbix-agent1: - hostname: zabbix-agent1.local - image: zabbix/zabbix-agent:${ZABTAG} - environment: - ZBX_HOSTNAME: zabbix-agent1 - ZBX_SERVER_HOST: zabbix-server - depends_on: - - zabbix-server - zabbix-agent2: - hostname: zabbix-agent2.local - image: zabbix/zabbix-agent:${ZABTAG} - environment: - ZBX_HOSTNAME: zabbix-agent2 - ZBX_SERVER_HOST: zabbix-server - depends_on: - - zabbix-server - zabbix-agent3: - hostname: zabbix-agent3.local - image: zabbix/zabbix-agent:${ZABTAG} - environment: - ZBX_HOSTNAME: zabbix-agent3 - ZBX_SERVER_HOST: zabbix-server - depends_on: - - zabbix-server diff --git a/examples/createHost.js b/examples/createHost.js index d217dfe..6636150 100644 --- a/examples/createHost.js +++ b/examples/createHost.js @@ -1,29 +1,39 @@ +/** + * In this example, we will create a Zabbix host "yet-another-host". + * We need at least one host group and interface details as this information + * is required to create a new host. We will query the host groups and get the + * last group id from the return values. + */ + const Zabbix = require('../index') -const whiteSpaceCount = 2 -const zabbix = new Zabbix( - 'http://127.0.0.1:8080/api_jsonrpc.php', - 'Admin', - 'zabbix' -) +const zabbix = new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' +}) -zabbix.login() - .then(() => zabbix.request('host.create', { - 'host': 'zabbix-agent1', - 'groups': [{ 'groupid': '2' }], - 'interfaces': [ - { - 'dns': 'zabbix-agent1', - 'ip': '', - 'main': 1, - 'port': '10050', - 'type': 1, - 'useip': 0 - } - ], - 'templates': [{ 'templateid': '10001' }], - 'inventory_mode': 1 - })) - .then((value) => console.log(JSON.stringify(value, null, whiteSpaceCount))) - .then(() => zabbix.logout()) - .catch((reson) => console.log(JSON.stringify(reson, null, whiteSpaceCount))) +const main = async () => { + try { + await zabbix.login() + const groups = await zabbix.request('hostgroup.get', {}) + const groupId = groups[groups.length - 1].groupid + const host = await zabbix.request('host.create', { + host: 'yet-another-host', + groups: [{ groupid: groupId }], + interfaces: [{ + type: 1, + main: 1, + useip: 1, + ip: '127.0.0.1', + dns: '', + port: '10050' + }] + }) + console.log(host) + zabbix.logout() + } catch (error) { + console.error(error) + } +} +main() diff --git a/examples/getEvents.js b/examples/getEvents.js deleted file mode 100644 index a682000..0000000 --- a/examples/getEvents.js +++ /dev/null @@ -1,37 +0,0 @@ -const Zabbix = require('../index') - -const whiteSpaceCount = 2 -const timeFrom = Date.parse('Mar 1 2017 00:00:00 GMT') / 1000 -const timeTill = Date.parse('Mar 31 2017 23:59:59 GMT') / 1000 -const event = {} // eslint: will contain ack and unack event count -const getEventParams = { - 'countOutput': true, - 'time_from': timeFrom, - 'time_till': timeTill, - 'acknowledged': false, - 'source': 0, // eslint: event created by a trigger - 'value': 1 // eslint: problem events only -} - -const zabbix = new Zabbix( - 'https://zabbix/zabbix/api_jsonrpc.php', - process.env.ZABBIX_USER, - process.env.ZABBIX_PASSWORD, - false // eslint: https rejectUnauthorized boolean -) - -zabbix.login() - .then(() => zabbix.request('event.get', getEventParams)) - .then((value) => { - event.unack = value - getEventParams.acknowledged = true - - return zabbix.request('event.get', getEventParams) - }) - .then((value) => { - event.ack = value - console.log(JSON.stringify(event, null, whiteSpaceCount)) - return value - }) - .then(() => zabbix.logout()) - .catch((reson) => console.log(JSON.stringify(reson, null, whiteSpaceCount))) diff --git a/examples/getHost.js b/examples/getHost.js index c870f76..20fe185 100644 --- a/examples/getHost.js +++ b/examples/getHost.js @@ -1,20 +1,29 @@ +/** + * In this example, we will query Zabbix hosts and apply a filter for host + * "zabbix-promise-host" and include the interfaces as well. + */ + const Zabbix = require('../index') -const whiteSpaceCount = 2 -const zabbix = new Zabbix( - 'http://127.0.0.1:8080/api_jsonrpc.php', - 'Admin', - 'zabbix' -) +const zabbix = new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' +}) -zabbix.login() - .then(() => zabbix.request('host.get', { - 'output': [ - 'hostid', - 'host' - ], - 'limit': 1 - })) - .then((value) => console.log(JSON.stringify(value, null, whiteSpaceCount))) - .then(() => zabbix.logout()) - .catch((reson) => console.log(JSON.stringify(reson, null, whiteSpaceCount))) +const main = async () => { + try { + await zabbix.login() + const hosts = await zabbix.request('host.get', { + selectInterfaces: 'extend', + filter: { + host: 'zabbix-promise-host' + } + }) + console.log(JSON.stringify(hosts, null, 2)) + zabbix.logout() + } catch (error) { + console.error(error) + } +} +main() diff --git a/examples/sendValues.js b/examples/sendValues.js deleted file mode 100644 index c81728c..0000000 --- a/examples/sendValues.js +++ /dev/null @@ -1,10 +0,0 @@ -const zabbix = require('../index') - -zabbix.sender({ - 'path': '/usr/local/bin/zabbix_sender', - 'server': 'zabbix-dev', - 'host': 'CloudGenix-Events', - 'values': '- testing 444\n- testing 111\n' -}) - .then((value) => console.log(value)) - .catch((reason) => console.log(reason)) diff --git a/examples/zabbixSender.js b/examples/zabbixSender.js new file mode 100644 index 0000000..518df7c --- /dev/null +++ b/examples/zabbixSender.js @@ -0,0 +1,22 @@ +/** + * In this example, we will send values to Zabbix trapper item type. There is + * no need for Zabbix Sender binary as zabbix-promise package implements the + * sender protocol in native JavaScript/Node.js. + */ + +const zabbix = require('../index') + +const main = async () => { + try { + const result = await zabbix.sender({ + server: '127.0.0.1', + host: 'zabbix-promise-host', + key: 'zabbix.promise.key', + value: Math.random() + }) + console.log(result) + } catch (error) { + console.error(error) + } +} +main() diff --git a/lib/api.js b/lib/api.js index 9a39ba4..703b706 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,83 +1,77 @@ -/** - * Dependencies - */ -const debug = require('debug')('api') const req = require('./wrapper') +const util = require('util') + +const debug = util.debuglog('zp:api') -/** - * Zabbix API Client Object - */ class Zabbix { /** - * @param {String} url - Zabbix API endpoint - * @param {String} user - Zabbix login name - * @param {String} password - Zabbix login password - * @param {Object} options - HTTP request options + * Create an instance to make Zabbix API calls. + * @param {object} opts + * @property {string} url - The URL of the Zabbix API endpoint. + * @property {string} user - The login name for authentication. + * @property {string} password - The password for authentication. + * @property {object} options - Any Nodejs HTTP request supported options. */ - constructor (url, user, password, options = {}) { - this.url = url - this.user = user - this.password = password - this.options = options - this.rpcid = 0 - this.authid = null - } // eslint: constructor + constructor (opts) { + this.url = opts.url + this.user = opts.user + this.password = opts.password + this.options = opts.options || {} + this.auth = null + + debug('Constructor options: %o', opts) + } /** - * @param {String} method - Zabbix API method - * @param {String} params - Parameters for the method call * - * @returns {Promise} a promise which resolves to the http response. + * @param {string} method - tbd + * @param {object|array} params - tbd + * @returns {object|string} abc */ - request (method, params) { - const opts = { - 'id': this.rpcid += 1, - 'uri': this.url, - 'auth': this.authid, - 'options': this.options, - method, - params - } - - return req.post(opts).then((value) => { - debug('HTTP response: %o', value) - - if (!Object.prototype.hasOwnProperty.call(value, 'result')) { - throw new Error(value) + async request (method, params) { + try { + const res = await req.post({ + url: this.url, + auth: this.auth, + options: this.options, + method, + params + }) + debug('API Request response: %o', res) + if (res.result) { + return res.result + } else { + throw JSON.stringify(res) } + } catch (error) { + throw error + } + } - return value.result - }) - } // eslint: request - - /** - * This method allows to log in to the API and generate an authentication - * token for subsequent API calls. - * - * @returns {Promise} a promise which resolves to the http response. - */ - login () { - return this.request('user.login', { - 'user': this.user, - 'password': this.password - }).then((value) => { - this.authid = value - return value - }) - } // eslint: login + async login () { + try { + const result = await this.request('user.login', { + user: this.user, + password: this.password + }) + this.auth = result + return result + } catch (error) { + throw error + } + } - /** - * This method allows to log out of the API and invalidates the current - * authentication token. - * - * @returns {Promise} a promise which resolves to the http response. - */ - logout () { - return this.request('user.logout', []).then((value) => { - this.authid = null - return value - }) - } // eslint: logout -} // eslint: class + async logout () { + try { + const result = await this.request('user.logout', []) + this.auth = null + return result + } catch (error) { + throw error + } finally { + this.auth = null + } + } +} module.exports = Zabbix diff --git a/lib/utils.js b/lib/utils.js index aebfdb2..21111bb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,70 +1,132 @@ -/** - * Dependencies - */ -const { spawn } = require('child_process') -const debug = require('debug')('utils') +const net = require('net') +const util = require('util') const Zabbix = require('./api') -/** - * Extending Zabbix API Client with helper methods - */ +const debug = util.debuglog('zp:utils') + class Client extends Zabbix { /** - * @param {Object} options - zabbix_sender options - * - * @returns {Promise} a promise which resolves to the http response. + * @param {object} options + * @property {integer} options.port - Zabbix server port to receive sender traffic + * @default 10051 - Default Zabbix server port + * @property {string} options.server - Zabbix server name or IP address + * @property {string} options.host - Zabbix host as configured in frontend + * @property {string} options.key - Zabbix host item key + * @property {string} options.value - The value to send */ - static sender (options) { - return new Promise((resolve, reject) => { - options.path = options.path || 'zabbix_sender' - options.port = options.port || '10051' - options.server = options.server || reject(new Error('Please provide ' + - 'zabbix server or proxy name.')) - options.host = options.host || reject(new Error('Please provide the ' + - 'zabbix host name as registered in Zabbix frontend.')) - options.values = options.values || reject(new Error('Specify values ' + - 'each line contains whitespace delimited: - ')) + static async sender (options) { + try { + if (!options.server) throw new Error('"server" is missing') + if (!options.host) throw new Error('"host" is missing') + if (!options.key) throw new Error('"key" is missing') + if (!options.value) throw new Error('"value" is missing') - resolve(options) - }).then((val) => { - const { path, server, port, host, values } = val + debug('Options received: %o', options) - return new Promise((resolve, reject) => { - debug('zabbix_sender options: %O', val) + /** + * Zabbix sender request message. + * https://www.zabbix.com/documentation/4.0/manual/appendix/items/trapper + */ + const payload = { + request: 'sender data', + data: [ + { + host: options.host, + key: options.key, + value: options.value + } + ] + } - let stdout = '' - let stderr = '' + // Creates a new Buffer containing payload string. + const payloadBuffer = Buffer.from(JSON.stringify(payload)) - // eslint-disable-next-line array-element-newline, max-len - const cmd = spawn(path, ['-z', server, '-p', port, '-s', host, '-r', '-i', '-']) + /** + * Returns the actual byte length of a string. This is not the same as + * String.prototype.length since that returns the number of characters in + * A string. When string is a Buffer, the actual byte length is returned. + */ + const payloadByteLength = Buffer.byteLength(payloadBuffer) - cmd.stdin.write(values) + /* + * Zabbix sender request and response messages must begin with header + * And data length. Here is the link describing all the headers, + * https://www.zabbix.com/documentation/4.0/manual/appendix/protocols/header_datalen + * + * The code below allocates a new Buffer of size 13 bytes and write the + * header information. + */ + const headerBuffer = Buffer.alloc(4 + 1 + 4 + 4) + headerBuffer.write('ZBXD\x01') + headerBuffer.writeUInt32LE(payloadByteLength, 5) + headerBuffer.write('\x00\x00\x00\x00', 9) - cmd.stdout.on('data', (data) => { - stdout += data - }) + /* + * Returns a new Buffer which is the result of concatenating the header and + * Payload buffers. + */ + const packet = Buffer.concat([headerBuffer, payloadBuffer]) - cmd.stderr.on('data', (data) => { - stderr += data + const serverResponse = await new Promise((resolve, reject) => { + /** + * Initiates connection to Zabbix server or proxy and sends the packet. + */ + const client = net.connect({ + port: options.port || 10051, + host: options.server + }, () => { + debug('Connected to server: %s', options.server) + client.write(packet) }) - cmd.on('error', (err) => reject(new Error(err))) + /** + * Emitted when data is received. The argument data will be a Buffer or + * String. By default, no encoding is assigned and stream data will be + * returned as Buffer objects. Note that the data will be lost if there + * is no listener when a Socket emits a 'data' event. + */ + client.on('data', (data) => { + debug('Server response raw: %s', data.toString()) + const jsonData = JSON.parse(data.slice(13).toString()) + debug('Server response parsed JSON: %j', jsonData) + client.end() - cmd.on('close', (code) => { - if (code === 0) { - resolve({ - stdout, - stderr - }) - } else { - reject(new Error(`${path} process exited with code ${code}`)) + // Read the Zabbix server response and reject the promise with the + // error message. + const errorMessage = `${data.toString()}\n` + 'A few things to check,\n' + + '- Double check the host and item key name in Zabbix\n' + + '- Double check the item configuration e.g. item type, data type and etc.\n' + + '- If using Zabbix proxy then ensure cache is updated.' + const failed = jsonData.info.split(';')[1].slice(-1) + if (failed !== '0') { + reject(new Error(errorMessage)) } + resolve(jsonData) + }) + + /** + * Emitted when an error occurs. The 'close' event will be called + * directly following this event. + */ + client.on('error', (error) => { + debug('Server response: %o', error) + reject(error) + }) + + /** + * Emitted when the other end of the socket sends a FIN packet, thus + * ending the readable side of the socket. + */ + client.on('end', () => { + debug('Connection closed.') }) + }) - cmd.stdin.end() - }) // eslint: then return promise - }) // eslint: promise then - } // eslint: sender -} // eslint: Class + return serverResponse + } catch (error) { + throw error + } + } +} module.exports = Client diff --git a/lib/wrapper.js b/lib/wrapper.js index 23db3d0..da01f9a 100644 --- a/lib/wrapper.js +++ b/lib/wrapper.js @@ -1,36 +1,104 @@ -/** - * Dependencies - */ -const rp = require('request-promise-native') -const debug = require('debug')('wrapper') +const http = require('http') +const https = require('https') +const { URL } = require('url') +const util = require('util') -module.exports = { +const debug = util.debuglog('zp:wrapper') +module.exports = { /** - * Wrapper around request-promise-native package for POST requests. - * - * @param {Object} opts - object with authentication token, method and - * parameters properties - * @returns {Promise} - A promise which resolves to the resource created. + * @param {object} opts + * @property {string} url + * @property {string} auth + * @property {string} method + * @property {object|Array} params + * @property {object} options */ - 'post': (opts) => { - let options = { - 'uri': opts.uri, - 'json': true, - 'gzip': true, - 'body': { - 'jsonrpc': '2.0', - 'id': opts.id, - 'auth': opts.auth, - 'method': opts.method, - 'params': opts.params + post: async (opts) => { + try { + // Creates a new URL object by parsing the input + const url = new URL(opts.url) + debug('Request URL: %o', url) + + // Use http or https module based on the input URL + let client = http + if (url.protocol === 'https:') { + client = https } - } - options = Object.assign(options, opts.options) + // Include or overwrite the user supplied HTTP request options. + const options = Object.assign({ + hostname: url.hostname, + port: url.port, + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }, opts.options) + debug('Request options: %o', options) + + // Stringify the JSON body to write to HTTP POST request. + const body = JSON.stringify({ + jsonrpc: '2.0', + id: String(Math.random()), + auth: opts.auth, + method: opts.method, + params: opts.params + }) + debug('Request body: %o', body) + + return new Promise((resolve, reject) => { + const req = client.request(options, (res) => { + const { statusCode } = res + const contentType = res.headers['content-type'] - debug('HTTP POST Options: %o', options) + debug('Response status code: %i', statusCode) + debug('Response status message: %s', res.statusMessage) + debug('Response headers: %o', res.headers) - return rp.post(options) + /** + * Check the response headers for status code and content type. + * If it's not 200 and application/json respectively then reject the + * promise and that will return the error message. + */ + let error = '' + if (statusCode !== 200) { + error = new Error('Request Failed.\n' + `Status Code: ${statusCode}`) + } else if (!/^application\/json/.test(contentType)) { + error = new Error('Invalid content-type.\n' + + `Expected application/json but received ${contentType}`) + } + + if (error) { + /** + * We are calling .resume() to consume the data from response. + * Because until the data is read it will consume memory that can + * eventually lead to a 'process out of memory' error. Read more at: + * https://nodejs.org/dist/latest-v10.x/docs/api/http.html#http_class_http_clientrequest + */ + res.resume() + reject(error) + } + + res.setEncoding('utf8') + let rawData = '' + res.on('data', (chunk) => { rawData = rawData + chunk }) + res.on('end', () => { + debug('Response body: %o', rawData) + resolve(JSON.parse(rawData)) + }) + }) + + req.on('error', (error) => { + reject(error) + }) + + req.write(body) + req.end() + }) + } catch (error) { + throw error + } } } diff --git a/package.json b/package.json index 0be0dfc..bbec431 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { "name": "zabbix-promise", - "version": "1.3.0", - "description": "Simplified Zabbix API Client with Promise support", + "version": "2.0.0", + "description": "Simplified Zabbix API Client", "main": "index.js", "scripts": { "cb": "checkbashisms ./tasks/test.sh; exit 0", "sc": "shellcheck -s bash -Calways ./tasks/test.sh", "pretest": "npm run cb && npm run sc", "test": "bash ./tasks/test.sh", - "up": "npm-check -u -E && rm -rf ./node_modules package-lock.json && npm i" + "up": "rm -rf ./node_modules package-lock.json && npm i" }, "repository": { "type": "git", @@ -26,19 +26,10 @@ "url": "https://github.com/sumitgoelpw/zabbix-promise/issues" }, "homepage": "https://github.com/sumitgoelpw/zabbix-promise#readme", - "dependencies": { - "debug": "4.1.1", - "request": "2.88.0", - "request-promise-native": "1.0.7" - }, "devDependencies": { - "chai": "4.2.0", - "chai-as-promised": "7.1.1", - "coveralls": "3.0.3", - "istanbul": "0.4.5", - "mocha": "6.0.2", - "mocha-lcov-reporter": "1.3.0", - "npm-check": "5.9.0", - "standard": "12.0.1" + "jsdoc": "*", + "nock": "*", + "standard": "*", + "tap": "*" } } diff --git a/tasks/test.sh b/tasks/test.sh index 0cd253f..f0122e4 100644 --- a/tasks/test.sh +++ b/tasks/test.sh @@ -6,26 +6,27 @@ export PATH="./node_modules/.bin:$PATH" export DBUSER='postgres' export DBPASS='postgres' -export HOSTPORT='8080' # linting check standard # run tests for zabbix -for VAR in 'alpine-3.0-latest' 'alpine-3.2-latest' +for VAR in 'alpine-3.0-latest' 'alpine-4.0-latest' 'alpine-4.2-latest' do export ZABTAG="$VAR" + + printf '\n>>> Building Zabbix %s conatiners.\n\n' "$VAR" docker-compose -p "$VAR" up -d - sleep 30 + printf '\n' && sleep 2 docker-compose -p "$VAR" ps - if [ "$CI" = 'true' ]; then - istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec - coveralls < ./coverage/lcov.info - rm -rf ./coverage - else - istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec - fi + printf '\n>>> Please allow 10 seconds for the application to start entirely.\n' + sleep 10 + + printf '\n>>> Running the test cases.\n\n' + node test/acceptance.test.js + # exit 0 + printf '\n>>> Tests passed, deleting the containers.\n\n' docker-compose -p "$VAR" down done diff --git a/test/acceptance.test.js b/test/acceptance.test.js new file mode 100644 index 0000000..c05b2ea --- /dev/null +++ b/test/acceptance.test.js @@ -0,0 +1,183 @@ +const Zabbix = require('../index') +const t = require('tap') +const test = t.test + +const zabbix = new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' +}) + +// We need host group name to create a new host group and it will return the +// group id that we will store in hostGroupId. We need host group id to create +// a new host later. +const hostGroupName = 'zabbix-promise-group' +let hostGroupId = null + +// We need host name to create a new host and it will return the host id that we +// will store in hostId. We need host id to create a new item later. +const hostName = 'zabbix-promise-host' +let hostId = null + +// We need item name and key to create Zabbix trapper item. +const itemName = 'zabbix-promise-item' +const itemKey = 'zabbix.promise.key' + +const main = async () => { + await test('User login', async t => { + await t.resolves(zabbix.login()) + + t.type(await new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + }).login(), 'string', 'return value type is string') + + // Wrong username + t.rejects(new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin1', + password: 'zabbix' + }).login(), 'expect rejected Promise for wrong username') + + // Wrong password + t.rejects(new Zabbix({ + url: 'http://127.0.0.1:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix1' + }).login(), 'expect rejected Promise for wrong password') + + // Wrong port + t.rejects(new Zabbix({ + url: 'http://127.0.0.1:80801/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + }).login(), 'expect rejected Promise for wrong URL') + + // HTTPS URL + t.rejects(new Zabbix({ + url: 'https://localhost:8080/api_jsonrpc.php', + user: 'Admin', + password: 'zabbix' + }).login(), 'expect rejected Promise for HTTPS') + }) + + await test('Create hostgroup', async t => { + const result = await zabbix.request('hostgroup.get', { + filter: { name: hostGroupName } + }) + if (result.length === 0) { + const value = await zabbix.request('hostgroup.create', { name: hostGroupName }) + hostGroupId = value.groupids[0] + t.match(hostGroupId, /[0-9]+/g, `${hostGroupName} created`) + } else { + hostGroupId = result[0].groupid + t.match(hostGroupId, /[0-9]+/g, `${hostGroupName} already exists`) + } + }) + + await test('Create host', async t => { + const result = await zabbix.request('host.get', { + filter: { host: hostName } + }) + if (result.length === 0) { + const value = await zabbix.request('host.create', { + host: hostName, + groups: [{ groupid: hostGroupId }], + interfaces: [{ + type: 1, + main: 1, + useip: 1, + ip: '127.0.0.1', + dns: '', + port: '10050' + }] + }) + hostId = value.hostids[0] + t.match(hostId, /[0-9]+/g, `${hostName} created`) + } else { + hostId = result[0].hostid + t.match(hostId, /[0-9]+/g, `${hostName} already exists`) + } + }) + + await test('Create item', async t => { + const result = await zabbix.request('item.get', { + hostids: hostId, + search: { key_: itemKey } + }) + if (result.length === 0) { + const value = await zabbix.request('item.create', { + hostid: hostId, + key_: itemKey, + name: itemName, + type: 2, // 2 - Zabbix trapper + value_type: 0 // 0 - numeric float + }) + t.match(value.itemids[0], /[0-9]+/g, `${itemName} created, please wait for 60 seconds.`) + await new Promise(resolve => setTimeout(resolve, 60000)) + } else { + t.match(result[0].itemid, /[0-9]+/g, `${itemName} already exists`) + } + }) + + await test('Zabbix sender', async t => { + t.resolves(Zabbix.sender({ + server: '127.0.0.1', + host: hostName, + key: itemKey, + value: Math.random() + })) + + // Wrong host name + t.rejects(Zabbix.sender({ + server: '127.0.0.1', + host: hostName + 'zabbix', + key: itemKey, + value: Math.random() + }), 'expect rejected Promise for wrong host name') + + // Wrong port + t.rejects(Zabbix.sender({ + server: '127.0.0.1', + port: 1234, + host: hostName, + key: itemKey, + value: Math.random() + }), 'expect rejected Promise for wrong server port') + + // Missing server + t.rejects(Zabbix.sender({ + host: hostName, + key: itemKey, + value: Math.random() + }), 'expect rejected Promise for missing server') + + // Missing host + t.rejects(Zabbix.sender({ + server: '127.0.0.1', + key: itemKey, + value: Math.random() + }), 'expect rejected Promise for missing host') + + // Missing key + t.rejects(Zabbix.sender({ + server: '127.0.0.1', + host: hostName, + value: Math.random() + }), 'expect rejected Promise for missing key') + + // Missing value + t.rejects(Zabbix.sender({ + server: '127.0.0.1', + host: hostName, + key: itemKey + }), 'expect rejected Promise for missing value') + }) + + await test('User logout', async t => { + await t.resolveMatch(zabbix.logout(), /true/g) + t.rejects(zabbix.logout()) + }) +} +main() diff --git a/test/host.js b/test/host.js deleted file mode 100644 index 490a86f..0000000 --- a/test/host.js +++ /dev/null @@ -1,117 +0,0 @@ -/* eslint-env mocha */ - -const chai = require('chai') -const chaiAsPromised = require('chai-as-promised') -const Zabbix = require('../index') - -const { expect } = chai -const zabbix = new Zabbix( - 'http://127.0.0.1:8080/api_jsonrpc.php', - 'Admin', - 'zabbix' -) - -chai.use(chaiAsPromised) - -describe('host actions:', () => { - const hosts = [ - { - 'name': 'zabbix-agent1', - 'id': '', - 'gid': '2', - 'tid': '10001' - }, - { - 'name': 'zabbix-agent2', - 'id': '', - 'gid': '2', - 'tid': '10001' - }, - { - 'name': 'zabbix-agent3', - 'id': '', - 'gid': '2', - 'tid': '10001' - } - ] - - it('login', () => expect(zabbix.login()).to.be.fulfilled - .and.to.eventually.be.a('string')) - - hosts.forEach((host) => { - it(`create host ${host.name}`, () => expect(zabbix.request('host.create', { - 'host': host.name, - 'groups': [{ 'groupid': host.gid }], - 'interfaces': [ - { - 'dns': host.name, - 'ip': '', - 'main': 1, - 'port': '10050', - 'type': 1, - 'useip': 0 - } - ] - }).then((value) => { - [host.id] = value.hostids - - return value - })).to.be.fulfilled.and.to.eventually.be.an('Object')) - }) - - // eslint-disable-next-line max-len - it(`update host ${hosts[0].name}`, () => expect(zabbix.request('host.update', { - 'hostid': hosts[0].id, - 'templates': [{ 'templateid': hosts[0].tid }], - 'inventory_mode': 1 - })).to.be.fulfilled.and.to.eventually.be.an('Object')) - - it(`get host ${hosts[0].name}`, () => expect(zabbix.request('host.get', { - 'output': [ - 'hostid', - 'host' - ], - 'hostids': hosts[0].id - })).to.be.fulfilled.and.to.eventually.be.an('Array')) - - // eslint-disable-next-line max-len - it(`add template to ${hosts[1].name}, ${hosts[2].name}`, () => expect(zabbix.request('host.massadd', { - 'hosts': [ - { 'hostid': hosts[1].id }, - { 'hostid': hosts[2].id } - ], - 'templates': [{ 'templateid': hosts[1].tid }] - })).to.be.fulfilled.and.to.eventually.be.an('Object')) - - it( - `update inventory contact ${hosts[1].name}, ${hosts[2].name}`, - () => expect(zabbix.request('host.massupdate', { - 'hosts': [ - { 'hostid': hosts[1].id }, - { 'hostid': hosts[2].id } - ], - 'inventory': { 'contact': 'Sumit Goel' }, - 'inventory_mode': 1 - })).to.be.fulfilled.and.to.eventually.be.an('Object') - ) - - it( - `unlink and clear template from ${hosts[1].name}, ${hosts[2].name}`, - () => expect(zabbix.request('host.massremove', { - 'hostids': [ - hosts[1].id, - hosts[2].id - ], - 'templateids_clear': hosts[1].tid - })).to.be.fulfilled.and.to.eventually.be.an('Object') - ) - - hosts.forEach((host) => { - // eslint-disable-next-line max-len - it(`delete host ${host.name}`, () => expect(zabbix.request('host.delete', [host.id])).to.be.fulfilled.and - .to.eventually.be.an('Object')) - }) - - it('logout', () => expect(zabbix.logout()).to.be.fulfilled - .and.to.eventually.be.a('boolean')) -}) diff --git a/zabbix-promise.code-workspace b/zabbix-promise.code-workspace deleted file mode 100644 index 1c5898e..0000000 --- a/zabbix-promise.code-workspace +++ /dev/null @@ -1,10 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": { - "eslint.enable": false - } -} \ No newline at end of file