├── .editorconfig
├── .eslintrc
├── .gitignore
├── .npmrc
├── .nvmrc
├── README.md
├── hydra-cli.js
├── license.txt
├── message.json
└── package.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["mocha"],
3 | "extends": ["eslint:recommended", "google"],
4 | "parserOptions": {
5 | "ecmaVersion": 6,
6 | "ecmaFeatures": {
7 | "jsx": true
8 | },
9 | "sourceType": "module"
10 | },
11 | "rules": {
12 | "valid-jsdoc": [ 2, {
13 | "requireReturn": true,
14 | "requireReturnDescription": true,
15 | "requireParamDescription": true,
16 | "prefer": {
17 | "return": "return"
18 | }
19 | }],
20 | "comma-dangle": 0,
21 | "curly": 2,
22 | "semi": [2, "always"],
23 | "no-console": 0,
24 | "no-debugger": 2,
25 | "no-extra-semi": 2,
26 | "no-constant-condition": 2,
27 | "no-alert": 2,
28 | "one-var-declaration-per-line": [2, "always"],
29 | "operator-linebreak": [
30 | 2,
31 | "after"
32 | ],
33 | "max-len": [
34 | 2,
35 | 240
36 | ],
37 | "indent": [
38 | 2,
39 | 2,
40 | {
41 | "SwitchCase": 1
42 | }
43 | ],
44 | "quotes": [
45 | 2,
46 | "single",
47 | {
48 | "avoidEscape": true
49 | }
50 | ],
51 | "no-multi-str": 2,
52 | "no-mixed-spaces-and-tabs": 2,
53 | "no-trailing-spaces": 2,
54 | "space-unary-ops": [
55 | 2,
56 | {
57 | "nonwords": false,
58 | "overrides": {}
59 | }
60 | ],
61 | "one-var": [
62 | 2,
63 | {
64 | "uninitialized": "always",
65 | "initialized": "never"
66 | }
67 | ],
68 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
69 | "keyword-spacing": [
70 | 2,
71 | {}
72 | ],
73 | "space-infix-ops": 2,
74 | "space-before-blocks": [
75 | 2,
76 | "always"
77 | ],
78 | "eol-last": 2,
79 | "space-in-parens": [
80 | 2,
81 | "never"
82 | ],
83 | "no-multiple-empty-lines": 2,
84 | "no-multi-spaces": 2,
85 | "key-spacing": [
86 | 2,
87 | {
88 | "beforeColon": false,
89 | "afterColon": true
90 | }
91 | ]
92 | },
93 | "env": {
94 | "browser": true,
95 | "node": true,
96 | "es6": true,
97 | "mocha": true
98 | },
99 | "globals": {
100 | "-": 0,
101 | "define": true,
102 | "expect": true,
103 | "it": true,
104 | "require": true
105 | }
106 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .bak
3 | .log
4 | scratch.*
5 | config/properties.js
6 | node_modules
7 | public
8 |
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v6.2.1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hydra-cli
2 |
3 | [](https://badge.fury.io/js/hydra-cli)
[]()
4 |
5 | Hydra command line interface for use with [Hydra](https://github.com/flywheelsports/hydra) enabled microservices.
6 |
7 | ## install
8 |
9 | ```shell
10 | $ [sudo] npm install -g hydra-cli
11 | ```
12 |
13 | ## Command overview
14 |
15 | ```
16 | hydra-cli version 1.8.0
17 | Usage: hydra-cli command [parameters]
18 | See docs at: https://github.com/flywheelsports/hydra-cli
19 |
20 | A command line interface for Hydra services
21 |
22 | Commands:
23 | help - this help list
24 | cfg list serviceName - display a list of config versions
25 | cfg pull label - download configuration file
26 | cfg push label filename - update configuration file
27 | cfg remove label - remove a configuration version
28 | config instanceName - configure connection to redis
29 | config list - display current configuration
30 | use instanceName - name of redis instance to use
31 | health [serviceName] - display service health
32 | healthlog serviceName - display service health log
33 | message create - create a message object
34 | message send message.json - send a message
35 | message queue message.json - queue a message
36 | nodes [serviceName] - display service instance nodes
37 | redis info - display redis info
38 | refresh node list - refresh list of nodes
39 | rest path [payload.json] - make an HTTP RESTful call to a service
40 | routes [serviceName] - display service API routes
41 | services [serviceName] - display list of services
42 | shell - display command to open redis shell
43 | ```
44 |
45 | ## help
46 | Lists the help screen above.
47 |
48 | > syntax: hydra-cli help
49 |
50 | ```shell
51 | $ hydra-cli help
52 | ```
53 |
54 | ## cfg
55 |
56 | Hydra-cli allows you to push and pull configurations for your microservices.
57 |
58 | In this example we're pushing the config.json file from the local directory and storing it in Redis under the myservice:0.0.1 key. This allows the `myservice` service to pull its 0.0.1 configuration during startup.
59 |
60 | ```shell
61 | $ hydra-cli cfg push myservice:0.0.1 config.json
62 | ```
63 |
64 | We can download the configuration file for the myservice using:
65 |
66 | ```shell
67 | $ hydra-cli cfg pull myservice:0.0.1 > config.json
68 | ```
69 |
70 | Because the `cfg pull` command outputs the contents to screen you'll need to use the standard out redirection to copy the output to a file.
71 |
72 | You can retrieve a list of config versions for a given service using:
73 |
74 | ```shell
75 | $ hydra-cli cfg list myservice
76 | ```
77 |
78 | To remove an entry, use:
79 |
80 | ```shell
81 | $ hydra-cli cfg remove myservice:0.0.1
82 | ```
83 |
84 | ## config
85 |
86 | Hydra-cli requires that you first point it to the instance of Redis which your microservices are using.
87 | You must name the instance you're configuring.
88 |
89 | > syntax: hydra-cli config instanceName
90 |
91 | ```shell
92 | $ hydra-cli config local
93 | redisUrl: 127.0.0.1
94 | redisPort: 6379
95 | redisDb: 15
96 | ```
97 |
98 | ## config list
99 | Lists your config settings.
100 |
101 | > syntax: hydra-cli config list
102 |
103 | ```javascript
104 | $ hydra-cli config list
105 | {
106 | "version": 2,
107 | "local": {
108 | "redisUrl": "127.0.0.1",
109 | "redisPort": "6379",
110 | "redisDb": "15"
111 | }
112 | }
113 | ```
114 |
115 | ## use
116 | Specify which redis instance to use.
117 |
118 | ```javascript
119 | $ hydra-cli use local
120 | ```
121 |
122 | ## health
123 | The health command displays the health of services which are currently running.
124 | If you specify the name of a service than only that service is displayed.
125 |
126 | > syntax: hydra-cli health [serviceName]
127 |
128 | > serviceName is optional
129 |
130 | ```javascript
131 | $ hydra-cli health
132 | [
133 | [
134 | {
135 | "updatedOn": "2016-11-22T18:01:49.637Z",
136 | "serviceName": "hello-service",
137 | "instanceID": "2c87057963121e1d7983bc952951ff3f",
138 | "sampledOn": "2016-11-22T18:01:49.637Z",
139 | "processID": 54906,
140 | "architecture": "x64",
141 | "platform": "darwin",
142 | "nodeVersion": "v6.8.1",
143 | "memory": {
144 | "rss": 41324544,
145 | "heapTotal": 24154112,
146 | "heapUsed": 20650296
147 | },
148 | "uptime": "2 hours, 58 minutes, 27.68 seconds",
149 | "uptimeSeconds": 10707.68,
150 | "usedDiskSpace": "53%"
151 | }
152 | ],
153 | [
154 | {
155 | "updatedOn": "2016-11-22T18:01:50.323Z",
156 | "serviceName": "red-service",
157 | "instanceID": "ce62591552a8b304d7236c820d0a4859",
158 | "sampledOn": "2016-11-22T18:01:50.323Z",
159 | "processID": 13431,
160 | "architecture": "x64",
161 | "platform": "darwin",
162 | "nodeVersion": "v6.8.1",
163 | "memory": {
164 | "rss": 45961216,
165 | "heapTotal": 26251264,
166 | "heapUsed": 20627944
167 | },
168 | "uptime": "1 hour, 9 minutes, 19.038999999999536 seconds",
169 | "uptimeSeconds": 4159.039,
170 | "usedDiskSpace": "53%"
171 | }
172 | ]
173 | ]
174 | ```
175 |
176 | ## healthlog
177 | Displays internal log for a service. The serviceName is required.
178 |
179 | > syntax: hydra-cli healthlog serviceName
180 |
181 | > serviceName is required
182 |
183 | ```shell
184 | $ hydra-cli healthlog red-service
185 | fatal | 2016-11-22T16:51:58.609Z PID:12664: Port 6000 is already in use
186 | ```
187 |
188 | ## message create
189 | The `message create` command will create a [UMF](https://github.com/cjus/umf) message which you can customize for use with the `message send` command.
190 |
191 | > syntax: hydra-cli message create
192 |
193 | ```shell
194 | $ hydra-cli message create
195 | {
196 | "to": "{serviceName here}:/",
197 | "from": "hydra-cli:/",
198 | "mid": "378572ab-3cd3-414b-b56f-3d2bbf089c19",
199 | "timestamp": "2016-11-22T18:14:49.441Z",
200 | "version": "UMF/1.4.3",
201 | "body": {}
202 | }
203 | ```
204 |
205 | Just edit the to field with the name of the service you'd like to send a message to.
206 |
207 | ## message send
208 |
209 | > syntax: hydra-cli send message.json
210 |
211 | > message.json is the name of a file containing JSON which you wish to send. This field is required.
212 |
213 | The `message send` command sends a [UMF](https://github.com/cjus/umf) fomatted message to a service. Use the `message create` command to create a message and place it in a file, such as message.json.
214 |
215 | ```shell
216 | $ hydra-cli message send message.json
217 | ```
218 |
219 | ## message queue
220 |
221 | ```shell
222 | $ hydra-cli message queue message.json
223 | ```
224 |
225 | Like send message but the message is pushed on the a service's queue rather then sent directly to a service.
226 |
227 | ## nodes
228 |
229 | Displays a list of services instances (called nodes). If you specify a serviceName then only service instances with that name will be displayed.
230 |
231 | > syntax: hydra-cli nodes [serviceName]
232 |
233 | > serviceName is optional
234 |
235 | ```javascript
236 | $ hydra-cli nodes
237 | [
238 | {
239 | "serviceName": "red-service",
240 | "serviceDescription": "red stuff",
241 | "version": "0.0.1",
242 | "instanceID": "6b8eacc1ead3e1f904647110ce8c092f",
243 | "updatedOn": "2016-11-22T18:19:04.377Z",
244 | "processID": 3801,
245 | "ip": "10.1.1.163",
246 | "port": 6000,
247 | "elapsed": 3
248 | },
249 | {
250 | "serviceName": "hello-service",
251 | "serviceDescription": "Hello service demo",
252 | "version": "not specified",
253 | "instanceID": "2c87057963121e1d7983bc952951ff3f",
254 | "updatedOn": "2016-11-22T18:19:02.943Z",
255 | "processID": 54906,
256 | "ip": "192.168.1.186",
257 | "port": 3000,
258 | "elapsed": 4
259 | }
260 | ]
261 | ```
262 |
263 | ## redis
264 | You can pull Redis runtime info using:
265 |
266 | ```shell
267 | $ hydra-cli redis info
268 | # Server
269 | redis_version:3.0.7
270 | redis_git_sha1:00000000
271 | redis_git_dirty:0
272 | redis_build_id:9608eaf6bab769c5
273 | redis_mode:standalone
274 | os:Linux 4.9.49-moby x86_64
275 | arch_bits:64
276 | multiplexing_api:epoll
277 | gcc_version:4.9.2
278 | ...
279 | ```
280 |
281 | ## refresh
282 | Refresh clears dead services from the nodes list. Use this when `hydra-cli nodes` returns nodes which have expired.
283 |
284 | ```javascript
285 | $ hydra-cli refresh node list
286 | ```
287 |
288 | ## rest
289 | The `rest` command allows you to make a RESTful API call to a service which exposes HTTP endpoints.
290 |
291 | > syntax: hydra-cli rest path [payload.json]
292 |
293 | > payload is a file containing JSON which you wish to send with POST and PUT calls.
294 |
295 | Note the use of the path `hello-service:[get]/` below. This format is required.
296 | The full format is: `{serviceID}@{serviceName}:{HTTP method get/post/put/delete etc...}{API path}
297 |
298 | This is an example of how you would call an API endpoint on a specific service instance:
299 |
300 | ```
301 | $ hydra-cli rest a921a00de7caf9103a0d96346b3a61f8@hello-service:[get]/v1/hello/greeting
302 | ```
303 |
304 | You may supply a file to upload when using `post` and `put` HTTP calls.
305 |
306 | > You can locate a service's instance using the `hydra-cli nodes` command.
307 |
308 | ```javascript
309 | $ hydra-cli rest hello-service:[get]/
310 | {
311 | "headers": {
312 | "access-control-allow-origin": "*",
313 | "x-process-id": "44032",
314 | "x-dns-prefetch-control": "off",
315 | "x-frame-options": "SAMEORIGIN",
316 | "x-download-options": "noopen",
317 | "x-content-type-options": "nosniff",
318 | "x-xss-protection": "1; mode=block",
319 | "x-powered-by": "hello-service/0.11.4",
320 | "content-type": "text/html; charset=utf-8",
321 | "content-length": "59",
322 | "etag": "W/\"3b-Kwmf5A3/0YsqP+L3cGK3eg\"",
323 | "x-response-time": "17.750ms",
324 | "date": "Tue, 22 Nov 2016 19:09:51 GMT",
325 | "connection": "close"
326 | },
327 | "body": "hello from hello-service - a921a00de7caf9103a0d96346b3a61f8",
328 | "statusCode": 200
329 | }
330 | ```
331 |
332 | If you forget to specify an HTTP type then you'll see a response like this one:
333 |
334 | ```javascript
335 | $ hydra-cli rest hello-service:/
336 | {
337 | "statusCode": 400,
338 | "statusMessage": "Bad Request",
339 | "statusDescription": "Request is invalid, missing parameters?",
340 | "result": {
341 | "reason": "HTTP method not specified in `to` field"
342 | }
343 | }
344 | ```
345 |
346 | ## routes
347 | The routes command will display routes which services register via hydra-express or via the use of the hydra.registerRoute call.
348 |
349 | > syntax: hydra-cli routes
350 |
351 | ```javascript
352 | $ hydra-cli routes
353 | {
354 | "hello-service": [
355 | "[GET]/_config/hello-service",
356 | "[get]/"
357 | ]
358 | }
359 | ```
360 |
361 | ## services
362 |
363 | Display a list of registered services.
364 |
365 | > syntax: hydra-cli services
366 |
367 | ```javascript
368 | $ hydra-cli services
369 | [
370 | {
371 | "serviceName": "hello-service",
372 | "type": "demo",
373 | "registeredOn": "2016-11-22T19:09:47.772Z"
374 | },
375 | {
376 | "serviceName": "red-service",
377 | "type": "red",
378 | "registeredOn": "2016-11-22T19:31:31.061Z"
379 | },
380 | {
381 | "serviceName": "blue-service",
382 | "type": "blue",
383 | "registeredOn": "2016-11-22T19:31:27.853Z"
384 | }
385 | ]
386 | ```
387 |
388 | ## shell
389 |
390 | Display command used to open a redis shell using redis-cli. On *nix machines you can use the following to quickly open a redis shell:
391 |
392 | ```shell
393 | $ $(hydra-cli shell)
394 | 52.3.229.252:6379[15]>
395 | ```
396 |
--------------------------------------------------------------------------------
/hydra-cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | const version = require('./package.json').version;
5 |
6 | const fs = require('fs');
7 | const rl = require('readline');
8 | const hydra = require('hydra');
9 | const Utils = hydra.getUtilsHelper();
10 | const config = hydra.getConfigHelper();
11 | const UMFMessage = hydra.getUMFMessageHelper();
12 |
13 | const CONFIG_FILE_VERSION = 2;
14 |
15 | /**
16 | * @name Program
17 | */
18 | class Program {
19 | /**
20 | * @name constructor
21 | */
22 | constructor() {
23 | this.configData = null;
24 | this.configName = '';
25 | this.hydraConfig = null;
26 | }
27 |
28 | /**
29 | * @name displayHelp
30 | * @description Display program help info
31 | * @return {undefined}
32 | */
33 | displayHelp() {
34 | console.log(`hydra-cli version ${version}`);
35 | console.log('Usage: hydra-cli command [parameters]');
36 | console.log('See docs at: https://github.com/flywheelsports/hydra-cli');
37 | console.log('');
38 | console.log('A command line interface for Hydra services');
39 | console.log('');
40 | console.log('Commands:');
41 | console.log(' help - this help list');
42 | console.log(' cfg list serviceName - display a list of config versions');
43 | console.log(' cfg pull label - download configuration file');
44 | console.log(' cfg push label filename - update configuration file');
45 | console.log(' cfg remove label - remove a configuration version');
46 | console.log(' config instanceName - configure connection to redis');
47 | console.log(' config list - display current configuration');
48 | console.log(' use instanceName - name of redis instance to use');
49 | console.log(' health [serviceName] - display service health');
50 | console.log(' healthlog serviceName - display service health log');
51 | console.log(' message create - create a message object');
52 | console.log(' message send message.json - send a message');
53 | console.log(' message queue message.json - queue a message');
54 | console.log(' nodes [serviceName] - display service instance nodes');
55 | console.log(' redis info - display redis info');
56 | console.log(' refresh node list - refresh list of nodes');
57 | console.log(' rest path [payload.json] - make an HTTP RESTful call to a service');
58 | console.log(' routes [serviceName] - display service API routes');
59 | console.log(' services [serviceName] - display list of services');
60 | console.log(' shell - display command to open redis shell');
61 | console.log('');
62 | }
63 |
64 | /**
65 | * @name getUserHome
66 | * @description Retrieve user's home directory
67 | * @return {string} - user's home directory path
68 | */
69 | getUserHome() {
70 | return process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'];
71 | }
72 |
73 | /**
74 | * @name main
75 | * @description entry point for command dispatch processing
76 | * @return {undefined}
77 | */
78 | main() {
79 | if (process.argv.length < 3) {
80 | this.displayHelp();
81 | process.exit();
82 | return;
83 | }
84 |
85 | let command = process.argv[2];
86 | let args = process.argv.slice(3);
87 |
88 | this.hydraConfig = `${this.getUserHome()}/.hydra-cli`;
89 | fs.readFile(this.hydraConfig, 'utf8', (err, data) => {
90 | if (!err) {
91 | try {
92 | this.configData = JSON.parse(data);
93 | if (!this.configData.version) {
94 | this.configData = {
95 | version: CONFIG_FILE_VERSION
96 | };
97 | }
98 | if (command === 'use' || command === 'config') {
99 | this.processCommand(command, args);
100 | return;
101 | }
102 | let conf = {
103 | 'hydra': {
104 | 'serviceName': 'hydra-cli',
105 | 'serviceDescription': 'Not a service',
106 | 'serviceIP': '',
107 | 'servicePort': 0,
108 | 'serviceType': 'non',
109 | 'redis': {
110 | 'url': this.configData.redisUrl || '',
111 | 'port': this.configData.redisPort || 0,
112 | 'db': this.configData.redisDb || 0
113 | }
114 | }
115 | };
116 | if (this.configData.redisPassword && this.configData.redisPassword !== '') {
117 | conf.hydra.redis.password = this.configData.redisPassword;
118 | }
119 |
120 | let tid = setTimeout(() => {
121 | console.log('Unable to connect to Redis. Use "hydra-cli config list" or "hydra-cli use instanceName" to switch to another instance.');
122 | clearTimeout(tid);
123 | process.exit();
124 | return;
125 | }, 5000);
126 |
127 | hydra.init(conf)
128 | .then(() => {
129 | this.processCommand(command, args);
130 | return 0;
131 | })
132 | .catch((err) => {
133 | console.log('err', err.message);
134 | this.configData = null;
135 | });
136 | } catch (err) {
137 | console.log('err', err.message);
138 | this.configData = null;
139 | this.processCommand(command, args);
140 | }
141 | } else {
142 | this.configData = null;
143 | this.processCommand(command, args);
144 | }
145 | });
146 | }
147 |
148 | /**
149 | * @name processCommand
150 | * @description process hydra-cli command
151 | * @param {string} command - command string
152 | * @param {array} args - array of command params
153 | * @return {undefined}
154 | */
155 | processCommand(command, args) {
156 | if (!this.configData && command !== 'use' && command !== 'config') {
157 | console.log('Warning, hydra-cli is not configured.');
158 | console.log('Use the hydra-cli config command.');
159 | return;
160 | }
161 |
162 | switch (command) {
163 | case 'cfg':
164 | this.handleCfgCommand(args);
165 | break;
166 | case 'config':
167 | this.handleConfigCommand(args);
168 | break;
169 | case 'use':
170 | this.handleUseCommand(args);
171 | break;
172 | case 'health':
173 | this.handleHealth(args);
174 | break;
175 | case 'healthlog':
176 | this.handleHealthLog(args);
177 | break;
178 | case 'help':
179 | this.displayHelp();
180 | process.exit();
181 | break;
182 | case 'message':
183 | switch (args[0]) {
184 | case 'create':
185 | this.handleMessageCreate(args);
186 | break;
187 | case 'send':
188 | this.handleMessageSend(args);
189 | break;
190 | case 'queue':
191 | this.handleMessageQueue(args);
192 | break;
193 | default:
194 | console.log(`Unknown message options: ${args[0]}`);
195 | this.exitApp();
196 | break;
197 | }
198 | break;
199 | case 'nodes':
200 | this.handleNodesList(args);
201 | break;
202 | case 'refresh':
203 | this.handleRefresh(args);
204 | break;
205 | case 'redis':
206 | this.handleRedis(args);
207 | break;
208 | case 'rest':
209 | this.handleRest(args);
210 | break;
211 | case 'routes':
212 | this.handleRoutes(args);
213 | break;
214 | case 'services':
215 | this.handleServices(args);
216 | break;
217 | case 'shell':
218 | this.handleShell();
219 | break;
220 | default:
221 | console.log(`Unknown command: ${command}`);
222 | this.exitApp();
223 | break;
224 | }
225 | }
226 |
227 | /**
228 | * @name exitApp
229 | * @description properly exit this app
230 | * @return {undefined}
231 | */
232 | exitApp() {
233 | setTimeout(() => {
234 | hydra.shutdown();
235 | process.exit();
236 | }, 250);
237 | }
238 |
239 | /**
240 | * @name displayJSON
241 | * @description pretty print json
242 | * @param {string} json - stringified json
243 | * @return {undefined}
244 | */
245 | displayJSON(json) {
246 | if (typeof json === 'string') {
247 | let js = Utils.safeJSONParse(json);
248 | if (!js) {
249 | console.log(json);
250 | } else {
251 | console.log(JSON.stringify(js, null, 2));
252 | }
253 | } else {
254 | console.log(JSON.stringify(json, null, 2));
255 | }
256 | }
257 |
258 | /* ************************************************************************* */
259 | /* ************************************************************************* */
260 | /* ************************************************************************* */
261 | /* ************************************************************************* */
262 |
263 | /**
264 | * @name handleCfgCommand
265 | * @description handle service config files
266 | * @param {array} args - program arguments
267 | * @return {undefined}
268 | */
269 | handleCfgCommand(args) {
270 | if (!args[0]) {
271 | console.log('requires "push" or "pull" options');
272 | this.exitApp();
273 | }
274 |
275 | if (args[0] === 'push') {
276 | if (args.length != 3) {
277 | console.log('cfg push requires a label and config filename');
278 | this.exitApp();
279 | }
280 | fs.readFile(args[2], 'utf8', (err, data) => {
281 | if (!err) {
282 | let strMessage = Utils.safeJSONParse(data);
283 | hydra.putConfig(args[1], strMessage)
284 | .then(() => {
285 | this.exitApp();
286 | })
287 | .catch((err) => {
288 | console.log(err.message);
289 | this.exitApp();
290 | });
291 | } else {
292 | console.log(`cfg push can't open file ${args[2]}`);
293 | this.exitApp();
294 | }
295 | });
296 | }
297 |
298 | if (args[0] === 'pull') {
299 | hydra.getConfig(args[1])
300 | .then((result) => {
301 | this.displayJSON(result);
302 | this.exitApp();
303 | })
304 | .catch((err) => {
305 | console.log(err.message);
306 | this.exitApp();
307 | });
308 | }
309 |
310 | if (args[0] === 'list') {
311 | hydra.listConfig(args[1])
312 | .then((result) => {
313 | result.forEach((item) => {
314 | console.log(item);
315 | });
316 | this.exitApp();
317 | })
318 | .catch((err) => {
319 | console.log(err.message);
320 | this.exitApp();
321 | });
322 | }
323 |
324 | if (args[0] === 'remove') {
325 | if (!args[1]) {
326 | console.log('Label is required.');
327 | this.exitApp();
328 | return;
329 | }
330 | let segs = args[1].split(':');
331 | if (segs.length !== 2) {
332 | console.log('Label format must be in serviceName:version format.');
333 | this.exitApp();
334 | return;
335 | }
336 | let redisClient = hydra.getClonedRedisClient();
337 | redisClient.hdel(`hydra:service:${segs[0]}:configs`, segs[1], (err, result) => {
338 | if (result > 0) {
339 | console.log(`Removed entry ${args[1]}`);
340 | } else {
341 | console.log(`Unable to locate entry for ${args[1]}`);
342 | }
343 | redisClient.quit();
344 | this.exitApp();
345 | });
346 | }
347 | }
348 |
349 | /**
350 | * @name handleConfigCommand
351 | * @description handle the creation of the app config DOT file.
352 | * @param {array} args - program arguments
353 | * @return {undefined}
354 | */
355 | handleConfigCommand(args) {
356 | if (args.length === 1 && args[0] === 'list') {
357 | console.log(JSON.stringify(this.configData, null, 2));
358 | process.exit();
359 | return;
360 | } else if (args.length !== 1) {
361 | console.log('An instance name is required.');
362 | process.exit();
363 | return;
364 | }
365 |
366 | this.configName = args[0];
367 |
368 | let prompts = rl.createInterface(process.stdin, process.stdout);
369 | prompts.question('redisUrl: ', (redisUrl) => {
370 | prompts.question('redisPort: ', (redisPort) => {
371 | prompts.question('redisDb: ', (redisDb) => {
372 | prompts.question('redisPassword (blank for null): ', (redisPassword)=>{
373 | let data = this.configData || {
374 | version: CONFIG_FILE_VERSION
375 | };
376 | Object.assign(data, {
377 | redisUrl,
378 | redisPort,
379 | redisDb,
380 | [this.configName]: {
381 | redisUrl,
382 | redisPort,
383 | redisDb
384 | }
385 | });
386 | if (redisPassword && redisPassword !== '') {
387 | data.redisPassword = redisPassword;
388 | data[this.configName].redisPassword = redisPassword;
389 | }
390 | fs.writeFile(this.hydraConfig, JSON.stringify(data), (err) => {
391 | if (err) {
392 | console.log(err.message);
393 | }
394 | process.exit();
395 | });
396 | });
397 | });
398 | });
399 | });
400 | }
401 |
402 | /**
403 | * @name handleUseCommand
404 | * @description handle the switching of configs
405 | * @param {array} args - program arguments
406 | * @return {undefined}
407 | */
408 | handleUseCommand(args) {
409 | if (args.length !== 1) {
410 | console.log('An instance name is required.');
411 | process.exit();
412 | return;
413 | }
414 | this.configName = args[0];
415 | if (!this.configData[this.configName]) {
416 | console.log('Instance name not found, create with hydra-cli config instanceName command.');
417 | process.exit();
418 | return;
419 | }
420 |
421 | this.configData = Object.assign(this.configData, this.configData[this.configName]);
422 | fs.writeFile(this.hydraConfig, JSON.stringify(this.configData), (err) => {
423 | if (err) {
424 | console.log(err.message);
425 | }
426 | process.exit();
427 | });
428 | }
429 |
430 | /**
431 | * @name handleHealth
432 | * @description display service health
433 | * @param {array} args - program arguments
434 | * @return {undefined}
435 | */
436 | handleHealth(args) {
437 | let serviceName = args[0];
438 | let entries = [];
439 | hydra.getServiceHealthAll()
440 | .then((services) => {
441 | services.forEach((service) => {
442 | if (serviceName && service.health.length > 0 && service.health[0].serviceName === serviceName) {
443 | entries.push(service.health);
444 | }
445 | if (!serviceName) {
446 | entries.push(service.health);
447 | }
448 | });
449 | this.displayJSON(entries);
450 | this.exitApp();
451 | });
452 | }
453 |
454 | /**
455 | * @name handleHealthLog
456 | * @description display service health log
457 | * @param {array} args - program arguments
458 | * @return {undefined}
459 | */
460 | handleHealthLog(args) {
461 | let serviceName = args[0];
462 |
463 | if (!serviceName) {
464 | console.log('Missing serviceName');
465 | this.exitApp();
466 | return;
467 | }
468 |
469 | hydra.getServiceHealthLog(serviceName)
470 | .then((logs) => {
471 | logs.forEach((entry) => {
472 | console.error(`${entry.type} | ${entry.ts} PID:${entry.processID}: ${entry.message}`);
473 | });
474 | this.exitApp();
475 | });
476 | }
477 |
478 | /**
479 | * @name handleMessageCreate
480 | * @description Display a new message
481 | * @param {array} _args - program arguments
482 | * @return {undefined}
483 | */
484 | handleMessageCreate(_args) {
485 | let msg = UMFMessage.createMessage({
486 | to: '{serviceName here}:/',
487 | from: 'hydra-cli:/',
488 | body: {}
489 | });
490 | this.displayJSON(msg);
491 | this.exitApp();
492 | }
493 |
494 | /**
495 | * @name handleMessageSend
496 | * @description Send message
497 | * @param {array} args - program arguments
498 | * @return {undefined}
499 | */
500 | handleMessageSend(args) {
501 | if (args.length !== 2) {
502 | console.log('Invalid number of parameters');
503 | this.exitApp();
504 | return;
505 | }
506 | config.init(args[1])
507 | .then(() => {
508 | hydra.sendMessage(config.getObject());
509 | this.exitApp();
510 | return null;
511 | })
512 | .catch((err) => {
513 | console.log(err.message);
514 | this.exitApp();
515 | });
516 | }
517 |
518 | /**
519 | * @name handleMessageQueue
520 | * @description Queue message
521 | * @param {array} args - program arguments
522 | * @return {undefined}
523 | */
524 | handleMessageQueue(args) {
525 | if (args.length !== 2) {
526 | console.log('Invalid number of parameters');
527 | this.exitApp();
528 | return;
529 | }
530 | config.init(args[1])
531 | .then(() => {
532 | hydra.queueMessage(config.getObject());
533 | this.exitApp();
534 | return null;
535 | })
536 | .catch((err) => {
537 | console.log(err.message);
538 | this.exitApp();
539 | });
540 | }
541 |
542 | /**
543 | * @name handleNodesList
544 | * @description handle the display of service nodes.
545 | * @param {array} args - program arguments
546 | * @return {undefined}
547 | */
548 | handleNodesList(args) {
549 | hydra.getServiceNodes()
550 | .then((nodes) => {
551 | let serviceList = [];
552 | let serviceName = args[0];
553 | if (serviceName) {
554 | nodes.forEach((service) => {
555 | if (serviceName === service.serviceName) {
556 | serviceList.push(service);
557 | }
558 | });
559 | } else {
560 | serviceList = nodes;
561 | }
562 | this.displayJSON(serviceList);
563 | this.exitApp();
564 | })
565 | .catch((err) => console.log(err));
566 | }
567 |
568 | /**
569 | * @name handleRedis
570 | * @description handle redis calls
571 | * @param {array} args - program arguments
572 | * @return {undefined}
573 | */
574 | handleRedis(args) {
575 | if (!args[0]) {
576 | console.log('requires "info" option');
577 | this.exitApp();
578 | return;
579 | }
580 |
581 | if (args[0] === 'info') {
582 | let redisClient = hydra.getClonedRedisClient();
583 | redisClient.info((err, result) => {
584 | if (!err) {
585 | console.log(`${result}`);
586 | } else {
587 | console.log(err);
588 | }
589 | redisClient.quit();
590 | this.exitApp();
591 | });
592 | }
593 |
594 | this.exitApp();
595 | }
596 |
597 | /**
598 | * @name handleRest
599 | * @description handle RESTful calls
600 | * @param {array} args - program arguments
601 | * @return {undefined}
602 | */
603 | handleRest(args) {
604 | let route = UMFMessage.parseRoute(args[0]);
605 | if (route.error) {
606 | console.log(`${route.error}`);
607 | this.exitApp();
608 | return;
609 | }
610 | let method = route.httpMethod || 'get';
611 | if ((method === 'get' || method === 'delete') && args.length > 1) {
612 | console.log(`Payload not allowed for HTTP '${method}' method`);
613 | this.exitApp();
614 | return;
615 | }
616 | if (args.length > 1) {
617 | config.init(args[1])
618 | .then(() => {
619 | let msg = UMFMessage.createMessage({
620 | to: `${args[0]}`,
621 | from: 'hydra-cli:/',
622 | headers: {
623 | 'content-type': 'application/json'
624 | },
625 | body: config.getObject() || {}
626 | });
627 | hydra.makeAPIRequest(msg)
628 | .then((res) => {
629 | if ((res.payLoad ?? res) == undefined) {
630 | console.trace(`Error parsing response from service.`);
631 | this.exitApp();
632 | return;
633 | }
634 | const consoleOutput = {statusCode: res.statusCode, statusMessage: res.statusMessage, statusDescription: res.statusDescription, headers: res.headers}
635 | consoleOutput.result = res.payLoad != null ? res.payLoad.toString(): (() => {
636 | const internalKeys = Object.keys(consoleOutput)
637 | const resultObj = {};
638 | Object.entries(res).forEach(([key, val]) => {
639 | if (!internalKeys.includes(key) && !(key == "result" && Object.entries(val).length == 0)) {
640 | resultObj[key] = val;
641 | }
642 | })
643 | return resultObj;
644 | })()
645 | if (res.payLoad != undefined) {
646 | delete res.payLoad;
647 | }
648 | this.displayJSON(consoleOutput);
649 | this.exitApp();
650 | })
651 | .catch((err) => {
652 | console.log(err.message);
653 | this.exitApp();
654 | });
655 | return null;
656 | })
657 | .catch((_err) => {
658 | console.log(`Unable to open ${args[1]}`);
659 | this.exitApp();
660 | });
661 | } else {
662 | let msg = UMFMessage.createMessage({
663 | to: `${args[0]}`,
664 | from: 'hydra-cli:/',
665 | body: {}
666 | });
667 | hydra.makeAPIRequest(msg)
668 | .then((res) => {
669 | if ((res.payLoad ?? res) == undefined) {
670 | console.trace(`Error parsing response from service.`);
671 | this.exitApp();
672 | return;
673 | }
674 | const consoleOutput = {statusCode: res.statusCode, statusMessage: res.statusMessage, statusDescription: res.statusDescription, headers: res.headers}
675 | consoleOutput.result = res.payLoad != null ? res.payLoad.toString(): (() => {
676 | const internalKeys = Object.keys(consoleOutput)
677 | const resultObj = {};
678 | Object.entries(res).forEach(([key, val]) => {
679 | if (!internalKeys.includes(key) && !(key == "result" && Object.entries(val).length == 0)) {
680 | resultObj[key] = val;
681 | }
682 | })
683 | return resultObj;
684 | })()
685 | if (res.payLoad != undefined) {
686 | delete res.payLoad;
687 | }
688 | this.displayJSON(consoleOutput);
689 | this.exitApp();
690 | })
691 | .catch((err) => {
692 | console.log(err.message);
693 | this.exitApp();
694 | });
695 | return null;
696 | }
697 | }
698 |
699 | /**
700 | * @name handleRoutes
701 | * @description handle the display of service routes
702 | * @param {array} args - program arguments
703 | * @return {undefined}
704 | */
705 | handleRoutes(args) {
706 | hydra.getAllServiceRoutes()
707 | .then((routes) => {
708 | let serviceName = args[0];
709 | if (serviceName) {
710 | routes = {
711 | serviceName: routes[serviceName]
712 | };
713 | }
714 | this.displayJSON(routes);
715 | this.exitApp();
716 | })
717 | .catch((err) => console.log(err.message));
718 | }
719 |
720 | /**
721 | * @name handleServices
722 | * @description display list of services
723 | * @param {array} args - program arguments
724 | * @return {undefined}
725 | */
726 | handleServices(args) {
727 | hydra.getServices()
728 | .then((services) => {
729 | let serviceList = [];
730 | let serviceName = args[0];
731 | if (serviceName) {
732 | services.forEach((service) => {
733 | if (serviceName === service.serviceName) {
734 | serviceList.push(service);
735 | }
736 | });
737 | } else {
738 | serviceList = services;
739 | }
740 | this.displayJSON(serviceList);
741 | this.exitApp();
742 | })
743 | .catch((err) => console.log(err.message));
744 | }
745 |
746 | /**
747 | * @name handleRefresh
748 | * @description refresh list of nodes
749 | * @param {array} _args - program arguments
750 | * @return {undefined}
751 | */
752 | handleRefresh(_args) {
753 | hydra.getServiceNodes()
754 | .then((nodes) => {
755 | let ids = [];
756 | nodes.forEach((node) => {
757 | if (node.elapsed > 60) {
758 | ids.push(node.instanceID);
759 | }
760 | });
761 | if (ids.length) {
762 | let redisClient = hydra.getClonedRedisClient();
763 | redisClient.hdel('hydra:service:nodes', ids);
764 | redisClient.quit();
765 | }
766 | console.log(`${ids.length} entries removed`);
767 | this.exitApp();
768 | })
769 | .catch((err) => {
770 | console.log(err);
771 | this.exitApp();
772 | });
773 | }
774 |
775 | /**
776 | * @name handleShell
777 | * @summary displays the command used to open a redis shell for the currently selected instance
778 | * @return {undefined}
779 | */
780 | handleShell() {
781 | console.log(`redis-cli -h ${this.configData.redisUrl} -p ${this.configData.redisPort} -n ${this.configData.redisDb}`);
782 | this.exitApp();
783 | }
784 | }
785 |
786 | new Program().main();
787 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Flywheel Sports Inc., and Contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/message.json:
--------------------------------------------------------------------------------
1 | {
2 | "to": "hello-service:/",
3 | "from": "hydra-cli:/",
4 | "mid": "d22dc384-6bf7-4f73-9cb6-867faab15311",
5 | "timestamp": "2016-11-21T21:16:37.626Z",
6 | "version": "UMF/1.4.3",
7 | "body": {
8 | "message": "Hello from the hydra-cli app"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hydra-cli",
3 | "version": "1.8.0",
4 | "description": "Hydra Commandline Interface",
5 | "author": "Carlos Justiniano ",
6 | "keywords": [
7 | "hydra",
8 | "command line",
9 | "microservice"
10 | ],
11 | "analyze": false,
12 | "preferGlobal": true,
13 | "license": "MIT",
14 | "engines": {
15 | "node": ">=6.2.1"
16 | },
17 | "repository": "pnxtech/hydra-cli",
18 | "dependencies": {
19 | "bluebird": "3.5.1",
20 | "fwsp-logger": "0.4.0",
21 | "hydra": "1.8.0"
22 | },
23 | "devDependencies": {
24 | "chai": "3.5.0",
25 | "eslint": "5.15.3",
26 | "eslint-config-google": "0.7.1",
27 | "eslint-plugin-mocha": "4.9.0",
28 | "mocha": "8.1.3",
29 | "redis-mock": "0.17.0",
30 | "superagent": "^6.1.0"
31 | },
32 | "bin": {
33 | "hydra-cli": "hydra-cli.js"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------