Skip to content

Commit c76aa27

Browse files
Merge branch 'extra' of git://github.com/ServerCentral/elastalert-server into ServerCentral-extra
2 parents b2bfcae + cefeff2 commit c76aa27

File tree

12 files changed

+167
-11
lines changed

12 files changed

+167
-11
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,5 @@ lib/
7474
*.pyc
7575
config/config.json
7676
package-lock.json
77+
78+
.vscode

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ The most convenient way to run the ElastAlert server is by using our Docker cont
1414
To run the Docker image you will want to mount the volumes for configuration and rule files to keep them after container updates. In order to do that conveniently, please do: `git clone https://github.com/bitsensor/elastalert.git; cd elastalert`
1515

1616
```bash
17-
docker run -d -p 3030:3030 \
17+
docker run -d -p 3030:3030 -p 3333:3333 \
1818
-v `pwd`/config/elastalert.yaml:/opt/elastalert/config.yaml \
1919
-v `pwd`/config/elastalert-test.yaml:/opt/elastalert/config-test.yaml \
2020
-v `pwd`/config/config.json:/opt/elastalert-server/config/config.json \
@@ -61,6 +61,7 @@ You can use the following config options:
6161
{
6262
"appName": "elastalert-server", // The name used by the logging framework.
6363
"port": 3030, // The port to bind to
64+
"wsport": 3333, // The port to bind to for websockets
6465
"elastalertPath": "/opt/elastalert", // The path to the root ElastAlert folder. It's the folder that contains the `setup.py` script.
6566
"start": "2014-01-01T00:00:00", // Optional date to start querying from
6667
"end": "2016-01-01T00:00:00", // Optional date to stop querying at
@@ -211,7 +212,11 @@ This server exposes the following REST API's:
211212
}
212213
}
213214
```
215+
216+
- **WEBSOCKET `/test`**
214217

218+
This allows you to test a rule and receive progress over a websocket. Send a message as JSON object (stringified) with two keys: `rule` (yaml string) and `options` (JSON object). You will receive progress messages over the socket as the test runs.
219+
215220
- **GET `/metadata/:type`**
216221

217222
Returns metadata from elasticsearch related to elasalert's state. `:type` should be one of: elastalert_status, elastalert, elastalert_error, or silence. See [docs about the elastalert metadata index](https://elastalert.readthedocs.io/en/latest/elastalert_status.html).
@@ -220,6 +225,10 @@ This server exposes the following REST API's:
220225

221226
Returns field mapping from elasticsearch for a given index.
222227

228+
- **GET `/search/:index`**
229+
230+
Performs elasticsearch query on behalf of the API. JSON body to this endpoint will become body of an ES search.
231+
223232
- **[WIP] GET `/config`**
224233

225234
Gets the ElastAlert configuration from `config.yaml` in `elastalertPath` (from the config).

config/config-hisoric-data-example.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"appName": "elastalert-server",
33
"port": 3030,
4+
"wsport": 3333,
45
"elastalertPath": "/opt/elastalert",
56
"start": "2014-01-01T00:00:00",
67
"end": "2016-01-01T00:00:00",
@@ -14,5 +15,9 @@
1415
"templatesPath": {
1516
"relative": true,
1617
"path": "/rule_templates"
17-
}
18+
},
19+
"es_host": "elasticsearch",
20+
"es_port": 9200,
21+
"writeback_index": "elastalert_status"
22+
1823
}

config/config-local-elastalert-installation.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"appName": "elastalert-server",
33
"port": 3030,
4+
"wsport": 3333,
45
"elastalertPath": "/opt/elastalert",
56
"verbose": false,
67
"es_debug": false,
@@ -12,5 +13,9 @@
1213
"templatesPath": {
1314
"relative": false,
1415
"path": "/opt/elastalert/rule_templates"
15-
}
16+
},
17+
"es_host": "elasticsearch",
18+
"es_port": 9200,
19+
"writeback_index": "elastalert_status"
20+
1621
}

config/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"appName": "elastalert-server",
33
"port": 3030,
4+
"wsport": 3333,
45
"elastalertPath": "/opt/elastalert",
56
"verbose": false,
67
"es_debug": false,
@@ -13,7 +14,7 @@
1314
"relative": true,
1415
"path": "/rule_templates"
1516
},
16-
"es_host": "localhost",
17+
"es_host": "elasticsearch",
1718
"es_port": 9200,
1819
"writeback_index": "elastalert_status"
1920
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"raven": "^2.6.1",
3636
"request": "^2.85.0",
3737
"request-promise-native": "^1.0.5",
38-
"tar": "^4.4.1"
38+
"tar": "^4.4.1",
39+
"ws": "^6.0.0"
3940
},
4041
"devDependencies": {
4142
"eslint": "^4.17.0",

src/common/websocket.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import WebSocket from 'ws';
2+
3+
export var wss = null;
4+
5+
export function listen(port) {
6+
wss = new WebSocket.Server({ port, path: '/test' });
7+
8+
wss.on('connection', ws => {
9+
ws.isAlive = true;
10+
ws.on('pong', () => {
11+
ws.isAlive = true;
12+
});
13+
});
14+
15+
return wss;
16+
}
17+
18+
// Keepalive in case clients lose connection during a long rule test.
19+
// If client doesn't respond in 10s this will close the socket and
20+
// therefore stop the elastalert test from continuing to run detached.
21+
setInterval(() => {
22+
wss.clients.forEach(ws => {
23+
if (ws.isAlive === false) return ws.terminate();
24+
ws.isAlive = false;
25+
ws.ping(() => {});
26+
});
27+
}, 10000);

src/controllers/test/index.js

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class TestController {
1919
});
2020
}
2121

