antietcd 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -48,8 +48,9 @@ Antietcd doesn't background itself, so use systemd or start-stop-daemon to run i
48
48
 
49
49
  ```
50
50
  node_modules/.bin/anticli [OPTIONS] put <key> [<value>]
51
- node_modules/.bin/anticli [OPTIONS] get <key> [-p|--prefix] [-v|--print-value-only] [-k|--keys-only]
51
+ node_modules/.bin/anticli [OPTIONS] get <key> [-p|--prefix] [-v|--print-value-only] [-k|--keys-only] [--no-temp]
52
52
  node_modules/.bin/anticli [OPTIONS] del <key> [-p|--prefix]
53
+ node_modules/.bin/anticli [OPTIONS] load [--with-lease] < dump.json
53
54
  ```
54
55
 
55
56
  For `put`, if `<value>` is not specified, it will be read from STDIN.
@@ -70,6 +71,9 @@ Options:
70
71
  <dt>--timeout 1000</dt>
71
72
  <dd>Specify request timeout in milliseconds</dd>
72
73
 
74
+ <dt>--json or --write-out=json</dt>
75
+ <dd>Print raw response in JSON</dd>
76
+
73
77
  </dl>
74
78
 
75
79
  ## Options
package/anticli.js CHANGED
@@ -4,6 +4,7 @@
4
4
  // (c) Vitaliy Filippov, 2024
5
5
  // License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
6
6
 
7
+ const fs = require('fs');
7
8
  const fsp = require('fs').promises;
8
9
  const http = require('http');
9
10
  const https = require('https');
@@ -15,13 +16,14 @@ License: Mozilla Public License 2.0 or Vitastor Network Public License 1.1
15
16
  Usage:
16
17
 
17
18
  anticli.js [OPTIONS] put <key> [<value>]
18
- anticli.js [OPTIONS] get <key> [-p|--prefix] [-v|--print-value-only] [-k|--keys-only]
19
+ anticli.js [OPTIONS] get <key> [-p|--prefix] [-v|--print-value-only] [-k|--keys-only] [--no-temp]
19
20
  anticli.js [OPTIONS] del <key> [-p|--prefix]
21
+ anticli.js [OPTIONS] load [--with-lease] < dump.json
20
22
 
21
23
  Options:
22
24
 