22-
testRule(rule, options) {
22+
testRule(rule, options, socket) {
2323
const self = this;
2424
let tempFileName = '~' + randomstring.generate() + '.temp';
2525
let tempFilePath = path.join(self.testFolder, tempFileName);
@@ -55,16 +55,42 @@ export default class TestController {
5555
break;
5656
}
5757

58+
5859
try {
5960
let testProcess = spawn('python', processOptions, {
6061
cwd: self._elastalertPath
6162
});
6263

64+
// When the websocket closes we kill the test process
65+
// so it doesn't keep running detached
66+
if (socket) {
67+
socket.on('close', () => {
68+
testProcess.kill();
69+
70+
fileSystem.deleteFile(tempFilePath)
71+
.catch(function (error) {
72+
logger.error(`Failed to delete temporary test file ${tempFilePath} with error:`, error);
73+
});
74+
});
75+
}
76+
6377
testProcess.stdout.on('data', function (data) {
78+
if (socket) {
79+
socket.send(JSON.stringify({
80+
event: 'result',
81+
data: data.toString()
82+
}));
83+
}
6484
stdoutLines.push(data.toString());
6585
});
6686

6787
testProcess.stderr.on('data', function (data) {
88+
if (socket) {
89+
socket.send(JSON.stringify({
90+
event: 'progress',
91+
data: data.toString()
92+
}));
93+
}
6894
stderrLines.push(data.toString());
6995
});
7096

@@ -77,8 +103,10 @@ export default class TestController {
77103
resolve(stdoutLines.join('\n'));
78104
}
79105
} else {
80-
reject(stderrLines.join('\n'));
81-
logger.error(stderrLines.join('\n'));
106+
if (!socket) {
107+
reject(stderrLines.join('\n'));
108+
logger.error(stderrLines.join('\n'));
109+
}
82110
}
83111

84112
fileSystem.deleteFile(tempFilePath)
@@ -95,6 +123,8 @@ export default class TestController {
95123
logger.error(`Failed to write file ${tempFileName} to ${self.testFolder} with error:`, error);
96124
reject(error);
97125
});
126+
}).catch((error) => {
127+
logger.error('Failed to test rule with error:', error);
98128
});
99129
}
100130

src/elastalert_server.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Logger from './common/logger';
44
import config from './common/config';
55
import path from 'path';
66
import FileSystem from './common/file_system';
7+
import { listen } from './common/websocket';
78
import setupRouter from './routes/route_setup';
89
import ProcessController from './controllers/process';
910
import RulesController from './controllers/rules';
@@ -81,8 +82,27 @@ export default class ElastalertServer {
8182
self._fileSystemController.createDirectoryIfNotExists(self.getDataFolder()).catch(function (error) {
8283
logger.error('Error creating data folder with error:', error);
8384
});
84-
85+
8586
logger.info('Server listening on port ' + config.get('port'));
87+
88+
let wss = listen(config.get('wsport'));
89+
90+
wss.on('connection', ws => {
91+
ws.on('message', (data) => {
92+
try {
93+
data = JSON.parse(data);
94+
if (data.rule) {
95+
let rule = data.rule;
96+
let options = data.options;
97+
self._testController.testRule(rule, options, ws);
98+
}
99+
} catch (error) {
100+
console.log(error);
101+
}
102+
});
103+
});
104+
105+
logger.info('Websocket listening on port 3333');
86106
} catch (error) {
87107
logger.error('Starting server failed with error:', error);
88108
process.exit(1);

src/handlers/metadata/get.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,43 @@ import config from '../../common/config';
22
import { getClient } from '../../common/elasticsearch_client';
33

44

5+
function escapeLuceneSyntax(str) {
6+
return [].map
7+
.call(str, char => {
8+
if (
9+
char === '/' ||
10+
char === '+' ||
11+
char === '-' ||
12+
char === '&' ||
13+
char === '|' ||
14+
char === '!' ||
15+
char === '(' ||
16+
char === ')' ||
17+
char === '{' ||
18+
char === '}' ||
19+
char === '[' ||
20+
char === ']' ||
21+
char === '^' ||
22+
char === '"' ||
23+
char === '~' ||
24+
char === '*' ||
25+
char === '?' ||
26+
char === ':' ||
27+
char === '\\'
28+
) {
29+
return `\\${char}`;
30+
}
31+
return char;
32+
})
33+
.join('');
34+
}
35+
536
function getQueryString(request) {
637
if (request.params.type === 'elastalert_error') {
738
return '*:*';
839
}
940
else {
10-
return `rule_name:${request.query.rule_name || '*'}`;
41+
return `rule_name:"${escapeLuceneSyntax(request.query.rule_name) || '*'}"`;
1142
}
1243
}
1344

0 commit comments

Comments
 (0)