23
25
  [--endpoints|-e http://node1:2379,http://node2:2379,http://node3:2379]
24
- [--cert cert.pem] [--key key.pem] [--timeout 1000]
26
+ [--cert cert.pem] [--key key.pem] [--timeout 1000] [--json]
25
27
  `;
26
28
 
27
29
  class AntiEtcdCli
@@ -54,6 +56,19 @@ class AntiEtcdCli
54
56
  {
55
57
  options['keys_only'] = true;
56
58
  }
59
+ else if (arg == '--with_lease')
60
+ {
61
+ options['with_lease'] = true;
62
+ }
63
+ else if (arg == '--write_out' && args[i+1] == 'json')
64
+ {
65
+ i++;
66
+ options['json'] = true;
67
+ }
68
+ else if (arg == '--json' || arg == '--write_out=json')
69
+ {
70
+ options['json'] = true;
71
+ }
57
72
  else if (arg[0] == '-' && arg[1] !== '-')
58
73
  {
59
74
  process.stderr.write('Unknown option '+arg);
@@ -68,9 +83,9 @@ class AntiEtcdCli
68
83
  cmd.push(arg);
69
84
  }
70
85
  }
71
- if (!cmd.length || cmd[0] != 'get' && cmd[0] != 'put' && cmd[0] != 'del')
86
+ if (!cmd.length || cmd[0] != 'get' && cmd[0] != 'put' && cmd[0] != 'del' && cmd[0] != 'load')
72
87
  {
73
- process.stderr.write('Supported commands: get, put, del. Use --help to see details\n');
88
+ process.stderr.write('Supported commands: get, put, del, load. Use --help to see details\n');
74
89
  process.exit(1);
75
90
  }
76
91
  return [ cmd, options ];
@@ -102,12 +117,51 @@ class AntiEtcdCli
102
117
  {
103
118
  await this.del(cmd.slice(1));
104
119
  }
120
+ else if (cmd[0] == 'load')
121
+ {
122
+ await this.load();
123
+ }
105
124
  // wait until output is fully flushed
106
125
  await new Promise(ok => process.stdout.write('', ok));
107
126
  await new Promise(ok => process.stderr.write('', ok));
108
127
  process.exit(0);
109
128
  }
110
129
 
130
+ async load()
131
+ {
132
+ const dump = JSON.parse(await new Promise((ok, no) => fs.readFile(0, { encoding: 'utf-8' }, (err, res) => err ? no(err) : ok(res))));
133
+ if (!dump.responses && !dump.kvs)
134
+ {
135
+ console.error('dump should be /kv/txn or /kv/range response in json format');
136
+ process.exit(1);
137
+ }
138
+ const success = [];
139
+ for (const r of (dump.responses
140
+ ? dump.responses.map(r => r.response_range).filter(r => r)
141
+ : [ dump ]))
142
+ {
143
+ for (const kv of r.kvs)
144
+ {
145
+ if (kv.value == null)
146
+ {
147
+ console.error('dump should contain values');
148
+ process.exit(1);
149
+ }
150
+ success.push({ request_put: { key: kv.key, value: kv.value, lease: this.options.with_lease ? kv.lease||undefined : undefined } });
151
+ }
152
+ }
153
+ const res = await this.request('/v3/kv/txn', { success });
154
+ if (this.options.json)
155
+ {
156
+ process.stdout.write(JSON.stringify(res));
157
+ return;
158
+ }
159
+ if (res.succeeded)
160
+ {
161
+ process.stdout.write('OK, loaded '+success.length+' values\n');
162
+ }
163
+ }
164
+
111
165
  async get(keys)
112
166
  {
113
167
  if (this.options.prefix)
@@ -116,6 +170,22 @@ class AntiEtcdCli
116
170
  }
117
171
  const txn = { success: keys.map(key => ({ request_range: this.options.prefix ? { key: b64(key+'/'), range_end: b64(key+'0') } : { key: b64(key) } })) };
118
172
  const res = await this.request('/v3/kv/txn', txn);
173
+ if (this.options.notemp)
174
+ {
175
+ // Skip temporary values (values with lease)
176
+ for (const r of res.responses||[])
177
+ {
178
+ if (r.response_range)
179
+ {
180
+ r.response_range.kvs = r.response_range.kvs.filter(kv => !kv.lease);
181
+ }
182
+ }
183
+ }
184
+ if (this.options.json)
185
+ {
186
+ process.stdout.write(JSON.stringify(keys.length == 1 ? res.responses[0].response_range : res));
187
+ return;
188
+ }
119
189
  for (const r of res.responses||[])
120
190
  {
121
191
  if (r.response_range)
@@ -139,7 +209,7 @@ class AntiEtcdCli
139
209
  {
140
210
  if (value === undefined)
141
211
  {
142
- value = await fsp.readFile(0, { encoding: 'utf-8' });
212
+ value = await new Promise((ok, no) => fs.readFile(0, { encoding: 'utf-8' }, (err, res) => err ? no(err) : ok(res)));
143
213
  }
144
214
  const res = await this.request('/v3/kv/put', { key: b64(key), value: b64(value) });
145
215
  if (res.header)
@@ -175,18 +245,18 @@ class AntiEtcdCli
175
245
  {
176
246
  if (res.json.error)
177
247
  {
178
- process.stderr.write(cur_url+': '+res.json.error);
248
+ process.stderr.write(cur_url+': '+res.json.error+'\n');
179
249
  process.exit(1);
180
250
  }
181
251
  return res.json;
182
252
  }
183
253
  if (res.body)
184
254
  {
185
- process.stderr.write(cur_url+': '+res.body);
255
+ process.stderr.write(cur_url+': '+res.body+'\n');
186
256
  }
187
257
  if (res.error)
188
258
  {
189
- process.stderr.write(cur_url+': '+res.error);
259
+ process.stderr.write(cur_url+': '+res.error+'\n');
190
260
  if (!res.response || !res.response.statusCode)
191
261
  {
192
262
  // This URL is unavailable
package/etctree.js CHANGED
@@ -120,7 +120,7 @@ class EtcTree
120
120
  {
121
121
  const key = this.de64(req.key);
122
122
  const end = this.de64(req.range_end);
123
- if (end != null && (key[key.length-1] != '/' || end[end.length-1] != '0' ||
123
+ if (end != null && (key !== '' && end !== '') && (key[key.length-1] != '/' || end[end.length-1] != '0' ||
124
124
  end.substr(0, end.length-1) !== key.substr(0, key.length-1)))
125
125
  {
126
126
  throw new RequestError(501, 'Non-directory range queries are unsupported');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antietcd",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "Simplistic etcd replacement based on TinyRaft",
5
5
  "main": "antietcd.js",
6
6
  "scripts": {