зеркало из https://github.com/microsoft/etcd3.git
Initial working version of password auth
This commit is contained in:
Родитель
35313479d9
Коммит
d57259c9be
|
@ -7,5 +7,9 @@ before_install:
|
||||||
- curl -L https://github.com/coreos/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
|
- curl -L https://github.com/coreos/etcd/releases/download/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
|
||||||
- mkdir -p /tmp/etcd
|
- mkdir -p /tmp/etcd
|
||||||
- tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd --strip-components=1
|
- tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd --strip-components=1
|
||||||
- /tmp/etcd/etcd > /dev/null &
|
- /tmp/etcd/etcd
|
||||||
|
--advertise-client-urls https://127.0.0.1:2379
|
||||||
|
--listen-client-urls https://127.0.0.1:2379
|
||||||
|
--cert-file ${TRAVIS_BUILD_DIR}/test/certs/certs/etcd0.localhost.crt
|
||||||
|
--key-file ${TRAVIS_BUILD_DIR}/test/certs/private/etcd0.localhost.key > /dev/null &
|
||||||
- npm i -g npm
|
- npm i -g npm
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) Microsoft
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Some code, where noted, is imported from the coreos/etcd project. This code
|
||||||
|
is made available under the Apache 2.0 license, which can be read here:
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0.txt
|
|
@ -32,6 +32,7 @@ const pbTypeAliases = {
|
||||||
bool: 'boolean',
|
bool: 'boolean',
|
||||||
string: 'string',
|
string: 'string',
|
||||||
bytes: 'Buffer',
|
bytes: 'Buffer',
|
||||||
|
Type: 'Permission',
|
||||||
};
|
};
|
||||||
|
|
||||||
const numericTypes = [
|
const numericTypes = [
|
||||||
|
@ -82,6 +83,7 @@ function template(name, params) {
|
||||||
getCommentPrefixing,
|
getCommentPrefixing,
|
||||||
getLineContaining,
|
getLineContaining,
|
||||||
formatType,
|
formatType,
|
||||||
|
aliases: pbTypeAliases,
|
||||||
});
|
});
|
||||||
|
|
||||||
emit(templates[name](params).replace(/^\-\- *\n/gm, '').replace(/^\-\-/gm, ''));
|
emit(templates[name](params).replace(/^\-\- *\n/gm, '').replace(/^\-\-/gm, ''));
|
||||||
|
@ -95,7 +97,7 @@ function stripPackageNameFrom(name) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatType(type, isInResponse = false) {
|
function formatTypeInner(type, isInResponse) {
|
||||||
if (type in pbTypeAliases) {
|
if (type in pbTypeAliases) {
|
||||||
return pbTypeAliases[type];
|
return pbTypeAliases[type];
|
||||||
}
|
}
|
||||||
|
@ -113,13 +115,25 @@ function formatType(type, isInResponse = false) {
|
||||||
return `I${type}`;
|
return `I${type}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatType(type, isInResponse = false) {
|
||||||
|
const isEnum = enums.includes(type);
|
||||||
|
const formatted = formatTypeInner(type, isInResponse);
|
||||||
|
|
||||||
|
// grpc unmarshals enums as their string representations.
|
||||||
|
if (isEnum) {
|
||||||
|
return isInResponse ? `keyof typeof ${formatted}` : `${formatted} | keyof typeof ${formatted}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
function getLineContaining(substring, from = 0) {
|
function getLineContaining(substring, from = 0) {
|
||||||
return lines.findIndex((l, i) => i >= from && l.includes(substring));
|
return lines.findIndex((l, i) => i >= from && l.includes(substring));
|
||||||
}
|
}
|
||||||
|
|
||||||
function indent(level) {
|
function indent(level) {
|
||||||
let out = '';
|
let out = '';
|
||||||
for (let i = 0; i < level; i++) {
|
for (let i = 0; i < level; i += 1) {
|
||||||
out += indentation;
|
out += indentation;
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export enum <%= name %> {
|
export enum <%= name in aliases ? aliases[name] : name %> {
|
||||||
--<% _.forOwn(node.values, (count, field) => { %>
|
--<% _.forOwn(node.values, (count, field) => { %>
|
||||||
--<%= getCommentPrefixing(`${field} = ${count}`, getLineContaining(`enum ${name}`)) %>
|
--<%= getCommentPrefixing(`${field} = ${count}`, getLineContaining(`enum ${name}`)) %>
|
||||||
<%= field %> = <%= count %>,
|
<%= _.camelCase(field) %> = <%= count %>,
|
||||||
--<% }) %>
|
--<% }) %>
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,11 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const changeCase = require('change-case');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
/**
|
|
||||||
* Matches lines that should be stripped out from the combined proto file.
|
|
||||||
* @type {RegExp[]}
|
|
||||||
*/
|
|
||||||
const ignores = [
|
|
||||||
/^import .+/,
|
|
||||||
/^option .+/,
|
|
||||||
/^package .+/,
|
|
||||||
/^syntax .+/,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Files to fetch and concatenate.
|
* Files to fetch and concatenate.
|
||||||
* @type {String[]}
|
* @type {String[]}
|
||||||
|
@ -43,6 +33,34 @@ const files = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches lines that should be stripped out from the combined proto file.
|
||||||
|
* @type {RegExp[]}
|
||||||
|
*/
|
||||||
|
const ignores = [
|
||||||
|
/^import .+/,
|
||||||
|
/^option .+/,
|
||||||
|
/^package .+/,
|
||||||
|
/^syntax .+/,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out lines that should be ignored when transforming the proto files.
|
||||||
|
*/
|
||||||
|
const filterRemovedLines = line => !ignores.some(re => re.test(line));
|
||||||
|
|
||||||
|
const uppercaseEnumFieldRe = /^(\s*)([A-Z_]+)(\s*=\s*[0-9]+;.*)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Etcd provides all enums as UPPER_CASE. We change them to cameCase here
|
||||||
|
* to match TypeScript conventions better.
|
||||||
|
*/
|
||||||
|
function lowerCaseEnumFields(line) {
|
||||||
|
return line.replace(uppercaseEnumFieldRe, (_match, indentation, name, value) => {
|
||||||
|
return `${indentation}${changeCase.camelCase(name)}${value}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrl = 'https://raw.githubusercontent.com/coreos/etcd/master';
|
const baseUrl = 'https://raw.githubusercontent.com/coreos/etcd/master';
|
||||||
|
|
||||||
Promise.all(files.map(f => {
|
Promise.all(files.map(f => {
|
||||||
|
@ -51,7 +69,8 @@ Promise.all(files.map(f => {
|
||||||
.then(contents => {
|
.then(contents => {
|
||||||
return 'syntax = "proto3";\n' + f.prefix + contents
|
return 'syntax = "proto3";\n' + f.prefix + contents
|
||||||
.split(/\r?\n/g)
|
.split(/\r?\n/g)
|
||||||
.filter(line => !ignores.some(re => re.test(line)))
|
.filter(filterRemovedLines)
|
||||||
|
.map(lowerCaseEnumFields)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
.replace(/\n\n+/g, '\n');
|
.replace(/\n\n+/g, '\n');
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"test": "npm-run-all --parallel test:lint test:unit",
|
"test": "npm-run-all --parallel test:lint test:unit",
|
||||||
"test:unit": "mocha --compilers ts:ts-node/register --timeout 20000 -r test/_setup.ts test/*.test.ts",
|
"test:unit": "mocha --compilers ts:ts-node/register --timeout 20000 -r test/_setup.ts test/*.test.ts",
|
||||||
"test:lint": "tslint --type-check --project tsconfig.json '{src,test}/**/*.ts'",
|
"test:lint": "tslint --type-check --project tsconfig.json '{src,test}/**/*.ts'",
|
||||||
"update-proto": "node ./bin/update-proto ./proto && node bin/generate-methods.js ./proto/rpc.proto > src/rpc.ts",
|
"build:proto": "node ./bin/update-proto ./proto && node bin/generate-methods.js ./proto/rpc.proto > src/rpc.ts",
|
||||||
"build:doc": "rm -rf docs && typedoc --exclude \"**/test/*\" --excludePrivate --out ./docs ./src/index.ts && node bin/tame-typedoc",
|
"build:doc": "rm -rf docs && typedoc --exclude \"**/test/*\" --excludePrivate --out ./docs ./src/index.ts && node bin/tame-typedoc",
|
||||||
"build:ts": "tsc && cp -R proto lib",
|
"build:ts": "tsc && cp -R proto lib",
|
||||||
"prepublish": "npm run -s build:ts"
|
"prepublish": "npm run -s build:ts"
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
"@types/sinon": "^2.1.2",
|
"@types/sinon": "^2.1.2",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"chai-subset": "^1.5.0",
|
"chai-subset": "^1.5.0",
|
||||||
|
"change-case": "^3.0.1",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mocha": "^3.2.0",
|
"mocha": "^3.2.0",
|
||||||
"node-fetch": "^1.6.3",
|
"node-fetch": "^1.6.3",
|
||||||
|
|
|
@ -10,9 +10,9 @@ message User {
|
||||||
// Permission is a single entity
|
// Permission is a single entity
|
||||||
message Permission {
|
message Permission {
|
||||||
enum Type {
|
enum Type {
|
||||||
READ = 0;
|
read = 0;
|
||||||
WRITE = 1;
|
write = 1;
|
||||||
READWRITE = 2;
|
readwrite = 2;
|
||||||
}
|
}
|
||||||
Type permType = 1;
|
Type permType = 1;
|
||||||
bytes key = 2;
|
bytes key = 2;
|
||||||
|
|
|
@ -21,8 +21,8 @@ message KeyValue {
|
||||||
}
|
}
|
||||||
message Event {
|
message Event {
|
||||||
enum EventType {
|
enum EventType {
|
||||||
PUT = 0;
|
put = 0;
|
||||||
DELETE = 1;
|
delete = 1;
|
||||||
}
|
}
|
||||||
// type is the kind of event. If type is a PUT, it indicates
|
// type is the kind of event. If type is a PUT, it indicates
|
||||||
// new data has been stored to the key. If type is a DELETE,
|
// new data has been stored to the key. If type is a DELETE,
|
||||||
|
|
|
@ -292,16 +292,16 @@ message ResponseHeader {
|
||||||
}
|
}
|
||||||
message RangeRequest {
|
message RangeRequest {
|
||||||
enum SortOrder {
|
enum SortOrder {
|
||||||
NONE = 0; // default, no sorting
|
none = 0; // default, no sorting
|
||||||
ASCEND = 1; // lowest target value first
|
ascend = 1; // lowest target value first
|
||||||
DESCEND = 2; // highest target value first
|
descend = 2; // highest target value first
|
||||||
}
|
}
|
||||||
enum SortTarget {
|
enum SortTarget {
|
||||||
KEY = 0;
|
key = 0;
|
||||||
VERSION = 1;
|
version = 1;
|
||||||
CREATE = 2;
|
create = 2;
|
||||||
MOD = 3;
|
mod = 3;
|
||||||
VALUE = 4;
|
value = 4;
|
||||||
}
|
}
|
||||||
// key is the first key for the range. If range_end is not given, the request only looks up key.
|
// key is the first key for the range. If range_end is not given, the request only looks up key.
|
||||||
bytes key = 1;
|
bytes key = 1;
|
||||||
|
@ -418,16 +418,16 @@ message ResponseOp {
|
||||||
}
|
}
|
||||||
message Compare {
|
message Compare {
|
||||||
enum CompareResult {
|
enum CompareResult {
|
||||||
EQUAL = 0;
|
equal = 0;
|
||||||
GREATER = 1;
|
greater = 1;
|
||||||
LESS = 2;
|
less = 2;
|
||||||
NOT_EQUAL = 3;
|
notEqual = 3;
|
||||||
}
|
}
|
||||||
enum CompareTarget {
|
enum CompareTarget {
|
||||||
VERSION = 0;
|
version = 0;
|
||||||
CREATE = 1;
|
create = 1;
|
||||||
MOD = 2;
|
mod = 2;
|
||||||
VALUE= 3;
|
value= 3;
|
||||||
}
|
}
|
||||||
// result is logical comparison operation for this comparison.
|
// result is logical comparison operation for this comparison.
|
||||||
CompareResult result = 1;
|
CompareResult result = 1;
|
||||||
|
@ -537,9 +537,9 @@ message WatchCreateRequest {
|
||||||
bool progress_notify = 4;
|
bool progress_notify = 4;
|
||||||
enum FilterType {
|
enum FilterType {
|
||||||
// filter out put event.
|
// filter out put event.
|
||||||
NOPUT = 0;
|
noput = 0;
|
||||||
// filter out delete event.
|
// filter out delete event.
|
||||||
NODELETE = 1;
|
nodelete = 1;
|
||||||
}
|
}
|
||||||
// filters filter the events at server side before it sends back to the watcher.
|
// filters filter the events at server side before it sends back to the watcher.
|
||||||
repeated FilterType filters = 5;
|
repeated FilterType filters = 5;
|
||||||
|
@ -641,6 +641,8 @@ message MemberAddResponse {
|
||||||
ResponseHeader header = 1;
|
ResponseHeader header = 1;
|
||||||
// member is the member information for the added member.
|
// member is the member information for the added member.
|
||||||
Member member = 2;
|
Member member = 2;
|
||||||
|
// members is a list of all members after adding the new member.
|
||||||
|
repeated Member members = 3;
|
||||||
}
|
}
|
||||||
message MemberRemoveRequest {
|
message MemberRemoveRequest {
|
||||||
// ID is the member ID of the member to remove.
|
// ID is the member ID of the member to remove.
|
||||||
|
@ -648,6 +650,8 @@ message MemberRemoveRequest {
|
||||||
}
|
}
|
||||||
message MemberRemoveResponse {
|
message MemberRemoveResponse {
|
||||||
ResponseHeader header = 1;
|
ResponseHeader header = 1;
|
||||||
|
// members is a list of all members after removing the member.
|
||||||
|
repeated Member members = 2;
|
||||||
}
|
}
|
||||||
message MemberUpdateRequest {
|
message MemberUpdateRequest {
|
||||||
// ID is the member ID of the member to update.
|
// ID is the member ID of the member to update.
|
||||||
|
@ -657,6 +661,8 @@ message MemberUpdateRequest {
|
||||||
}
|
}
|
||||||
message MemberUpdateResponse{
|
message MemberUpdateResponse{
|
||||||
ResponseHeader header = 1;
|
ResponseHeader header = 1;
|
||||||
|
// members is a list of all members after updating the member.
|
||||||
|
repeated Member members = 2;
|
||||||
}
|
}
|
||||||
message MemberListRequest {
|
message MemberListRequest {
|
||||||
}
|
}
|
||||||
|
@ -671,14 +677,14 @@ message DefragmentResponse {
|
||||||
ResponseHeader header = 1;
|
ResponseHeader header = 1;
|
||||||
}
|
}
|
||||||
enum AlarmType {
|
enum AlarmType {
|
||||||
NONE = 0; // default, used to query if any alarm is active
|
none = 0; // default, used to query if any alarm is active
|
||||||
NOSPACE = 1; // space quota is exhausted
|
nospace = 1; // space quota is exhausted
|
||||||
}
|
}
|
||||||
message AlarmRequest {
|
message AlarmRequest {
|
||||||
enum AlarmAction {
|
enum AlarmAction {
|
||||||
GET = 0;
|
get = 0;
|
||||||
ACTIVATE = 1;
|
activate = 1;
|
||||||
DEACTIVATE = 2;
|
deactivate = 2;
|
||||||
}
|
}
|
||||||
// action is the kind of alarm request to issue. The action
|
// action is the kind of alarm request to issue. The action
|
||||||
// may GET alarm statuses, ACTIVATE an alarm, or DEACTIVATE a
|
// may GET alarm statuses, ACTIVATE an alarm, or DEACTIVATE a
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
import { Range } from './range';
|
||||||
|
import { AuthClient, Permission } from './rpc';
|
||||||
|
import { toBuffer } from './util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IPermission can be used to grant a certain role in etcd access to a certain
|
||||||
|
* key range, or prefix.
|
||||||
|
*/
|
||||||
|
export type IPermissionRequest =
|
||||||
|
{ permission: keyof typeof Permission, range: Range } |
|
||||||
|
{ permission: keyof typeof Permission, key: Buffer | string };
|
||||||
|
|
||||||
|
function getRange(req: IPermissionRequest): Range {
|
||||||
|
if (req.hasOwnProperty('key')) {
|
||||||
|
return new Range(toBuffer((<{ key: Buffer | string }> req).key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<{ range: Range }> req).range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IGrant is used for granting a permission to a user.
|
||||||
|
*/
|
||||||
|
export interface IPermissionResult {
|
||||||
|
permission: keyof typeof Permission;
|
||||||
|
range: Range;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Role provides an entry point for managing etcd roles. Etcd has an
|
||||||
|
* ACL-esque system: users have one or more roles, and roles have one or
|
||||||
|
* more permissions that grant them access (read, write, or both) on key
|
||||||
|
* ranges.
|
||||||
|
*/
|
||||||
|
export class Role {
|
||||||
|
constructor(
|
||||||
|
private client: AuthClient,
|
||||||
|
public readonly name: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the role in etcd.
|
||||||
|
*/
|
||||||
|
public create(): Promise<this> {
|
||||||
|
return this.client.roleAdd({ name: this.name }).then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the role from etcd.
|
||||||
|
*/
|
||||||
|
public delete(): Promise<this> {
|
||||||
|
return this.client.roleDelete({ role: this.name }).then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a permission from the role in etcd.
|
||||||
|
*/
|
||||||
|
public revoke(req: IPermissionRequest | IPermissionRequest[]): Promise<this> {
|
||||||
|
if (req instanceof Array) {
|
||||||
|
return Promise.all(req.map(r => this.grant(r))).then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = getRange(req);
|
||||||
|
return this.client.roleRevokePermission({
|
||||||
|
role: this.name,
|
||||||
|
key: range.start.toString(),
|
||||||
|
range_end: range.end.toString(),
|
||||||
|
})
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grants one or more permissions to this role.
|
||||||
|
*/
|
||||||
|
public grant(req: IPermissionRequest | IPermissionRequest[]): Promise<this> {
|
||||||
|
if (req instanceof Array) {
|
||||||
|
return Promise.all(req.map(r => this.grant(r))).then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = getRange(req);
|
||||||
|
return this.client.roleGrantPermission({
|
||||||
|
name: this.name,
|
||||||
|
perm: {
|
||||||
|
permType: 'read',
|
||||||
|
key: range.start,
|
||||||
|
range_end: range.end,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of permissions the role has.
|
||||||
|
*/
|
||||||
|
public permissions(): Promise<IPermissionResult[]> {
|
||||||
|
return this.client.roleGet({ role: this.name })
|
||||||
|
.then(response => {
|
||||||
|
return response.perm.map(perm => ({
|
||||||
|
permission: perm.permType,
|
||||||
|
range: new Range(perm.key, perm.range_end),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grants a user access to the role.
|
||||||
|
*/
|
||||||
|
public addUser(user: string | User): Promise<this> {
|
||||||
|
if (user instanceof User) {
|
||||||
|
user = user.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.userGrantRole({ user, role: this.name })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a user's access to the role.
|
||||||
|
*/
|
||||||
|
public removeUser(user: string | User): Promise<this> {
|
||||||
|
if (user instanceof User) {
|
||||||
|
user = user.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.userRevokeRole({ name: user, role: this.name })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The User provides an entry point for managing etcd users. The user can
|
||||||
|
* be added to Roles to manage permissions.
|
||||||
|
*/
|
||||||
|
export class User {
|
||||||
|
constructor(
|
||||||
|
private client: AuthClient,
|
||||||
|
public readonly name: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the user, with the provided password.
|
||||||
|
*/
|
||||||
|
public create(password: string): Promise<this> {
|
||||||
|
return this.client.userAdd({ name: this.name, password })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the user's password.
|
||||||
|
*/
|
||||||
|
public setPassword(password: string): Promise<this> {
|
||||||
|
return this.client.userChangePassword({ name: this.name, password })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the user from etcd.
|
||||||
|
*/
|
||||||
|
public delete(): Promise<this> {
|
||||||
|
return this.client.userDelete({ name: this.name }).then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of roles this user has.
|
||||||
|
*/
|
||||||
|
public roles(): Promise<Role[]> {
|
||||||
|
return this.client.userGet({ name: this.name }).then(res => {
|
||||||
|
return res.roles.map(role => new Role(this.client, role));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the user to a role.
|
||||||
|
*/
|
||||||
|
public addRole(role: string | Role): Promise<this> {
|
||||||
|
if (role instanceof Role) {
|
||||||
|
role = role.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.userGrantRole({ user: this.name, role })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the user's access to a role.
|
||||||
|
*/
|
||||||
|
public removeRole(role: string | Role): Promise<this> {
|
||||||
|
if (role instanceof Role) {
|
||||||
|
role = role.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.client.userRevokeRole({ name: this.name, role })
|
||||||
|
.then(() => this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
export interface IBackoffStrategy {
|
export interface IBackoffStrategy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* getDelay returns the amount of delay of the current backoff.
|
* getDelay returns the amount of delay of the current backoff.
|
||||||
*/
|
*/
|
||||||
|
@ -15,5 +14,4 @@ export interface IBackoffStrategy {
|
||||||
* Returns a strategy with a reset backoff counter.
|
* Returns a strategy with a reset backoff counter.
|
||||||
*/
|
*/
|
||||||
reset(): IBackoffStrategy;
|
reset(): IBackoffStrategy;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { IBackoffStrategy } from './backoff';
|
||||||
* given in milliseconds.
|
* given in milliseconds.
|
||||||
*/
|
*/
|
||||||
export interface IExponentialOptions {
|
export interface IExponentialOptions {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The initial delay passed to the equation.
|
* The initial delay passed to the equation.
|
||||||
*/
|
*/
|
||||||
|
@ -22,17 +21,15 @@ export interface IExponentialOptions {
|
||||||
* max is the maximum value of the delay.
|
* max is the maximum value of the delay.
|
||||||
*/
|
*/
|
||||||
max: number;
|
max: number;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see https://en.wikipedia.org/wiki/Exponential_backoff
|
* @see https://en.wikipedia.org/wiki/Exponential_backoff
|
||||||
*/
|
*/
|
||||||
export class ExponentialBackoff implements IBackoffStrategy {
|
export class ExponentialBackoff implements IBackoffStrategy {
|
||||||
|
|
||||||
private counter: number;
|
private counter: number;
|
||||||
|
|
||||||
constructor (protected options: IExponentialOptions) {
|
constructor(protected options: IExponentialOptions) {
|
||||||
this.counter = 0;
|
this.counter = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,5 +56,4 @@ export class ExponentialBackoff implements IBackoffStrategy {
|
||||||
public reset(): IBackoffStrategy {
|
public reset(): IBackoffStrategy {
|
||||||
return new ExponentialBackoff(this.options);
|
return new ExponentialBackoff(this.options);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
156
src/builder.ts
156
src/builder.ts
|
@ -1,59 +1,19 @@
|
||||||
|
import { rangable, Range } from './range';
|
||||||
import * as RPC from './rpc';
|
import * as RPC from './rpc';
|
||||||
import { PromiseWrap } from './util';
|
import { PromiseWrap, toBuffer } from './util';
|
||||||
|
|
||||||
const zeroKey = Buffer.from([0]);
|
|
||||||
const emptyBuffer = Buffer.from([]);
|
const emptyBuffer = Buffer.from([]);
|
||||||
|
|
||||||
/**
|
|
||||||
* prefixStart returns a buffer to start the key as a prefix.
|
|
||||||
*/
|
|
||||||
export function prefixStart(key: Buffer | string) {
|
|
||||||
if (key.length === 0) {
|
|
||||||
return zeroKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return toBuffer(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* prefixEnd returns the end of a range request, where `key` is the "start"
|
|
||||||
* value, to get all values that share the prefix.
|
|
||||||
*/
|
|
||||||
export function prefixEnd(key: Buffer): Buffer {
|
|
||||||
if (key.equals(zeroKey)) {
|
|
||||||
return zeroKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
let buffer = Buffer.from(key); // copy to prevent mutation
|
|
||||||
for (let i = buffer.length - 1; i >= 0; i -= 0) {
|
|
||||||
if (buffer[i] < 0xff) {
|
|
||||||
buffer[i] = buffer[i] + 1;
|
|
||||||
buffer = buffer.slice(0, i + 1);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return zeroKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sortMap = {
|
|
||||||
key: RPC.SortTarget.KEY,
|
|
||||||
version: RPC.SortTarget.VERSION,
|
|
||||||
createdAt: RPC.SortTarget.CREATE,
|
|
||||||
modifiedAt: RPC.SortTarget.MOD,
|
|
||||||
value: RPC.SortTarget.VALUE,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comparators can be passed to various operations in the ComparatorBuilder.
|
* Comparators can be passed to various operations in the ComparatorBuilder.
|
||||||
*/
|
*/
|
||||||
export const comparator = {
|
export const comparator = {
|
||||||
'==': RPC.CompareResult.EQUAL,
|
'==': RPC.CompareResult.equal,
|
||||||
'===': RPC.CompareResult.EQUAL,
|
'===': RPC.CompareResult.equal,
|
||||||
'>': RPC.CompareResult.GREATER,
|
'>': RPC.CompareResult.greater,
|
||||||
'<': RPC.CompareResult.LESS,
|
'<': RPC.CompareResult.less,
|
||||||
'!=': RPC.CompareResult.NOT_EQUAL,
|
'!=': RPC.CompareResult.notEqual,
|
||||||
'!==': RPC.CompareResult.NOT_EQUAL,
|
'!==': RPC.CompareResult.notEqual,
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ICompareTarget {
|
export interface ICompareTarget {
|
||||||
|
@ -68,23 +28,11 @@ export interface IOperation {
|
||||||
/**
|
/**
|
||||||
* compareTarget are the types of things that can be compared against.
|
* compareTarget are the types of things that can be compared against.
|
||||||
*/
|
*/
|
||||||
export const compareTarget = {
|
export const compareTarget: { [key in keyof typeof RPC.CompareTarget]: keyof RPC.ICompare } = {
|
||||||
value: {
|
value: 'value',
|
||||||
value: RPC.CompareTarget.VALUE,
|
version: 'version',
|
||||||
key: 'value',
|
create: 'create_revision',
|
||||||
},
|
mod: 'mod_revision',
|
||||||
version: {
|
|
||||||
value: RPC.CompareTarget.VERSION,
|
|
||||||
key: 'value',
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
value: RPC.CompareTarget.CREATE,
|
|
||||||
key: 'create_revision',
|
|
||||||
},
|
|
||||||
modifiedAt: {
|
|
||||||
value: RPC.CompareTarget.MOD,
|
|
||||||
key: 'mod_revision',
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,17 +46,6 @@ function assertWithin<T>(map: T, value: keyof T, thing: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the input to a buffer, if it is not already.
|
|
||||||
*/
|
|
||||||
function toBuffer(input: string | Buffer): Buffer {
|
|
||||||
if (input instanceof Buffer) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Buffer.from(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RangeBuilder is a primitive builder for range queries on the kv store.
|
* RangeBuilder is a primitive builder for range queries on the kv store.
|
||||||
* It's extended by the Single and MultiRangeBuilders, which contain
|
* It's extended by the Single and MultiRangeBuilders, which contain
|
||||||
|
@ -226,7 +163,6 @@ export class SingleRangeBuilder extends RangeBuilder<string> {
|
||||||
* MultiRangeBuilder is a query builder that looks up multiple keys.
|
* MultiRangeBuilder is a query builder that looks up multiple keys.
|
||||||
*/
|
*/
|
||||||
export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
||||||
|
|
||||||
private queryPrefix: Buffer;
|
private queryPrefix: Buffer;
|
||||||
|
|
||||||
constructor(private kv: RPC.KVClient) {
|
constructor(private kv: RPC.KVClient) {
|
||||||
|
@ -241,8 +177,16 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
||||||
*/
|
*/
|
||||||
public prefix(value: string | Buffer): this {
|
public prefix(value: string | Buffer): this {
|
||||||
this.queryPrefix = toBuffer(value);
|
this.queryPrefix = toBuffer(value);
|
||||||
this.request.key = prefixStart(value);
|
return this.inRange(Range.prefix(value));
|
||||||
this.request.range_end = prefixEnd(this.request.key);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inRange instructs the builder to get keys in the specified byte range.
|
||||||
|
*/
|
||||||
|
public inRange(r: rangable): this {
|
||||||
|
const range = Range.from(r);
|
||||||
|
this.request.key = range.start;
|
||||||
|
this.request.range_end = range.end;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,15 +197,6 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
||||||
return this.prefix('');
|
return this.prefix('');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* inRange instructs the builder to get keys in the specified byte range.
|
|
||||||
*/
|
|
||||||
public inRange(start: string | Buffer, end: string | Buffer): this {
|
|
||||||
this.request.key = toBuffer(start);
|
|
||||||
this.request.range_end = toBuffer(end);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Limit sets the maximum number of results to retrieve.
|
* Limit sets the maximum number of results to retrieve.
|
||||||
*/
|
*/
|
||||||
|
@ -273,10 +208,11 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
||||||
/**
|
/**
|
||||||
* Sort specifies how the result should be sorted.
|
* Sort specifies how the result should be sorted.
|
||||||
*/
|
*/
|
||||||
public sort(target: keyof typeof sortMap, order: 'asc' | 'desc'): this {
|
public sort(target: keyof typeof RPC.SortTarget, order: keyof typeof RPC.SortOrder): this {
|
||||||
assertWithin(sortMap, target, 'sort order in client.get().sort(...)');
|
assertWithin(RPC.SortTarget, target, 'sort order in client.get().sort(...)');
|
||||||
this.request.sort_target = sortMap[target];
|
assertWithin(RPC.SortOrder, order, 'sort order in client.get().sort(...)');
|
||||||
this.request.sort_order = order.toLowerCase() === 'asc' ? RPC.SortOrder.ASCEND : RPC.SortOrder.DESCEND;
|
this.request.sort_target = RPC.SortTarget[target];
|
||||||
|
this.request.sort_order = RPC.SortOrder[order];
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,7 +288,7 @@ export class MultiRangeBuilder extends RangeBuilder<{ [key: string]: string }> {
|
||||||
private mapValues<T>(iterator: (buf: Buffer) => T): Promise<{ [key: string]: T }> {
|
private mapValues<T>(iterator: (buf: Buffer) => T): Promise<{ [key: string]: T }> {
|
||||||
return this.exec().then(res => {
|
return this.exec().then(res => {
|
||||||
const output: { [key: string]: T } = {};
|
const output: { [key: string]: T } = {};
|
||||||
for (let i = 0; i < res.kvs.length; i += 1) {
|
for (let i = 0; i < res.kvs.length; i++) {
|
||||||
output[res.kvs[i].key.slice(this.queryPrefix.length).toString()] =
|
output[res.kvs[i].key.slice(this.queryPrefix.length).toString()] =
|
||||||
iterator(res.kvs[i].value);
|
iterator(res.kvs[i].value);
|
||||||
}
|
}
|
||||||
|
@ -382,11 +318,18 @@ export class DeleteBuilder extends PromiseWrap<RPC.IDeleteRangeResponse> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix instructs the query to delete all keys that have the provided prefix.
|
* key sets a single key to be deleted.
|
||||||
*/
|
*/
|
||||||
public prefix(value: string | Buffer): this {
|
public prefix(value: string | Buffer): this {
|
||||||
this.request.key = prefixStart(value);
|
return this.range(Range.prefix(value));
|
||||||
this.request.range_end = prefixEnd(this.request.key);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the byte range of values to delete.
|
||||||
|
*/
|
||||||
|
public range(range: Range): this {
|
||||||
|
this.request.key = range.start;
|
||||||
|
this.request.range_end = range.end;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -400,9 +343,10 @@ export class DeleteBuilder extends PromiseWrap<RPC.IDeleteRangeResponse> {
|
||||||
/**
|
/**
|
||||||
* inRange instructs the builder to delete keys in the specified byte range.
|
* inRange instructs the builder to delete keys in the specified byte range.
|
||||||
*/
|
*/
|
||||||
public inRange(start: string | Buffer, end: string | Buffer): this {
|
public inRange(r: rangable): this {
|
||||||
this.request.key = toBuffer(start);
|
const range = Range.from(r);
|
||||||
this.request.range_end = toBuffer(end);
|
this.request.key = range.start;
|
||||||
|
this.request.range_end = range.end;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -523,7 +467,7 @@ export class PutBuilder extends PromiseWrap<RPC.IPutResponse> {
|
||||||
* const id = uuid.v4();
|
* const id = uuid.v4();
|
||||||
*
|
*
|
||||||
* function lock() {
|
* function lock() {
|
||||||
* return client.if('my_lock', 'createdAt', '==', 0)
|
* return client.if('my_lock', 'create', '==', 0)
|
||||||
* .then(client.put('my_lock').value(id))
|
* .then(client.put('my_lock').value(id))
|
||||||
* .else(client.get('my_lock'))
|
* .else(client.get('my_lock'))
|
||||||
* .commit()
|
* .commit()
|
||||||
|
@ -545,8 +489,12 @@ export class ComparatorBuilder {
|
||||||
/**
|
/**
|
||||||
* Adds a new clause to the transaction.
|
* Adds a new clause to the transaction.
|
||||||
*/
|
*/
|
||||||
public and(key: string | Buffer, column: keyof typeof compareTarget,
|
public and(
|
||||||
cmp: keyof typeof comparator, value: string | Buffer | number): this {
|
key: string | Buffer,
|
||||||
|
column: keyof typeof RPC.CompareTarget,
|
||||||
|
cmp: keyof typeof comparator,
|
||||||
|
value: string | Buffer | number,
|
||||||
|
): this {
|
||||||
assertWithin(compareTarget, column, 'comparison target in client.and(...)');
|
assertWithin(compareTarget, column, 'comparison target in client.and(...)');
|
||||||
assertWithin(comparator, cmp, 'comparator in client.and(...)');
|
assertWithin(comparator, cmp, 'comparator in client.and(...)');
|
||||||
|
|
||||||
|
@ -558,8 +506,8 @@ export class ComparatorBuilder {
|
||||||
this.request.compare.push({
|
this.request.compare.push({
|
||||||
key: toBuffer(key),
|
key: toBuffer(key),
|
||||||
result: comparator[cmp],
|
result: comparator[cmp],
|
||||||
target: compareTarget[column].value,
|
target: RPC.CompareTarget[column],
|
||||||
[compareTarget[column].key]: typeof value === 'number' ? value : toBuffer(value),
|
[compareTarget[column]]: typeof value === 'number' ? value : toBuffer(value),
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { ExponentialBackoff } from './backoff/exponential';
|
import { ExponentialBackoff } from './backoff/exponential';
|
||||||
import { castGrpcError, GRPCGenericError } from './errors';
|
import { castGrpcError, GRPCGenericError } from './errors';
|
||||||
import { IOptions } from './options';
|
import { IOptions } from './options';
|
||||||
import { ICallable, Services } from './rpc';
|
import { ICallable, Services, IAuthenticateResponse } from './rpc';
|
||||||
import { SharedPool } from './shared-pool';
|
import { SharedPool } from './shared-pool';
|
||||||
import { forOwn } from './util';
|
import { forOwn } from './util';
|
||||||
|
|
||||||
const grpc = require('grpc');
|
const grpc = require('grpc'); // tslint:disable-line
|
||||||
const services = grpc.load(`${__dirname}/../proto/rpc.proto`);
|
const services = grpc.load(`${__dirname}/../proto/rpc.proto`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,12 +22,101 @@ export const defaultBackoffStrategy = new ExponentialBackoff({
|
||||||
random: 1,
|
random: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for typing internally.
|
||||||
|
*/
|
||||||
|
interface GRPCCredentials {
|
||||||
|
isGRPCCredential: void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves and returns an auth token for accessing etcd. This function is
|
||||||
|
* based on the algorithm in {@link https://git.io/vHzwh}.
|
||||||
|
*/
|
||||||
|
class Authentictor {
|
||||||
|
private awaitingToken: Promise<GRPCCredentials> | null = null;
|
||||||
|
|
||||||
|
constructor(private options: IOptions) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Augments the call credentials with the configured username and password,
|
||||||
|
* if any.
|
||||||
|
*/
|
||||||
|
public augmentCredentials(original: GRPCCredentials): Promise<GRPCCredentials> {
|
||||||
|
if (this.awaitingToken !== null) {
|
||||||
|
return this.awaitingToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hosts = typeof this.options.hosts === 'string'
|
||||||
|
? [this.options.hosts]
|
||||||
|
: this.options.hosts;
|
||||||
|
const auth = this.options.auth;
|
||||||
|
|
||||||
|
if (!auth) {
|
||||||
|
return Promise.resolve(original);
|
||||||
|
}
|
||||||
|
|
||||||
|
const attempt = (index: number, previousRejection?: Error): Promise<GRPCCredentials> => {
|
||||||
|
if (index > hosts.length) {
|
||||||
|
this.awaitingToken = null;
|
||||||
|
return Promise.reject(previousRejection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getCredentialsFromHost(hosts[index], auth, original)
|
||||||
|
.then(token => {
|
||||||
|
this.awaitingToken = null;
|
||||||
|
return grpc.credentials.combineChannelCredentials(
|
||||||
|
original, this.createMetadataAugmenter(token));
|
||||||
|
})
|
||||||
|
.catch(err => attempt(index + 1, err));
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.awaitingToken = attempt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an auth token from etcd.
|
||||||
|
*/
|
||||||
|
private getCredentialsFromHost(address: string, auth: { username: string, password: string},
|
||||||
|
credentials: GRPCCredentials): Promise<string> {
|
||||||
|
|
||||||
|
const service = new services.etcdserverpb.Auth(address, credentials);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
service.authenticate(
|
||||||
|
{ name: auth.username, password: auth.password },
|
||||||
|
(err: Error | null, res: IAuthenticateResponse) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(res.token);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a metadata generator that adds the auth token to grpc calls.
|
||||||
|
*/
|
||||||
|
private createMetadataAugmenter(token: string): GRPCCredentials {
|
||||||
|
return grpc.credentials.createFromMetadataGenerator(
|
||||||
|
(_ctx: any, callback: (err: Error | null, result?: any) => void) => {
|
||||||
|
const metadata = new grpc.Metadata();
|
||||||
|
metadata.add('token', token);
|
||||||
|
callback(null, metadata);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Host {
|
class Host {
|
||||||
|
|
||||||
private cachedCredentials: Promise<any> | null = null;
|
|
||||||
private cachedServices: { [name in keyof typeof Services]?: Promise<IRawGRPC> } = Object.create(null);
|
private cachedServices: { [name in keyof typeof Services]?: Promise<IRawGRPC> } = Object.create(null);
|
||||||
|
|
||||||
constructor(private host: string, private options: IOptions) {}
|
constructor(
|
||||||
|
private host: string,
|
||||||
|
private channelCredentials: Promise<GRPCCredentials>,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the given GRPC service on the current host.
|
* Returns the given GRPC service on the current host.
|
||||||
|
@ -38,14 +127,10 @@ class Host {
|
||||||
return Promise.resolve(service);
|
return Promise.resolve(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.cachedCredentials === null) {
|
return this.channelCredentials.then(credentials => {
|
||||||
this.cachedCredentials = this.buildAuthentication();
|
const instance = new services.etcdserverpb[name](this.host, credentials);
|
||||||
}
|
instance.etcdHost = this;
|
||||||
|
return instance;
|
||||||
return this.cachedServices[name] = this.cachedCredentials.then(credentials => {
|
|
||||||
const s = new services.etcdserverpb[name](this.host, credentials);
|
|
||||||
s.etcdHost = this;
|
|
||||||
return s;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,36 +139,12 @@ class Host {
|
||||||
* existing client
|
* existing client
|
||||||
*/
|
*/
|
||||||
public close() {
|
public close() {
|
||||||
if (!this.cachedCredentials) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
forOwn(this.cachedServices, (service: Promise<IRawGRPC>) => {
|
forOwn(this.cachedServices, (service: Promise<IRawGRPC>) => {
|
||||||
service.then(c => grpc.closeClient(c));
|
service.then(c => grpc.closeClient(c));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cachedCredentials = null;
|
|
||||||
this.cachedServices = Object.create(null);
|
this.cachedServices = Object.create(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildAuthentication(): Promise<any> {
|
|
||||||
const { credentials, auth } = this.options;
|
|
||||||
|
|
||||||
let protocolCredentials = grpc.credentials.createInsecure();
|
|
||||||
if (credentials) {
|
|
||||||
protocolCredentials = grpc.credentials.createSsl(
|
|
||||||
credentials.rootCertificate,
|
|
||||||
credentials.privateKey,
|
|
||||||
credentials.certChain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth) {
|
|
||||||
throw new Error('password auth not supported yet'); // todo(connor4312)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve(grpc.credentials.combineCallCredentials(protocolCredentials));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,16 +155,10 @@ export class ConnectionPool implements ICallable {
|
||||||
|
|
||||||
private pool = new SharedPool<Host>(this.options.backoffStrategy || defaultBackoffStrategy);
|
private pool = new SharedPool<Host>(this.options.backoffStrategy || defaultBackoffStrategy);
|
||||||
private mockImpl: ICallable | null;
|
private mockImpl: ICallable | null;
|
||||||
|
private authenticator = new Authentictor(this.options);
|
||||||
|
|
||||||
constructor(private options: IOptions) {
|
constructor(private options: IOptions) {
|
||||||
if (typeof options.hosts === 'string') {
|
this.seedHosts();
|
||||||
options.hosts = [options.hosts];
|
|
||||||
}
|
|
||||||
if (options.hosts.length === 0) {
|
|
||||||
throw new Error('Cannot construct an etcd client with no hosts specified');
|
|
||||||
}
|
|
||||||
|
|
||||||
options.hosts.forEach(host => this.pool.add(new Host(host, options)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,4 +223,41 @@ export class ConnectionPool implements ICallable {
|
||||||
|
|
||||||
return this.pool.pull().then(client => client.getService(service));
|
return this.pool.pull().then(client => client.getService(service));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds configured etcd hosts to the connection pool.
|
||||||
|
*/
|
||||||
|
private seedHosts() {
|
||||||
|
const credentials = this.buildAuthentication();
|
||||||
|
const { hosts } = this.options;
|
||||||
|
|
||||||
|
if (typeof hosts === 'string') {
|
||||||
|
this.pool.add(new Host(hosts, credentials));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hosts.length === 0) {
|
||||||
|
throw new Error('Cannot construct an etcd client with no hosts specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts.forEach(host => this.pool.add(new Host(host, credentials)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates authentication credentials to use for etcd clients.
|
||||||
|
*/
|
||||||
|
private buildAuthentication(): Promise<GRPCCredentials> {
|
||||||
|
const { credentials } = this.options;
|
||||||
|
|
||||||
|
let protocolCredentials = grpc.credentials.createInsecure();
|
||||||
|
if (credentials) {
|
||||||
|
protocolCredentials = grpc.credentials.createSsl(
|
||||||
|
credentials.rootCertificate,
|
||||||
|
credentials.privateKey,
|
||||||
|
credentials.certChain,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.authenticator.augmentCredentials(protocolCredentials);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class GRPCCancelledError extends GRPCGenericError {}
|
||||||
export class EtcdError extends Error {}
|
export class EtcdError extends Error {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EtcdLeaseTimeoutError is thrown when trying to renew a lease that's
|
* EtcdLeaseInvalidError is thrown when trying to renew a lease that's
|
||||||
* expired.
|
* expired.
|
||||||
*/
|
*/
|
||||||
export class EtcdLeaseInvalidError extends Error {
|
export class EtcdLeaseInvalidError extends Error {
|
||||||
|
@ -41,6 +41,34 @@ export class EtcdLeaseInvalidError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtcdRoleExistsError is thrown when trying to create a role that already exists.
|
||||||
|
*/
|
||||||
|
export class EtcdRoleExistsError extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtcdUserExistsError is thrown when trying to create a user that already exists.
|
||||||
|
*/
|
||||||
|
export class EtcdUserExistsError extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtcdRoleNotGrantedError is thrown when trying to revoke a role from a user
|
||||||
|
* to which the role is not granted.
|
||||||
|
*/
|
||||||
|
export class EtcdRoleNotGrantedError extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtcdRoleNotFoundError is thrown when trying to operate on a role that does
|
||||||
|
* not exist.
|
||||||
|
*/
|
||||||
|
export class EtcdRoleNotFoundError extends Error {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EtcdUserNotFoundError is thrown when trying to operate on a user that does
|
||||||
|
* not exist.
|
||||||
|
*/
|
||||||
|
export class EtcdUserNotFoundError extends Error {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EtcdLockFailedError is thrown when we fail to aquire a lock.
|
* EtcdLockFailedError is thrown when we fail to aquire a lock.
|
||||||
*/
|
*/
|
||||||
|
@ -78,6 +106,11 @@ const grpcMessageToError = new Map<string | RegExp, IErrorCtor>([
|
||||||
['Cancelled before creating subchannel', GRPCCancelledError],
|
['Cancelled before creating subchannel', GRPCCancelledError],
|
||||||
['Pick cancelled', GRPCCancelledError],
|
['Pick cancelled', GRPCCancelledError],
|
||||||
['Disconnected', GRPCCancelledError],
|
['Disconnected', GRPCCancelledError],
|
||||||
|
[/role name already exists/, EtcdRoleExistsError],
|
||||||
|
[/user name already exists/, EtcdUserExistsError],
|
||||||
|
[/role is not granted to the user/, EtcdRoleNotGrantedError],
|
||||||
|
[/role name not found/, EtcdRoleNotFoundError],
|
||||||
|
[/user name not found/, EtcdUserNotFoundError],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function getMatchingGrpcError(err: Error): IErrorCtor | null {
|
function getMatchingGrpcError(err: Error): IErrorCtor | null {
|
||||||
|
@ -109,9 +142,9 @@ export function castGrpcError(err: Error): Error {
|
||||||
return err; // it looks like it's already some kind of typed error
|
return err; // it looks like it's already some kind of typed error
|
||||||
}
|
}
|
||||||
|
|
||||||
let ctor: IErrorCtor = getMatchingGrpcError(err) || GRPCGenericError;
|
let ctor = getMatchingGrpcError(err);
|
||||||
if (err.message.includes('etcdserver:')) {
|
if (!ctor) {
|
||||||
ctor = EtcdError;
|
ctor = err.message.includes('etcdserver:') ? EtcdError : GRPCGenericError;
|
||||||
}
|
}
|
||||||
|
|
||||||
const castError = new ctor(rewriteErrorName(err.message, ctor));
|
const castError = new ctor(rewriteErrorName(err.message, ctor));
|
||||||
|
|
56
src/index.ts
56
src/index.ts
|
@ -1,13 +1,18 @@
|
||||||
|
import { Role, User } from './auth';
|
||||||
import * as Builder from './builder';
|
import * as Builder from './builder';
|
||||||
import { ConnectionPool } from './connection-pool';
|
import { ConnectionPool } from './connection-pool';
|
||||||
import { Lease } from './lease';
|
import { Lease } from './lease';
|
||||||
import { Lock } from './lock';
|
import { Lock } from './lock';
|
||||||
import { IOptions } from './options';
|
import { IOptions } from './options';
|
||||||
|
import { rangable, Range } from './range';
|
||||||
import * as RPC from './rpc';
|
import * as RPC from './rpc';
|
||||||
|
|
||||||
export * from './errors';
|
export * from './auth';
|
||||||
export * from './builder';
|
export * from './builder';
|
||||||
|
export * from './errors';
|
||||||
export * from './lease';
|
export * from './lease';
|
||||||
|
export * from './options';
|
||||||
|
export * from './range';
|
||||||
export * from './rpc';
|
export * from './rpc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +33,6 @@ export * from './rpc';
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class Etcd3 {
|
export class Etcd3 {
|
||||||
|
|
||||||
private pool = new ConnectionPool(this.options);
|
private pool = new ConnectionPool(this.options);
|
||||||
|
|
||||||
public readonly kv = new RPC.KVClient(this.pool);
|
public readonly kv = new RPC.KVClient(this.pool);
|
||||||
|
@ -90,11 +94,55 @@ export class Etcd3 {
|
||||||
* statements atomically. See documentation on the ComparatorBuilder for
|
* statements atomically. See documentation on the ComparatorBuilder for
|
||||||
* more information.
|
* more information.
|
||||||
*/
|
*/
|
||||||
public if(key: string | Buffer, column: keyof typeof Builder.compareTarget,
|
public if(
|
||||||
cmp: keyof typeof Builder.comparator, value: string | Buffer | number): Builder.ComparatorBuilder {
|
key: string | Buffer,
|
||||||
|
column: keyof typeof Builder.compareTarget,
|
||||||
|
cmp: keyof typeof Builder.comparator,
|
||||||
|
value: string | Buffer | number,
|
||||||
|
): Builder.ComparatorBuilder {
|
||||||
return new Builder.ComparatorBuilder(this.kv).and(key, column, cmp, value);
|
return new Builder.ComparatorBuilder(this.kv).and(key, column, cmp, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a structure representing an etcd range. Used in permission grants
|
||||||
|
* and queries. This is a convenience method for `Etcd3.Range.from(...)`.
|
||||||
|
*/
|
||||||
|
public range(r: rangable): Range {
|
||||||
|
return Range.from(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves to an array of roles available in etcd.
|
||||||
|
*/
|
||||||
|
public getRoles(): Promise<Role[]> {
|
||||||
|
return this.auth.roleList().then(result => {
|
||||||
|
return result.roles.map(role => new Role(this.auth, role));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object to manipulate the role with the provided name.
|
||||||
|
*/
|
||||||
|
public role(name: string): Role {
|
||||||
|
return new Role(this.auth, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves to an array of users available in etcd.
|
||||||
|
*/
|
||||||
|
public getUsers(): Promise<User[]> {
|
||||||
|
return this.auth.userList().then(result => {
|
||||||
|
return result.users.map(user => new User(this.auth, user));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object to manipulate the user with the provided name.
|
||||||
|
*/
|
||||||
|
public user(name: string): User {
|
||||||
|
return new User(this.auth, name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `.mock()` allows you to insert an interface that will be called into
|
* `.mock()` allows you to insert an interface that will be called into
|
||||||
* instead of calling out to the "real" service. `unmock` should be called
|
* instead of calling out to the "real" service. `unmock` should be called
|
||||||
|
|
16
src/lease.ts
16
src/lease.ts
|
@ -76,7 +76,6 @@ const enum State {
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class Lease extends EventEmitter {
|
export class Lease extends EventEmitter {
|
||||||
|
|
||||||
private leaseID: Promise<string | Error>;
|
private leaseID: Promise<string | Error>;
|
||||||
private state = State.Alive;
|
private state = State.Alive;
|
||||||
|
|
||||||
|
@ -263,12 +262,15 @@ export class Lease extends EventEmitter {
|
||||||
this.keepalive();
|
this.keepalive();
|
||||||
});
|
});
|
||||||
|
|
||||||
const keepaliveTimer = setInterval(() => {
|
const keepaliveTimer = setInterval(
|
||||||
this.emit('keepaliveFired');
|
() => {
|
||||||
this.grant()
|
this.emit('keepaliveFired');
|
||||||
.then(id => stream.write({ ID: id }))
|
this.grant()
|
||||||
.catch(() => this.close()); // will only throw if the initial grant failed
|
.then(id => stream.write({ ID: id }))
|
||||||
}, 1000 * this.ttl / 3);
|
.catch(() => this.close()); // will only throw if the initial grant failed
|
||||||
|
},
|
||||||
|
1000 * this.ttl / 3,
|
||||||
|
);
|
||||||
|
|
||||||
this.teardown = () => {
|
this.teardown = () => {
|
||||||
clearInterval(keepaliveTimer);
|
clearInterval(keepaliveTimer);
|
||||||
|
|
|
@ -30,7 +30,6 @@ import * as RPC from './rpc';
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export class Lock {
|
export class Lock {
|
||||||
|
|
||||||
private leaseTTL = 30;
|
private leaseTTL = 30;
|
||||||
private lease: Lease | null;
|
private lease: Lease | null;
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ export class Lock {
|
||||||
|
|
||||||
return lease.grant().then(leaseID => {
|
return lease.grant().then(leaseID => {
|
||||||
return new ComparatorBuilder(kv)
|
return new ComparatorBuilder(kv)
|
||||||
.and(this.key, 'createdAt', '==', 0)
|
.and(this.key, 'create', '==', 0)
|
||||||
.then(new PutBuilder(kv, this.key).value('').lease(leaseID))
|
.then(new PutBuilder(kv, this.key).value('').lease(leaseID))
|
||||||
.commit()
|
.commit()
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* Decorates a property or accessor with a memoization. By default it memoizes
|
||||||
|
* based on a strict equality check of the function's first parameter. Inspired
|
||||||
|
* by: https://gist.github.com/dsherret/cbe661faf7e3cfad8397
|
||||||
|
*/
|
||||||
|
export function Memoize(hasher: (...args: any[]) => any = value => value) { // tslint:disable-line
|
||||||
|
return (_target: any, _prop: string, descriptor: TypedPropertyDescriptor<any>) => {
|
||||||
|
if (descriptor.value != null) {
|
||||||
|
descriptor.value = getNewFunction<any, any>(hasher, descriptor.value);
|
||||||
|
} else if (descriptor.get != null) {
|
||||||
|
descriptor.get = getNewFunction<any, any>(hasher, descriptor.get);
|
||||||
|
} else {
|
||||||
|
throw new Error('Can only attach @Memoize() to methods and property getters');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordsSymbol = Symbol('memoized records');
|
||||||
|
const funcIdSymbol = Symbol('unique memoized function id');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears memoized values for a function on the provided object instance.
|
||||||
|
*/
|
||||||
|
export function forget(instance: any, func: Function) {
|
||||||
|
const id: string = (<any> func)[funcIdSymbol];
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('Cannot forget a function that is non memoized!');
|
||||||
|
}
|
||||||
|
if (!instance[recordsSymbol]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete instance[recordsSymbol][id];
|
||||||
|
}
|
||||||
|
|
||||||
|
let funcIdCounter = 0;
|
||||||
|
|
||||||
|
/* tslint:disable:no-invalid-this */
|
||||||
|
function getNewFunction<T, R>(
|
||||||
|
hasher: (...args: any[]) => T,
|
||||||
|
originalFunction: (...args: any[]) => R,
|
||||||
|
) {
|
||||||
|
const id = funcIdCounter;
|
||||||
|
funcIdCounter++;
|
||||||
|
|
||||||
|
const func = function(this: any) {
|
||||||
|
let records: { [key: string]: Map<T, R> } = this[recordsSymbol];
|
||||||
|
if (!records) {
|
||||||
|
records = this[recordsSymbol] = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = records[id];
|
||||||
|
if (!results) {
|
||||||
|
results = records[id] = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashKey = hasher.apply(this, arguments);
|
||||||
|
if (results.has(hashKey)) {
|
||||||
|
return results.get(hashKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = originalFunction.apply(this, arguments);
|
||||||
|
results.set(hashKey, result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.assign(func, { [funcIdSymbol]: id });
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { toBuffer } from './util';
|
||||||
|
|
||||||
|
const zeroKey = Buffer.from([0]);
|
||||||
|
const emptyKey = Buffer.from([]);
|
||||||
|
|
||||||
|
function compare(a: Buffer, b: Buffer) {
|
||||||
|
if (a.length === 0) {
|
||||||
|
return b.length === 0 ? 0 : 1;
|
||||||
|
}
|
||||||
|
if (b.length === 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.compare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rangable is a type that can be converted into an etcd range.
|
||||||
|
export type rangable = Range
|
||||||
|
| string
|
||||||
|
| Buffer
|
||||||
|
| { start: string | Buffer, end: string | Buffer }
|
||||||
|
| { prefix: string | Buffer };
|
||||||
|
|
||||||
|
function rangableIsPrefix(r: rangable): r is { prefix: string | Buffer } {
|
||||||
|
return r.hasOwnProperty('prefix');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range represents a byte range in etcd. Parts of this class are based on the
|
||||||
|
* logic found internally within etcd here:
|
||||||
|
* https://github.com/coreos/etcd/blob/c4a45c57135bf49ae701352c9151dc1be433d1dd/pkg/adt/interval_tree.go
|
||||||
|
*/
|
||||||
|
export class Range {
|
||||||
|
public readonly start: Buffer;
|
||||||
|
public readonly end: Buffer;
|
||||||
|
|
||||||
|
constructor(start: Buffer | string, end: Buffer | string = emptyKey) {
|
||||||
|
this.start = toBuffer(start);
|
||||||
|
this.end = toBuffer(end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the byte range includes the provided value.
|
||||||
|
*/
|
||||||
|
public includes(value: string | Buffer) {
|
||||||
|
value = toBuffer(value);
|
||||||
|
return compare(this.start, value) <= 0 && compare(this.end, value) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the other range to this one, returning:
|
||||||
|
* -1 if this range comes before the other one
|
||||||
|
* 1 if this range comes after the other one
|
||||||
|
* 0 if they overlap
|
||||||
|
*/
|
||||||
|
public compare(other: Range): number {
|
||||||
|
const ivbCmpBegin = compare(this.start, other.start);
|
||||||
|
const ivbCmpEnd = compare(this.start, other.end);
|
||||||
|
const iveCmpBegin = compare(this.end, other.start);
|
||||||
|
|
||||||
|
if (ivbCmpBegin < 0 && iveCmpBegin <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ivbCmpEnd >= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix returns a Range that maps to all keys
|
||||||
|
* prefixed with the provided string.
|
||||||
|
*/
|
||||||
|
public static prefix(prefix: string | Buffer) {
|
||||||
|
if (prefix.length === 0) {
|
||||||
|
return new Range(zeroKey, zeroKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = toBuffer(prefix);
|
||||||
|
let end = Buffer.from(start); // copy to prevent mutation
|
||||||
|
for (let i = end.length - 1; i >= 0; i--) {
|
||||||
|
if (end[i] < 0xff) {
|
||||||
|
end[i]++;
|
||||||
|
end = end.slice(0, i + 1);
|
||||||
|
return new Range(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Range(start, zeroKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a rangable into a qualified Range.
|
||||||
|
*/
|
||||||
|
public static from(v: rangable): Range {
|
||||||
|
if (typeof v === 'string' || v instanceof Buffer) {
|
||||||
|
return new Range(toBuffer(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v instanceof Range) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangableIsPrefix(v)) {
|
||||||
|
return Range.prefix(v.prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Range(v.start, v.end);
|
||||||
|
}
|
||||||
|
}
|
124
src/rpc.ts
124
src/rpc.ts
|
@ -1,7 +1,3 @@
|
||||||
/**
|
|
||||||
* RPC module doc here
|
|
||||||
*/
|
|
||||||
|
|
||||||
// AUTOGENERATED CODE, DO NOT EDIT
|
// AUTOGENERATED CODE, DO NOT EDIT
|
||||||
// tslint:disable
|
// tslint:disable
|
||||||
|
|
||||||
|
@ -297,22 +293,22 @@ export enum SortOrder {
|
||||||
/**
|
/**
|
||||||
* default, no sorting
|
* default, no sorting
|
||||||
*/
|
*/
|
||||||
NONE = 0,
|
none = 0,
|
||||||
/**
|
/**
|
||||||
* lowest target value first
|
* lowest target value first
|
||||||
*/
|
*/
|
||||||
ASCEND = 1,
|
ascend = 1,
|
||||||
/**
|
/**
|
||||||
* highest target value first
|
* highest target value first
|
||||||
*/
|
*/
|
||||||
DESCEND = 2,
|
descend = 2,
|
||||||
}
|
}
|
||||||
export enum SortTarget {
|
export enum SortTarget {
|
||||||
KEY = 0,
|
key = 0,
|
||||||
VERSION = 1,
|
version = 1,
|
||||||
CREATE = 2,
|
create = 2,
|
||||||
MOD = 3,
|
mod = 3,
|
||||||
VALUE = 4,
|
value = 4,
|
||||||
}
|
}
|
||||||
export interface IRangeRequest {
|
export interface IRangeRequest {
|
||||||
/**
|
/**
|
||||||
|
@ -341,11 +337,11 @@ export interface IRangeRequest {
|
||||||
/**
|
/**
|
||||||
* sort_order is the order for returned sorted results.
|
* sort_order is the order for returned sorted results.
|
||||||
*/
|
*/
|
||||||
sort_order?: SortOrder;
|
sort_order?: SortOrder | keyof typeof SortOrder;
|
||||||
/**
|
/**
|
||||||
* sort_target is the key-value field to use for sorting.
|
* sort_target is the key-value field to use for sorting.
|
||||||
*/
|
*/
|
||||||
sort_target?: SortTarget;
|
sort_target?: SortTarget | keyof typeof SortTarget;
|
||||||
/**
|
/**
|
||||||
* serializable sets the range request to use serializable member-local reads.
|
* serializable sets the range request to use serializable member-local reads.
|
||||||
* Range requests are linearizable by default; linearizable requests have higher
|
* Range requests are linearizable by default; linearizable requests have higher
|
||||||
|
@ -478,26 +474,26 @@ export interface IResponseOp {
|
||||||
response_delete_range: IDeleteRangeResponse;
|
response_delete_range: IDeleteRangeResponse;
|
||||||
}
|
}
|
||||||
export enum CompareResult {
|
export enum CompareResult {
|
||||||
EQUAL = 0,
|
equal = 0,
|
||||||
GREATER = 1,
|
greater = 1,
|
||||||
LESS = 2,
|
less = 2,
|
||||||
NOT_EQUAL = 3,
|
notEqual = 3,
|
||||||
}
|
}
|
||||||
export enum CompareTarget {
|
export enum CompareTarget {
|
||||||
VERSION = 0,
|
version = 0,
|
||||||
CREATE = 1,
|
create = 1,
|
||||||
MOD = 2,
|
mod = 2,
|
||||||
VALUE = 3,
|
value = 3,
|
||||||
}
|
}
|
||||||
export interface ICompare {
|
export interface ICompare {
|
||||||
/**
|
/**
|
||||||
* result is logical comparison operation for this comparison.
|
* result is logical comparison operation for this comparison.
|
||||||
*/
|
*/
|
||||||
result?: CompareResult;
|
result?: CompareResult | keyof typeof CompareResult;
|
||||||
/**
|
/**
|
||||||
* target is the key-value field to inspect for the comparison.
|
* target is the key-value field to inspect for the comparison.
|
||||||
*/
|
*/
|
||||||
target?: CompareTarget;
|
target?: CompareTarget | keyof typeof CompareTarget;
|
||||||
/**
|
/**
|
||||||
* key is the subject key for the comparison operation.
|
* key is the subject key for the comparison operation.
|
||||||
*/
|
*/
|
||||||
|
@ -551,7 +547,7 @@ export interface ITxnResponse {
|
||||||
}
|
}
|
||||||
export interface ICompactionRequest {
|
export interface ICompactionRequest {
|
||||||
/**
|
/**
|
||||||
* revision is the key-value store revision for the compaction operation.
|
* revision is the key-value store revision for the compaction operation.
|
||||||
*/
|
*/
|
||||||
revision?: string | number;
|
revision?: string | number;
|
||||||
/**
|
/**
|
||||||
|
@ -594,11 +590,11 @@ export enum FilterType {
|
||||||
/**
|
/**
|
||||||
* filter out put event.
|
* filter out put event.
|
||||||
*/
|
*/
|
||||||
NOPUT = 0,
|
noput = 0,
|
||||||
/**
|
/**
|
||||||
* filter out delete event.
|
* filter out delete event.
|
||||||
*/
|
*/
|
||||||
NODELETE = 1,
|
nodelete = 1,
|
||||||
}
|
}
|
||||||
export interface IWatchCreateRequest {
|
export interface IWatchCreateRequest {
|
||||||
/**
|
/**
|
||||||
|
@ -627,7 +623,7 @@ export interface IWatchCreateRequest {
|
||||||
/**
|
/**
|
||||||
* filters filter the events at server side before it sends back to the watcher.
|
* filters filter the events at server side before it sends back to the watcher.
|
||||||
*/
|
*/
|
||||||
filters?: FilterType[];
|
filters?: FilterType | keyof typeof FilterType[];
|
||||||
/**
|
/**
|
||||||
* If prev_kv is set, created watcher gets the previous KV before the event happens.
|
* If prev_kv is set, created watcher gets the previous KV before the event happens.
|
||||||
* If the previous KV is already compacted, nothing will be returned.
|
* If the previous KV is already compacted, nothing will be returned.
|
||||||
|
@ -768,6 +764,10 @@ export interface IMemberAddResponse {
|
||||||
* member is the member information for the added member.
|
* member is the member information for the added member.
|
||||||
*/
|
*/
|
||||||
member: IMember;
|
member: IMember;
|
||||||
|
/**
|
||||||
|
* members is a list of all members after adding the new member.
|
||||||
|
*/
|
||||||
|
members: IMember[];
|
||||||
}
|
}
|
||||||
export interface IMemberRemoveRequest {
|
export interface IMemberRemoveRequest {
|
||||||
/**
|
/**
|
||||||
|
@ -777,6 +777,10 @@ export interface IMemberRemoveRequest {
|
||||||
}
|
}
|
||||||
export interface IMemberRemoveResponse {
|
export interface IMemberRemoveResponse {
|
||||||
header: IResponseHeader;
|
header: IResponseHeader;
|
||||||
|
/**
|
||||||
|
* members is a list of all members after removing the member.
|
||||||
|
*/
|
||||||
|
members: IMember[];
|
||||||
}
|
}
|
||||||
export interface IMemberUpdateRequest {
|
export interface IMemberUpdateRequest {
|
||||||
/**
|
/**
|
||||||
|
@ -790,6 +794,10 @@ export interface IMemberUpdateRequest {
|
||||||
}
|
}
|
||||||
export interface IMemberUpdateResponse {
|
export interface IMemberUpdateResponse {
|
||||||
header: IResponseHeader;
|
header: IResponseHeader;
|
||||||
|
/**
|
||||||
|
* members is a list of all members after updating the member.
|
||||||
|
*/
|
||||||
|
members: IMember[];
|
||||||
}
|
}
|
||||||
export interface IMemberListResponse {
|
export interface IMemberListResponse {
|
||||||
header: IResponseHeader;
|
header: IResponseHeader;
|
||||||
|
@ -805,16 +813,16 @@ export enum AlarmType {
|
||||||
/**
|
/**
|
||||||
* default, used to query if any alarm is active
|
* default, used to query if any alarm is active
|
||||||
*/
|
*/
|
||||||
NONE = 0,
|
none = 0,
|
||||||
/**
|
/**
|
||||||
* space quota is exhausted
|
* space quota is exhausted
|
||||||
*/
|
*/
|
||||||
NOSPACE = 1,
|
nospace = 1,
|
||||||
}
|
}
|
||||||
export enum AlarmAction {
|
export enum AlarmAction {
|
||||||
GET = 0,
|
get = 0,
|
||||||
ACTIVATE = 1,
|
activate = 1,
|
||||||
DEACTIVATE = 2,
|
deactivate = 2,
|
||||||
}
|
}
|
||||||
export interface IAlarmRequest {
|
export interface IAlarmRequest {
|
||||||
/**
|
/**
|
||||||
|
@ -822,7 +830,7 @@ export interface IAlarmRequest {
|
||||||
* may GET alarm statuses, ACTIVATE an alarm, or DEACTIVATE a
|
* may GET alarm statuses, ACTIVATE an alarm, or DEACTIVATE a
|
||||||
* raised alarm.
|
* raised alarm.
|
||||||
*/
|
*/
|
||||||
action?: AlarmAction;
|
action?: AlarmAction | keyof typeof AlarmAction;
|
||||||
/**
|
/**
|
||||||
* memberID is the ID of the member associated with the alarm. If memberID is 0, the
|
* memberID is the ID of the member associated with the alarm. If memberID is 0, the
|
||||||
* alarm request covers all members.
|
* alarm request covers all members.
|
||||||
|
@ -831,7 +839,7 @@ export interface IAlarmRequest {
|
||||||
/**
|
/**
|
||||||
* alarm is the type of alarm to consider for this request.
|
* alarm is the type of alarm to consider for this request.
|
||||||
*/
|
*/
|
||||||
alarm?: AlarmType;
|
alarm?: AlarmType | keyof typeof AlarmType;
|
||||||
}
|
}
|
||||||
export interface IAlarmMember {
|
export interface IAlarmMember {
|
||||||
/**
|
/**
|
||||||
|
@ -841,7 +849,7 @@ export interface IAlarmMember {
|
||||||
/**
|
/**
|
||||||
* alarm is the type of alarm which has been raised.
|
* alarm is the type of alarm which has been raised.
|
||||||
*/
|
*/
|
||||||
alarm: AlarmType;
|
alarm: keyof typeof AlarmType;
|
||||||
}
|
}
|
||||||
export interface IAlarmResponse {
|
export interface IAlarmResponse {
|
||||||
header: IResponseHeader;
|
header: IResponseHeader;
|
||||||
|
@ -997,25 +1005,6 @@ export interface IAuthRoleGrantPermissionResponse {
|
||||||
export interface IAuthRoleRevokePermissionResponse {
|
export interface IAuthRoleRevokePermissionResponse {
|
||||||
header: IResponseHeader;
|
header: IResponseHeader;
|
||||||
}
|
}
|
||||||
export interface IUser {
|
|
||||||
name?: Buffer;
|
|
||||||
password?: Buffer;
|
|
||||||
roles?: string[];
|
|
||||||
}
|
|
||||||
export enum Type {
|
|
||||||
READ = 0,
|
|
||||||
WRITE = 1,
|
|
||||||
READWRITE = 2,
|
|
||||||
}
|
|
||||||
export interface IPermission {
|
|
||||||
permType: Type;
|
|
||||||
key: Buffer;
|
|
||||||
range_end: Buffer;
|
|
||||||
}
|
|
||||||
export interface IRole {
|
|
||||||
name?: Buffer;
|
|
||||||
keyPermission?: IPermission[];
|
|
||||||
}
|
|
||||||
export interface IKeyValue {
|
export interface IKeyValue {
|
||||||
/**
|
/**
|
||||||
* key is the first key for the range. If range_end is not given, the request only looks up key.
|
* key is the first key for the range. If range_end is not given, the request only looks up key.
|
||||||
|
@ -1042,14 +1031,14 @@ export enum EventType {
|
||||||
/**
|
/**
|
||||||
* filter out put event.
|
* filter out put event.
|
||||||
*/
|
*/
|
||||||
PUT = 0,
|
put = 0,
|
||||||
/**
|
/**
|
||||||
* filter out delete event.
|
* filter out delete event.
|
||||||
*/
|
*/
|
||||||
DELETE = 1,
|
delete = 1,
|
||||||
}
|
}
|
||||||
export interface IEvent {
|
export interface IEvent {
|
||||||
type: EventType;
|
type: keyof typeof EventType;
|
||||||
/**
|
/**
|
||||||
* if prev_kv is set in the request, the previous key-value pair will be returned.
|
* if prev_kv is set in the request, the previous key-value pair will be returned.
|
||||||
*/
|
*/
|
||||||
|
@ -1060,6 +1049,25 @@ export interface IEvent {
|
||||||
*/
|
*/
|
||||||
prev_kv: IKeyValue;
|
prev_kv: IKeyValue;
|
||||||
}
|
}
|
||||||
|
export interface IUser {
|
||||||
|
name?: Buffer;
|
||||||
|
password?: Buffer;
|
||||||
|
roles?: string[];
|
||||||
|
}
|
||||||
|
export enum Permission {
|
||||||
|
read = 0,
|
||||||
|
write = 1,
|
||||||
|
readwrite = 2,
|
||||||
|
}
|
||||||
|
export interface IPermission {
|
||||||
|
permType: keyof typeof Permission;
|
||||||
|
key: Buffer;
|
||||||
|
range_end: Buffer;
|
||||||
|
}
|
||||||
|
export interface IRole {
|
||||||
|
name?: Buffer;
|
||||||
|
keyPermission?: IPermission[];
|
||||||
|
}
|
||||||
export const Services = {
|
export const Services = {
|
||||||
KV: KVClient,
|
KV: KVClient,
|
||||||
Watch: WatchClient,
|
Watch: WatchClient,
|
||||||
|
|
|
@ -58,10 +58,10 @@ export class SharedPool<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextAvailable = minBy(available, r => r.availableAfter);
|
const nextAvailable = minBy(available, r => r.availableAfter);
|
||||||
this.contentionCount += 1;
|
this.contentionCount++;
|
||||||
|
|
||||||
return delay(nextAvailable[0].availableAfter - now).then(() => {
|
return delay(nextAvailable[0].availableAfter - now).then(() => {
|
||||||
this.contentionCount -= 1;
|
this.contentionCount--;
|
||||||
return this.pull();
|
return this.pull();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
15
src/util.ts
15
src/util.ts
|
@ -1,3 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Converts the input to a buffer, if it is not already.
|
||||||
|
*/
|
||||||
|
export function toBuffer(input: string | Buffer): Buffer {
|
||||||
|
if (input instanceof Buffer) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(input);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns items with the smallest value as picked by the `prop` function.
|
* Returns items with the smallest value as picked by the `prop` function.
|
||||||
|
@ -5,7 +15,7 @@
|
||||||
export function minBy<T>(items: T[], prop: (x: T) => number): T[] {
|
export function minBy<T>(items: T[], prop: (x: T) => number): T[] {
|
||||||
let min = prop(items[0]);
|
let min = prop(items[0]);
|
||||||
let output = [items[0]];
|
let output = [items[0]];
|
||||||
for (let i = 1; i < items.length; i += 1) {
|
for (let i = 1; i < items.length; i++) {
|
||||||
const thisMin = prop(items[i]);
|
const thisMin = prop(items[i]);
|
||||||
if (thisMin < min) {
|
if (thisMin < min) {
|
||||||
min = thisMin;
|
min = thisMin;
|
||||||
|
@ -37,7 +47,7 @@ export function delay(duration: number): Promise<void> {
|
||||||
*/
|
*/
|
||||||
export function forOwn<T>(obj: T, iterator: <K extends keyof T>(value: T[K], key: K) => void): void {
|
export function forOwn<T>(obj: T, iterator: <K extends keyof T>(value: T[K], key: K) => void): void {
|
||||||
const keys = <(keyof T)[]> Object.keys(obj);
|
const keys = <(keyof T)[]> Object.keys(obj);
|
||||||
for (let i = 0; i < keys.length; i += 1) {
|
for (let i = 0; i < keys.length; i++) {
|
||||||
iterator(obj[keys[i]], keys[i]);
|
iterator(obj[keys[i]], keys[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +57,6 @@ export function forOwn<T>(obj: T, iterator: <K extends keyof T>(value: T[K], key
|
||||||
* method when called.
|
* method when called.
|
||||||
*/
|
*/
|
||||||
export abstract class PromiseWrap<T> implements PromiseLike<T> {
|
export abstract class PromiseWrap<T> implements PromiseLike<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* createPromise should ben override to run the promised action.
|
* createPromise should ben override to run the promised action.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2,6 +2,6 @@ import * as chai from 'chai';
|
||||||
|
|
||||||
import { SharedPool } from '../src/shared-pool';
|
import { SharedPool } from '../src/shared-pool';
|
||||||
|
|
||||||
chai.use(require('chai-subset'));
|
chai.use(require('chai-subset')); // tslint:disable-line
|
||||||
|
|
||||||
(<any> SharedPool).deterministicInsertion = true;
|
(<any> SharedPool).deterministicInsertion = true;
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe('backoff strategies', () => {
|
||||||
random: 1,
|
random: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
function next () {
|
function next() {
|
||||||
const value = exp.getDelay();
|
const value = exp.getDelay();
|
||||||
exp = exp.next();
|
exp = exp.next();
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,117 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 2 (0x2)
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
Issuer: C=US, CN=ca.etcd.localhost, O=etcd-ca
|
||||||
|
Validity
|
||||||
|
Not Before: May 31 15:21:47 2017 GMT
|
||||||
|
Not After : May 29 15:21:47 2027 GMT
|
||||||
|
Subject: O=etcd-ca, CN=etcd-client
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (4096 bit)
|
||||||
|
Modulus:
|
||||||
|
00:9c:b7:61:32:f0:d0:4f:76:bd:e9:61:8e:cb:f0:
|
||||||
|
64:a4:48:e1:7d:1d:b8:53:b3:47:56:ae:21:6b:13:
|
||||||
|
1c:90:dd:62:9f:c6:44:d7:1a:ab:5f:1f:45:28:63:
|
||||||
|
57:ff:60:c8:c7:9e:73:e4:a8:08:ea:05:b8:3e:92:
|
||||||
|
ec:c6:90:07:56:d2:7f:33:7b:ba:ea:e3:e8:fb:11:
|
||||||
|
db:7f:c1:20:cd:23:5b:bf:44:50:a2:3e:59:5e:14:
|
||||||
|
52:6c:27:d1:bd:6c:3c:d4:14:4a:7a:ca:d4:7e:38:
|
||||||
|
98:8d:85:1b:e2:7b:89:c0:1c:61:c2:e4:70:23:9d:
|
||||||
|
f1:72:02:ce:59:3b:65:66:6d:04:54:32:0f:6d:ea:
|
||||||
|
5a:0d:68:7b:40:bb:a2:95:0d:a7:60:c2:fc:33:5f:
|
||||||
|
26:66:3e:c9:3f:76:b2:5a:0a:4d:8f:a2:e3:c5:72:
|
||||||
|
e3:ef:5e:06:fc:38:2d:87:12:de:2b:62:91:1b:b4:
|
||||||
|
45:be:c3:8f:cc:21:36:f1:6f:a3:b6:4d:c0:18:09:
|
||||||
|
17:0f:6d:d3:bd:a9:25:8d:64:9b:a8:ca:c0:bc:be:
|
||||||
|
96:a4:51:df:a0:48:c1:07:87:b6:c5:5a:3b:8c:6f:
|
||||||
|
9c:b0:ac:97:d2:41:05:7c:1d:bf:7c:f7:fa:26:ab:
|
||||||
|
ac:d6:54:60:4e:21:41:2a:aa:47:84:cb:b3:3d:e2:
|
||||||
|
01:c3:3f:7a:32:26:e6:ca:cb:c3:fc:2e:e5:63:4f:
|
||||||
|
9f:47:44:a7:a5:b1:55:23:ce:9d:75:a1:08:02:f7:
|
||||||
|
db:f2:63:5e:3a:c2:35:f0:ad:a9:a7:88:e2:95:7a:
|
||||||
|
2d:c0:0e:19:be:83:79:bf:48:3b:51:5b:e7:67:6b:
|
||||||
|
45:db:69:ab:2e:23:f4:86:1f:2b:e6:c0:90:dc:e6:
|
||||||
|
16:8f:82:bc:43:ef:c3:76:23:68:d9:1e:99:37:6f:
|
||||||
|
40:37:d0:b2:49:d3:da:1f:49:3a:c3:f8:19:e1:66:
|
||||||
|
a7:9a:22:56:c6:ce:f7:85:df:37:f7:f4:2f:74:55:
|
||||||
|
66:f3:a9:f5:11:bb:67:97:3b:31:4c:fb:e7:67:1c:
|
||||||
|
90:6b:f2:2f:56:bb:6d:cf:28:e0:bb:35:87:ad:dc:
|
||||||
|
69:02:e3:79:24:38:f2:75:cf:52:81:c1:42:c8:f4:
|
||||||
|
4e:08:b4:7c:f5:fc:f4:1a:5a:e4:6f:74:65:80:2a:
|
||||||
|
12:ad:50:73:62:08:23:6c:30:a1:89:c6:92:bf:1b:
|
||||||
|
6c:22:8f:38:f3:d2:9e:c5:6d:7a:82:91:63:3d:ec:
|
||||||
|
58:ee:d5:cf:97:eb:03:6d:14:fc:42:3a:a4:df:7a:
|
||||||
|
f4:c6:c1:37:aa:37:98:e0:46:c2:99:e4:5f:7b:f3:
|
||||||
|
1f:ec:52:26:8a:d5:f1:b8:97:d2:0b:17:b0:aa:d2:
|
||||||
|
b5:8a:a7
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
b6:a5:6c:5b:8c:a4:dc:5c:a5:43:f0:c0:47:49:15:c2:81:d9:
|
||||||
|
58:b7:53:41:f1:cc:e8:14:f2:2e:76:59:2d:d7:08:3e:e1:72:
|
||||||
|
52:0a:30:79:fc:76:a3:ee:a6:d1:07:7d:bc:4b:97:62:5b:e0:
|
||||||
|
69:62:7e:4d:0e:f7:2b:be:11:c2:56:3d:31:89:12:c1:0d:34:
|
||||||
|
82:87:e8:d6:aa:92:d4:5c:d6:7c:b1:46:4d:87:de:7d:20:12:
|
||||||
|
7f:35:76:f6:27:ef:0c:84:c3:e9:95:dd:c6:07:7e:d5:4d:5d:
|
||||||
|
c2:e7:63:5b:cd:59:59:ec:87:32:64:10:19:9b:8d:e1:b1:8a:
|
||||||
|
28:a3:0a:ba:30:8c:9d:15:63:ea:a2:42:90:34:7b:a7:aa:9b:
|
||||||
|
95:b2:5e:a6:0d:50:29:67:8f:2e:c6:f9:8d:0b:3a:e3:f5:11:
|
||||||
|
27:d3:92:97:5a:62:f8:b2:ef:c4:b2:2a:c7:32:c6:13:bf:c8:
|
||||||
|
89:76:19:43:bb:d0:52:0c:14:af:f2:67:9d:f6:32:2d:2f:18:
|
||||||
|
e0:ac:5f:d0:e6:89:43:0b:06:8f:f0:33:78:86:cb:e6:07:64:
|
||||||
|
62:7d:89:ca:06:58:23:a8:9b:39:2a:38:9f:18:93:f0:8b:d2:
|
||||||
|
bb:5f:b8:f7:55:46:1a:01:bf:c1:ea:b5:9c:01:72:65:ca:fa:
|
||||||
|
ec:63:ef:58:59:d0:7c:08:cf:25:3d:45:fb:4e:8d:51:7d:83:
|
||||||
|
74:31:2f:0a:a1:38:02:c6:a3:77:6f:c9:10:87:72:14:e9:cb:
|
||||||
|
89:82:e1:ce:3f:86:4e:66:a1:ca:40:bd:3a:27:81:a4:75:6c:
|
||||||
|
25:8e:f0:16:b8:9c:45:3a:26:b4:06:53:24:e4:f9:d5:5f:1d:
|
||||||
|
8e:55:23:cb:44:1f:90:dc:5e:74:b1:c3:4e:6d:8a:60:de:9a:
|
||||||
|
45:16:8c:05:66:c4:84:9e:76:d9:d8:56:25:12:ff:9b:fc:97:
|
||||||
|
4d:05:32:8a:9a:5e:ff:a4:e2:b0:64:1a:31:4b:b1:37:4a:fe:
|
||||||
|
6b:a3:c5:49:ff:75:bf:1b:d0:81:16:70:a5:5a:af:75:ed:ed:
|
||||||
|
ed:95:15:57:68:5d:36:57:06:b5:81:15:6a:9f:03:78:5c:8e:
|
||||||
|
9f:51:89:27:de:a6:ad:f3:5b:11:0a:04:be:7d:ca:a1:c5:f2:
|
||||||
|
1a:c0:63:3c:67:f8:b7:f6:05:98:9e:b7:64:69:e8:15:69:fa:
|
||||||
|
05:5f:20:dd:be:a2:bb:c0:8b:c4:f8:a9:96:04:6b:86:a5:18:
|
||||||
|
43:eb:6d:1f:b9:d8:76:46:e2:df:a5:0e:bc:d8:43:62:9f:6f:
|
||||||
|
bc:77:2b:30:50:a1:de:4d:cd:58:a9:4b:49:9f:ee:88:ff:be:
|
||||||
|
73:ff:e1:c0:f5:04:af:48
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
SA==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,119 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 1 (0x1)
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
Issuer: C=US, CN=ca.etcd.localhost, O=etcd-ca
|
||||||
|
Validity
|
||||||
|
Not Before: May 31 15:20:32 2017 GMT
|
||||||
|
Not After : May 29 15:20:32 2027 GMT
|
||||||
|
Subject: O=etcd-ca, CN=etcd0.localhost
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (4096 bit)
|
||||||
|
Modulus:
|
||||||
|
00:da:c3:56:43:3f:7d:50:48:dd:9b:16:4f:c1:05:
|
||||||
|
db:ad:9d:ae:8f:4e:aa:0b:f4:d8:04:a1:39:27:dc:
|
||||||
|
dc:75:05:e7:88:b6:b2:e0:b4:3a:cc:fe:19:b3:5b:
|
||||||
|
3b:ea:74:63:2c:70:58:c8:f4:42:98:ec:5e:78:c8:
|
||||||
|
36:21:15:38:0a:2d:84:61:93:56:5a:2b:e9:ca:61:
|
||||||
|
a5:fe:c9:a2:95:8f:69:af:14:2b:24:fa:1a:05:24:
|
||||||
|
23:57:a1:9a:7c:09:f4:6d:79:1d:17:b4:01:5e:07:
|
||||||
|
ca:2c:c4:9a:bb:a5:c1:37:12:92:8d:51:06:1a:5c:
|
||||||
|
c2:73:56:d4:7e:be:a9:59:80:3a:11:0b:b4:13:f9:
|
||||||
|
64:d3:d3:86:9c:58:a0:e2:0e:67:76:d9:28:37:63:
|
||||||
|
9b:89:73:60:43:d6:50:b3:c7:39:2f:4e:85:82:df:
|
||||||
|
a1:b0:87:9d:5a:70:b8:15:0c:e7:63:55:73:26:4d:
|
||||||
|
5e:bb:34:24:23:9e:ab:3b:7e:8c:71:aa:98:6a:41:
|
||||||
|
2c:1d:ce:0f:7c:fd:fd:b1:c8:c0:28:0d:d1:8e:63:
|
||||||
|
90:ed:c7:0e:1b:5f:9b:c9:80:6c:72:ee:9c:67:f4:
|
||||||
|
e8:d8:27:75:88:78:1a:ae:90:85:49:f8:e7:aa:ce:
|
||||||
|
c2:57:cb:1f:fb:a6:ea:8b:c2:e7:e2:13:f4:c7:b9:
|
||||||
|
0a:d0:af:3e:94:87:c0:37:b5:8f:d5:b9:6a:1a:27:
|
||||||
|
90:e7:a6:7e:06:98:5e:2f:ec:9d:ab:1d:e6:22:bd:
|
||||||
|
be:a1:82:db:22:5f:f5:ec:ee:a4:bf:e8:89:6e:ea:
|
||||||
|
da:6f:a9:88:e8:68:76:e3:41:19:88:32:fa:16:65:
|
||||||
|
41:a8:ab:5c:6c:6c:84:26:71:10:33:68:8a:9f:3b:
|
||||||
|
fe:b5:7c:36:2d:18:a0:2d:ac:92:18:ad:4d:6c:ac:
|
||||||
|
15:d0:77:7a:ad:09:86:3f:b4:ac:0c:1c:09:83:9a:
|
||||||
|
59:37:25:2d:0b:d1:44:04:3b:d9:3e:3c:fb:48:de:
|
||||||
|
1e:e8:e0:77:ab:6d:f6:4b:41:bd:72:65:63:80:d3:
|
||||||
|
be:a5:d3:c5:00:25:5f:7d:29:fc:86:dc:fc:8e:16:
|
||||||
|
92:cc:7a:d7:cb:e2:42:37:62:b5:12:91:69:71:56:
|
||||||
|
84:7a:e4:ca:43:77:b0:05:5e:b5:a4:35:9d:83:a5:
|
||||||
|
f3:24:46:bf:49:85:64:1f:63:81:7b:8c:4a:44:db:
|
||||||
|
0b:eb:d8:81:d4:14:67:3e:a6:34:b9:4a:55:b6:39:
|
||||||
|
d9:4b:c3:fc:9e:b4:bf:39:fe:ae:d2:ab:f2:09:a9:
|
||||||
|
67:83:fd:6b:b4:32:e9:9e:b8:d5:93:72:3d:b9:e8:
|
||||||
|
d0:8b:eb:c3:3e:7a:b2:25:ab:ee:b8:a5:ab:4a:47:
|
||||||
|
f7:77:a9
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication, TLS Web Server Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
IP Address:127.0.0.1, IP Address:10.0.1.10
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
24:8d:61:d0:08:64:f5:1c:30:5f:19:44:1a:4b:0f:1e:a3:85:
|
||||||
|
ad:6b:0a:1c:4c:3d:89:6f:cb:86:f0:a6:59:72:7d:91:33:68:
|
||||||
|
6a:90:f0:10:27:6b:54:fe:c9:94:b9:8a:ec:c5:25:61:0b:9f:
|
||||||
|
c9:7f:41:14:a4:dd:0d:72:f9:ed:31:e5:b3:d8:a7:f0:82:00:
|
||||||
|
58:20:2a:96:53:2e:f6:73:7e:77:ae:1d:19:3b:5b:fc:b3:90:
|
||||||
|
39:f0:6d:82:60:66:a3:c4:98:ba:c8:92:7e:e1:4a:a5:79:99:
|
||||||
|
2c:60:79:cc:86:ec:e5:e7:d5:fb:b8:78:0c:dd:7a:2d:a4:2f:
|
||||||
|
25:ff:b4:ff:48:68:f1:60:c9:0f:93:94:df:a5:1e:1e:1a:0c:
|
||||||
|
63:67:ae:5a:15:23:77:94:5c:b4:75:b7:06:30:62:45:e7:5e:
|
||||||
|
8b:c1:77:a4:22:8b:fc:de:49:95:e6:a8:df:2a:25:05:3d:7b:
|
||||||
|
24:39:af:5f:94:12:a1:29:50:23:03:8d:f5:5e:d1:20:e7:37:
|
||||||
|
bc:83:02:de:30:51:54:58:48:41:ef:d4:6e:90:6b:68:87:6c:
|
||||||
|
d8:0a:3a:5f:62:b5:5f:35:42:b6:5e:39:0e:9c:19:0e:3f:e1:
|
||||||
|
68:f6:dc:91:a7:74:0b:5f:7f:81:78:55:85:a5:9a:28:7b:64:
|
||||||
|
19:e8:54:ba:77:fc:62:e3:1f:e2:28:09:4d:28:08:98:6e:f9:
|
||||||
|
b3:b8:90:32:4c:69:a5:a5:72:49:88:03:bd:45:36:15:a8:66:
|
||||||
|
dc:b5:98:7d:6e:80:7c:5c:f2:dd:ff:62:91:b7:90:c7:9d:ce:
|
||||||
|
9a:da:e1:62:c8:07:2a:7b:d5:31:b5:c4:0f:44:59:1a:58:0b:
|
||||||
|
7a:35:6d:1b:e7:d6:15:5b:1d:48:dd:8e:f7:13:91:ff:4d:32:
|
||||||
|
1a:06:7c:01:38:dd:eb:9d:a8:63:3c:60:33:29:7d:e4:b4:08:
|
||||||
|
e8:12:1b:da:ee:3c:28:eb:e5:b5:c6:51:41:8d:82:7f:32:c9:
|
||||||
|
50:f5:41:a2:15:b1:64:e1:ec:01:eb:b2:2e:4b:60:57:32:79:
|
||||||
|
36:12:e7:ca:6c:28:8d:92:5f:74:10:f3:b2:8d:76:9b:ec:f6:
|
||||||
|
9e:34:81:2a:62:49:a0:ca:13:4d:9f:92:f6:7c:12:dc:76:fb:
|
||||||
|
11:cc:ae:e6:f2:6a:27:2b:7d:04:c9:91:ce:2f:d8:ab:6b:f0:
|
||||||
|
4e:e3:b4:63:e1:38:c8:fa:e3:41:43:9a:2c:8c:31:13:b8:cc:
|
||||||
|
ec:e2:24:6e:90:67:da:a4:59:68:ad:f6:6e:7c:fd:d8:f5:f1:
|
||||||
|
8c:e3:5d:80:aa:21:d9:14:ca:9b:cd:25:bb:d2:19:1c:f5:04:
|
||||||
|
ef:2e:b7:42:59:35:2d:df
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEuDCCAqACAQAwNTELMAkGA1UEBhMCVVMxFDASBgNVBAMMC2V0Y2QtY2xpZW50
|
||||||
|
MRAwDgYDVQQKDAdldGNkLWNhMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
|
||||||
|
AgEAnLdhMvDQT3a96WGOy/BkpEjhfR24U7NHVq4haxMckN1in8ZE1xqrXx9FKGNX
|
||||||
|
/2DIx55z5KgI6gW4PpLsxpAHVtJ/M3u66uPo+xHbf8EgzSNbv0RQoj5ZXhRSbCfR
|
||||||
|
vWw81BRKesrUfjiYjYUb4nuJwBxhwuRwI53xcgLOWTtlZm0EVDIPbepaDWh7QLui
|
||||||
|
lQ2nYML8M18mZj7JP3ayWgpNj6LjxXLj714G/DgthxLeK2KRG7RFvsOPzCE28W+j
|
||||||
|
tk3AGAkXD23TvakljWSbqMrAvL6WpFHfoEjBB4e2xVo7jG+csKyX0kEFfB2/fPf6
|
||||||
|
Jqus1lRgTiFBKqpHhMuzPeIBwz96MibmysvD/C7lY0+fR0SnpbFVI86ddaEIAvfb
|
||||||
|
8mNeOsI18K2pp4jilXotwA4ZvoN5v0g7UVvnZ2tF22mrLiP0hh8r5sCQ3OYWj4K8
|
||||||
|
Q+/DdiNo2R6ZN29AN9CySdPaH0k6w/gZ4WanmiJWxs73hd839/QvdFVm86n1Ebtn
|
||||||
|
lzsxTPvnZxyQa/IvVrttzyjguzWHrdxpAuN5JDjydc9SgcFCyPROCLR89fz0Glrk
|
||||||
|
b3RlgCoSrVBzYggjbDChicaSvxtsIo8489KexW16gpFjPexY7tXPl+sDbRT8Qjqk
|
||||||
|
33r0xsE3qjeY4EbCmeRfe/Mf7FImitXxuJfSCxewqtK1iqcCAwEAAaA+MDwGCSqG
|
||||||
|
SIb3DQEJDjEvMC0wCQYDVR0TBAIwADATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNV
|
||||||
|
HQ8EBAMCBaAwDQYJKoZIhvcNAQELBQADggIBAEAmerskdQikcyoId53QIjEBmYaB
|
||||||
|
93LtbvWBtxGb3NtP5wot5sF8mXpC5XPJe6Yn/haalH8yGUuDfiPZUC//VqzRnz+0
|
||||||
|
yPsXDcjg34t27VZAkc2yDBGq1VQ7PsvCsM1BN/q/ywecKIvFLVTjuK2Rl3A7Nhyw
|
||||||
|
JvS1Nv47GZKg6TiGIMFsdjYFR4XiIuoOSW75NwbjxwBqKBAJRNh/iVZJ0IG7kKsg
|
||||||
|
z5kb+m22ekVSXkvxSKsqTF6sDJM2CAXsmv6rQmh1bLKP9T4Hf9aiM+lgqGFSKFSS
|
||||||
|
lUhNp12zBDlxwEmYbI7DUp1WnE+bsdAcX/mzL2BUVLGU+OdsM8pLEOpRJxKJYo0Q
|
||||||
|
Hif5cpmNB3t9mdXVDMrTrA/XNUAIkWclqI6zW857OeT2LNidtRlvFTVf4JDzosmB
|
||||||
|
EZPBh3j+Cu03zZOuUvQVOzZYZIZx4oYTtMSTT75pM622RqOl2Kk+4cRCjd0Grb0B
|
||||||
|
yFvZTzHdIVA0Ay3YEEOsbOxnz2nV4Y4v6q6xUhzDL5XudWW5iCySc9ql6ZQRfxMQ
|
||||||
|
M+9sAFn5d7Ckr6kSM/Vzf/txedIvOSwaRrf8p7ZdosKys6+hHPnsihvxiEhWgrET
|
||||||
|
/zZxNw5pZYpbczN0C92e3t2iGbWADSW16zVYJ1VkENGHqiHe5qjlwB7iPo3Db1Mp
|
||||||
|
pUWbCW6IjndkAPmh
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,28 @@
|
||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEvDCCAqQCAQAwOTELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD2V0Y2QwLmxvY2Fs
|
||||||
|
aG9zdDEQMA4GA1UECgwHZXRjZC1jYTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
|
||||||
|
AgoCggIBANrDVkM/fVBI3ZsWT8EF262dro9Oqgv02AShOSfc3HUF54i2suC0Osz+
|
||||||
|
GbNbO+p0YyxwWMj0QpjsXnjINiEVOAothGGTVlor6cphpf7JopWPaa8UKyT6GgUk
|
||||||
|
I1ehmnwJ9G15HRe0AV4HyizEmrulwTcSko1RBhpcwnNW1H6+qVmAOhELtBP5ZNPT
|
||||||
|
hpxYoOIOZ3bZKDdjm4lzYEPWULPHOS9OhYLfobCHnVpwuBUM52NVcyZNXrs0JCOe
|
||||||
|
qzt+jHGqmGpBLB3OD3z9/bHIwCgN0Y5jkO3HDhtfm8mAbHLunGf06NgndYh4Gq6Q
|
||||||
|
hUn456rOwlfLH/um6ovC5+IT9Me5CtCvPpSHwDe1j9W5ahonkOemfgaYXi/snasd
|
||||||
|
5iK9vqGC2yJf9ezupL/oiW7q2m+piOhoduNBGYgy+hZlQairXGxshCZxEDNoip87
|
||||||
|
/rV8Ni0YoC2skhitTWysFdB3eq0Jhj+0rAwcCYOaWTclLQvRRAQ72T48+0jeHujg
|
||||||
|
d6tt9ktBvXJlY4DTvqXTxQAlX30p/Ibc/I4Wksx618viQjditRKRaXFWhHrkykN3
|
||||||
|
sAVetaQ1nYOl8yRGv0mFZB9jgXuMSkTbC+vYgdQUZz6mNLlKVbY52UvD/J60vzn+
|
||||||
|
rtKr8gmpZ4P9a7Qy6Z641ZNyPbno0Ivrwz56siWr7rilq0pH93epAgMBAAGgPjA8
|
||||||
|
BgkqhkiG9w0BCQ4xLzAtMAkGA1UdEwQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwIw
|
||||||
|
CwYDVR0PBAQDAgWgMA0GCSqGSIb3DQEBCwUAA4ICAQBtqTYafzl3HF98FpAaBBOb
|
||||||
|
tLqdV4Q98YgX0wLiqSEsSomDT1TbhdDODXiwPNBefIEhXH1TKCd/a54MzQ/JyjS8
|
||||||
|
sQiVthG/Rf2UTc+0xoGs0sosPE9I2K+m7WwKSm6E6+BHwQ3cauPRupG3CkJqPgSX
|
||||||
|
TQ6dg4dqqjH6UdNVwYwIOC4XCS6CcJChIodiYFlqWvPOMpXlw0Tce4AtjRUSMoYj
|
||||||
|
SUselwIhlGsE6Hzq6M52qzjOb2GbR2RjXgdc0dn+8izwPAhgEa68emzjx/KMfjaH
|
||||||
|
9RRpOZ+EgIPE1eYhZViwyM5VM3tB2qkAnFYqPSzlUte/bh4rT7wAEGUnItpV/bqq
|
||||||
|
HnYucMyKEz5rysROBlDb93szAZxzJCuBTrVd5O2XpclFngnEvAWOt316IzvW/Yvu
|
||||||
|
3IK3RACLQqqoPDktyYxcFlY6dI0jSG8XB/ZZGeyi/FVOZ/sk0guuwQeqjb/Rx3zp
|
||||||
|
V33GFZoR/7yiwkA3c49DhbioBYuUQXuiZQuL1MnE9QeYX9GjIRwX9shyB93jr2VZ
|
||||||
|
58aJSQu5/tdwyFNaUeZ+EO2ZAY455nlKTJxsHMwVHLXtMsJXIm8/ikdzxLuMg8mt
|
||||||
|
DO9LtgiPbHce+7bkiGcVw2fjH+ZmfJAlqrKR1+/d6kAPsPrB09NhenEQ+oFpGcaQ
|
||||||
|
TneEPnjcCS3PE0CBCHs8yg==
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,119 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 1 (0x1)
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
Issuer: C=US, CN=ca.etcd.localhost, O=etcd-ca
|
||||||
|
Validity
|
||||||
|
Not Before: May 31 15:20:32 2017 GMT
|
||||||
|
Not After : May 29 15:20:32 2027 GMT
|
||||||
|
Subject: O=etcd-ca, CN=etcd0.localhost
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (4096 bit)
|
||||||
|
Modulus:
|
||||||
|
00:da:c3:56:43:3f:7d:50:48:dd:9b:16:4f:c1:05:
|
||||||
|
db:ad:9d:ae:8f:4e:aa:0b:f4:d8:04:a1:39:27:dc:
|
||||||
|
dc:75:05:e7:88:b6:b2:e0:b4:3a:cc:fe:19:b3:5b:
|
||||||
|
3b:ea:74:63:2c:70:58:c8:f4:42:98:ec:5e:78:c8:
|
||||||
|
36:21:15:38:0a:2d:84:61:93:56:5a:2b:e9:ca:61:
|
||||||
|
a5:fe:c9:a2:95:8f:69:af:14:2b:24:fa:1a:05:24:
|
||||||
|
23:57:a1:9a:7c:09:f4:6d:79:1d:17:b4:01:5e:07:
|
||||||
|
ca:2c:c4:9a:bb:a5:c1:37:12:92:8d:51:06:1a:5c:
|
||||||
|
c2:73:56:d4:7e:be:a9:59:80:3a:11:0b:b4:13:f9:
|
||||||
|
64:d3:d3:86:9c:58:a0:e2:0e:67:76:d9:28:37:63:
|
||||||
|
9b:89:73:60:43:d6:50:b3:c7:39:2f:4e:85:82:df:
|
||||||
|
a1:b0:87:9d:5a:70:b8:15:0c:e7:63:55:73:26:4d:
|
||||||
|
5e:bb:34:24:23:9e:ab:3b:7e:8c:71:aa:98:6a:41:
|
||||||
|
2c:1d:ce:0f:7c:fd:fd:b1:c8:c0:28:0d:d1:8e:63:
|
||||||
|
90:ed:c7:0e:1b:5f:9b:c9:80:6c:72:ee:9c:67:f4:
|
||||||
|
e8:d8:27:75:88:78:1a:ae:90:85:49:f8:e7:aa:ce:
|
||||||
|
c2:57:cb:1f:fb:a6:ea:8b:c2:e7:e2:13:f4:c7:b9:
|
||||||
|
0a:d0:af:3e:94:87:c0:37:b5:8f:d5:b9:6a:1a:27:
|
||||||
|
90:e7:a6:7e:06:98:5e:2f:ec:9d:ab:1d:e6:22:bd:
|
||||||
|
be:a1:82:db:22:5f:f5:ec:ee:a4:bf:e8:89:6e:ea:
|
||||||
|
da:6f:a9:88:e8:68:76:e3:41:19:88:32:fa:16:65:
|
||||||
|
41:a8:ab:5c:6c:6c:84:26:71:10:33:68:8a:9f:3b:
|
||||||
|
fe:b5:7c:36:2d:18:a0:2d:ac:92:18:ad:4d:6c:ac:
|
||||||
|
15:d0:77:7a:ad:09:86:3f:b4:ac:0c:1c:09:83:9a:
|
||||||
|
59:37:25:2d:0b:d1:44:04:3b:d9:3e:3c:fb:48:de:
|
||||||
|
1e:e8:e0:77:ab:6d:f6:4b:41:bd:72:65:63:80:d3:
|
||||||
|
be:a5:d3:c5:00:25:5f:7d:29:fc:86:dc:fc:8e:16:
|
||||||
|
92:cc:7a:d7:cb:e2:42:37:62:b5:12:91:69:71:56:
|
||||||
|
84:7a:e4:ca:43:77:b0:05:5e:b5:a4:35:9d:83:a5:
|
||||||
|
f3:24:46:bf:49:85:64:1f:63:81:7b:8c:4a:44:db:
|
||||||
|
0b:eb:d8:81:d4:14:67:3e:a6:34:b9:4a:55:b6:39:
|
||||||
|
d9:4b:c3:fc:9e:b4:bf:39:fe:ae:d2:ab:f2:09:a9:
|
||||||
|
67:83:fd:6b:b4:32:e9:9e:b8:d5:93:72:3d:b9:e8:
|
||||||
|
d0:8b:eb:c3:3e:7a:b2:25:ab:ee:b8:a5:ab:4a:47:
|
||||||
|
f7:77:a9
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication, TLS Web Server Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
X509v3 Subject Alternative Name:
|
||||||
|
IP Address:127.0.0.1, IP Address:10.0.1.10
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
24:8d:61:d0:08:64:f5:1c:30:5f:19:44:1a:4b:0f:1e:a3:85:
|
||||||
|
ad:6b:0a:1c:4c:3d:89:6f:cb:86:f0:a6:59:72:7d:91:33:68:
|
||||||
|
6a:90:f0:10:27:6b:54:fe:c9:94:b9:8a:ec:c5:25:61:0b:9f:
|
||||||
|
c9:7f:41:14:a4:dd:0d:72:f9:ed:31:e5:b3:d8:a7:f0:82:00:
|
||||||
|
58:20:2a:96:53:2e:f6:73:7e:77:ae:1d:19:3b:5b:fc:b3:90:
|
||||||
|
39:f0:6d:82:60:66:a3:c4:98:ba:c8:92:7e:e1:4a:a5:79:99:
|
||||||
|
2c:60:79:cc:86:ec:e5:e7:d5:fb:b8:78:0c:dd:7a:2d:a4:2f:
|
||||||
|
25:ff:b4:ff:48:68:f1:60:c9:0f:93:94:df:a5:1e:1e:1a:0c:
|
||||||
|
63:67:ae:5a:15:23:77:94:5c:b4:75:b7:06:30:62:45:e7:5e:
|
||||||
|
8b:c1:77:a4:22:8b:fc:de:49:95:e6:a8:df:2a:25:05:3d:7b:
|
||||||
|
24:39:af:5f:94:12:a1:29:50:23:03:8d:f5:5e:d1:20:e7:37:
|
||||||
|
bc:83:02:de:30:51:54:58:48:41:ef:d4:6e:90:6b:68:87:6c:
|
||||||
|
d8:0a:3a:5f:62:b5:5f:35:42:b6:5e:39:0e:9c:19:0e:3f:e1:
|
||||||
|
68:f6:dc:91:a7:74:0b:5f:7f:81:78:55:85:a5:9a:28:7b:64:
|
||||||
|
19:e8:54:ba:77:fc:62:e3:1f:e2:28:09:4d:28:08:98:6e:f9:
|
||||||
|
b3:b8:90:32:4c:69:a5:a5:72:49:88:03:bd:45:36:15:a8:66:
|
||||||
|
dc:b5:98:7d:6e:80:7c:5c:f2:dd:ff:62:91:b7:90:c7:9d:ce:
|
||||||
|
9a:da:e1:62:c8:07:2a:7b:d5:31:b5:c4:0f:44:59:1a:58:0b:
|
||||||
|
7a:35:6d:1b:e7:d6:15:5b:1d:48:dd:8e:f7:13:91:ff:4d:32:
|
||||||
|
1a:06:7c:01:38:dd:eb:9d:a8:63:3c:60:33:29:7d:e4:b4:08:
|
||||||
|
e8:12:1b:da:ee:3c:28:eb:e5:b5:c6:51:41:8d:82:7f:32:c9:
|
||||||
|
50:f5:41:a2:15:b1:64:e1:ec:01:eb:b2:2e:4b:60:57:32:79:
|
||||||
|
36:12:e7:ca:6c:28:8d:92:5f:74:10:f3:b2:8d:76:9b:ec:f6:
|
||||||
|
9e:34:81:2a:62:49:a0:ca:13:4d:9f:92:f6:7c:12:dc:76:fb:
|
||||||
|
11:cc:ae:e6:f2:6a:27:2b:7d:04:c9:91:ce:2f:d8:ab:6b:f0:
|
||||||
|
4e:e3:b4:63:e1:38:c8:fa:e3:41:43:9a:2c:8c:31:13:b8:cc:
|
||||||
|
ec:e2:24:6e:90:67:da:a4:59:68:ad:f6:6e:7c:fd:d8:f5:f1:
|
||||||
|
8c:e3:5d:80:aa:21:d9:14:ca:9b:cd:25:bb:d2:19:1c:f5:04:
|
||||||
|
ef:2e:b7:42:59:35:2d:df
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,117 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 2 (0x2)
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
Issuer: C=US, CN=ca.etcd.localhost, O=etcd-ca
|
||||||
|
Validity
|
||||||
|
Not Before: May 31 15:21:47 2017 GMT
|
||||||
|
Not After : May 29 15:21:47 2027 GMT
|
||||||
|
Subject: O=etcd-ca, CN=etcd-client
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (4096 bit)
|
||||||
|
Modulus:
|
||||||
|
00:9c:b7:61:32:f0:d0:4f:76:bd:e9:61:8e:cb:f0:
|
||||||
|
64:a4:48:e1:7d:1d:b8:53:b3:47:56:ae:21:6b:13:
|
||||||
|
1c:90:dd:62:9f:c6:44:d7:1a:ab:5f:1f:45:28:63:
|
||||||
|
57:ff:60:c8:c7:9e:73:e4:a8:08:ea:05:b8:3e:92:
|
||||||
|
ec:c6:90:07:56:d2:7f:33:7b:ba:ea:e3:e8:fb:11:
|
||||||
|
db:7f:c1:20:cd:23:5b:bf:44:50:a2:3e:59:5e:14:
|
||||||
|
52:6c:27:d1:bd:6c:3c:d4:14:4a:7a:ca:d4:7e:38:
|
||||||
|
98:8d:85:1b:e2:7b:89:c0:1c:61:c2:e4:70:23:9d:
|
||||||
|
f1:72:02:ce:59:3b:65:66:6d:04:54:32:0f:6d:ea:
|
||||||
|
5a:0d:68:7b:40:bb:a2:95:0d:a7:60:c2:fc:33:5f:
|
||||||
|
26:66:3e:c9:3f:76:b2:5a:0a:4d:8f:a2:e3:c5:72:
|
||||||
|
e3:ef:5e:06:fc:38:2d:87:12:de:2b:62:91:1b:b4:
|
||||||
|
45:be:c3:8f:cc:21:36:f1:6f:a3:b6:4d:c0:18:09:
|
||||||
|
17:0f:6d:d3:bd:a9:25:8d:64:9b:a8:ca:c0:bc:be:
|
||||||
|
96:a4:51:df:a0:48:c1:07:87:b6:c5:5a:3b:8c:6f:
|
||||||
|
9c:b0:ac:97:d2:41:05:7c:1d:bf:7c:f7:fa:26:ab:
|
||||||
|
ac:d6:54:60:4e:21:41:2a:aa:47:84:cb:b3:3d:e2:
|
||||||
|
01:c3:3f:7a:32:26:e6:ca:cb:c3:fc:2e:e5:63:4f:
|
||||||
|
9f:47:44:a7:a5:b1:55:23:ce:9d:75:a1:08:02:f7:
|
||||||
|
db:f2:63:5e:3a:c2:35:f0:ad:a9:a7:88:e2:95:7a:
|
||||||
|
2d:c0:0e:19:be:83:79:bf:48:3b:51:5b:e7:67:6b:
|
||||||
|
45:db:69:ab:2e:23:f4:86:1f:2b:e6:c0:90:dc:e6:
|
||||||
|
16:8f:82:bc:43:ef:c3:76:23:68:d9:1e:99:37:6f:
|
||||||
|
40:37:d0:b2:49:d3:da:1f:49:3a:c3:f8:19:e1:66:
|
||||||
|
a7:9a:22:56:c6:ce:f7:85:df:37:f7:f4:2f:74:55:
|
||||||
|
66:f3:a9:f5:11:bb:67:97:3b:31:4c:fb:e7:67:1c:
|
||||||
|
90:6b:f2:2f:56:bb:6d:cf:28:e0:bb:35:87:ad:dc:
|
||||||
|
69:02:e3:79:24:38:f2:75:cf:52:81:c1:42:c8:f4:
|
||||||
|
4e:08:b4:7c:f5:fc:f4:1a:5a:e4:6f:74:65:80:2a:
|
||||||
|
12:ad:50:73:62:08:23:6c:30:a1:89:c6:92:bf:1b:
|
||||||
|
6c:22:8f:38:f3:d2:9e:c5:6d:7a:82:91:63:3d:ec:
|
||||||
|
58:ee:d5:cf:97:eb:03:6d:14:fc:42:3a:a4:df:7a:
|
||||||
|
f4:c6:c1:37:aa:37:98:e0:46:c2:99:e4:5f:7b:f3:
|
||||||
|
1f:ec:52:26:8a:d5:f1:b8:97:d2:0b:17:b0:aa:d2:
|
||||||
|
b5:8a:a7
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
Signature Algorithm: sha512WithRSAEncryption
|
||||||
|
b6:a5:6c:5b:8c:a4:dc:5c:a5:43:f0:c0:47:49:15:c2:81:d9:
|
||||||
|
58:b7:53:41:f1:cc:e8:14:f2:2e:76:59:2d:d7:08:3e:e1:72:
|
||||||
|
52:0a:30:79:fc:76:a3:ee:a6:d1:07:7d:bc:4b:97:62:5b:e0:
|
||||||
|
69:62:7e:4d:0e:f7:2b:be:11:c2:56:3d:31:89:12:c1:0d:34:
|
||||||
|
82:87:e8:d6:aa:92:d4:5c:d6:7c:b1:46:4d:87:de:7d:20:12:
|
||||||
|
7f:35:76:f6:27:ef:0c:84:c3:e9:95:dd:c6:07:7e:d5:4d:5d:
|
||||||
|
c2:e7:63:5b:cd:59:59:ec:87:32:64:10:19:9b:8d:e1:b1:8a:
|
||||||
|
28:a3:0a:ba:30:8c:9d:15:63:ea:a2:42:90:34:7b:a7:aa:9b:
|
||||||
|
95:b2:5e:a6:0d:50:29:67:8f:2e:c6:f9:8d:0b:3a:e3:f5:11:
|
||||||
|
27:d3:92:97:5a:62:f8:b2:ef:c4:b2:2a:c7:32:c6:13:bf:c8:
|
||||||
|
89:76:19:43:bb:d0:52:0c:14:af:f2:67:9d:f6:32:2d:2f:18:
|
||||||
|
e0:ac:5f:d0:e6:89:43:0b:06:8f:f0:33:78:86:cb:e6:07:64:
|
||||||
|
62:7d:89:ca:06:58:23:a8:9b:39:2a:38:9f:18:93:f0:8b:d2:
|
||||||
|
bb:5f:b8:f7:55:46:1a:01:bf:c1:ea:b5:9c:01:72:65:ca:fa:
|
||||||
|
ec:63:ef:58:59:d0:7c:08:cf:25:3d:45:fb:4e:8d:51:7d:83:
|
||||||
|
74:31:2f:0a:a1:38:02:c6:a3:77:6f:c9:10:87:72:14:e9:cb:
|
||||||
|
89:82:e1:ce:3f:86:4e:66:a1:ca:40:bd:3a:27:81:a4:75:6c:
|
||||||
|
25:8e:f0:16:b8:9c:45:3a:26:b4:06:53:24:e4:f9:d5:5f:1d:
|
||||||
|
8e:55:23:cb:44:1f:90:dc:5e:74:b1:c3:4e:6d:8a:60:de:9a:
|
||||||
|
45:16:8c:05:66:c4:84:9e:76:d9:d8:56:25:12:ff:9b:fc:97:
|
||||||
|
4d:05:32:8a:9a:5e:ff:a4:e2:b0:64:1a:31:4b:b1:37:4a:fe:
|
||||||
|
6b:a3:c5:49:ff:75:bf:1b:d0:81:16:70:a5:5a:af:75:ed:ed:
|
||||||
|
ed:95:15:57:68:5d:36:57:06:b5:81:15:6a:9f:03:78:5c:8e:
|
||||||
|
9f:51:89:27:de:a6:ad:f3:5b:11:0a:04:be:7d:ca:a1:c5:f2:
|
||||||
|
1a:c0:63:3c:67:f8:b7:f6:05:98:9e:b7:64:69:e8:15:69:fa:
|
||||||
|
05:5f:20:dd:be:a2:bb:c0:8b:c4:f8:a9:96:04:6b:86:a5:18:
|
||||||
|
43:eb:6d:1f:b9:d8:76:46:e2:df:a5:0e:bc:d8:43:62:9f:6f:
|
||||||
|
bc:77:2b:30:50:a1:de:4d:cd:58:a9:4b:49:9f:ee:88:ff:be:
|
||||||
|
73:ff:e1:c0:f5:04:af:48
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
SA==
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
MJS8GsAhCrLBofSK4hSvUKj3x1qo
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
lGlsn9EcTo64YBVsKed9ozJlDcJ7
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,52 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
HsoCRI7O1n+/trrmcq1D7JW0FVcbNA==
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
These certificates are self-signed certs created using [these instructions](https://github.com/kelseyhightower/etcd-production-setup).
|
|
@ -5,31 +5,47 @@ import {
|
||||||
Etcd3,
|
Etcd3,
|
||||||
EtcdLeaseInvalidError,
|
EtcdLeaseInvalidError,
|
||||||
EtcdLockFailedError,
|
EtcdLockFailedError,
|
||||||
|
EtcdRoleExistsError,
|
||||||
|
EtcdRoleNotFoundError,
|
||||||
|
EtcdRoleNotGrantedError,
|
||||||
|
EtcdUserExistsError,
|
||||||
|
EtcdUserNotFoundError,
|
||||||
GRPCConnectFailedError,
|
GRPCConnectFailedError,
|
||||||
Lease,
|
Lease,
|
||||||
|
Role,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import { getHosts } from './util';
|
import { getOptions } from './util';
|
||||||
|
|
||||||
describe('connection pool', () => {
|
function expectReject(promise: Promise<any>, err: { new (message: string): Error }) {
|
||||||
|
return promise
|
||||||
|
.then(() => { throw new Error('expected to reject'); })
|
||||||
|
.catch(actualErr => expect(actualErr).to.be.an.instanceof(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function wipeAll(things: Promise<{ delete(): any }[]>) {
|
||||||
|
return things.then(items => Promise.all(items.map(item => item.delete())));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('client', () => {
|
||||||
let client: Etcd3;
|
let client: Etcd3;
|
||||||
let badClient: Etcd3;
|
let badClient: Etcd3;
|
||||||
|
|
||||||
beforeEach(() => {
|
// beforeEach(() => {
|
||||||
client = new Etcd3({ hosts: getHosts() });
|
// client = new Etcd3(getOptions());
|
||||||
badClient = new Etcd3({ hosts: '127.0.0.1:1' });
|
// badClient = new Etcd3(getOptions({ hosts: '127.0.0.1:1' }));
|
||||||
return Promise.all([
|
// return Promise.all([
|
||||||
client.put('foo1').value('bar1'),
|
// client.put('foo1').value('bar1'),
|
||||||
client.put('foo2').value('bar2'),
|
// client.put('foo2').value('bar2'),
|
||||||
client.put('foo3').value('{"value":"bar3"}'),
|
// client.put('foo3').value('{"value":"bar3"}'),
|
||||||
client.put('baz').value('bar5'),
|
// client.put('baz').value('bar5'),
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
|
|
||||||
afterEach(async () => {
|
// afterEach(async () => {
|
||||||
await client.delete().all();
|
// await client.delete().all();
|
||||||
client.close();
|
// client.close();
|
||||||
badClient.close();
|
// badClient.close();
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('allows mocking', async () => {
|
it('allows mocking', async () => {
|
||||||
const mock = client.mock({
|
const mock = client.mock({
|
||||||
|
@ -95,14 +111,14 @@ describe('connection pool', () => {
|
||||||
it('sorts', async () => {
|
it('sorts', async () => {
|
||||||
expect(await client.getAll()
|
expect(await client.getAll()
|
||||||
.prefix('foo')
|
.prefix('foo')
|
||||||
.sort('key', 'asc')
|
.sort('key', 'ascend')
|
||||||
.limit(2)
|
.limit(2)
|
||||||
.keys(),
|
.keys(),
|
||||||
).to.deep.equal(['1', '2']);
|
).to.deep.equal(['1', '2']);
|
||||||
|
|
||||||
expect(await client.getAll()
|
expect(await client.getAll()
|
||||||
.prefix('foo')
|
.prefix('foo')
|
||||||
.sort('key', 'desc')
|
.sort('key', 'descend')
|
||||||
.limit(2)
|
.limit(2)
|
||||||
.keys(),
|
.keys(),
|
||||||
).to.deep.equal(['3', '2']);
|
).to.deep.equal(['3', '2']);
|
||||||
|
@ -311,4 +327,169 @@ describe('connection pool', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('roles', () => {
|
||||||
|
afterEach(() => wipeAll(client.getRoles()));
|
||||||
|
|
||||||
|
const expectRoles = async (expected: string[]) => {
|
||||||
|
const list = await client.getRoles();
|
||||||
|
expect(list.map(r => r.name)).to.deep.equal(expected);
|
||||||
|
};
|
||||||
|
|
||||||
|
it('create and deletes', async () => {
|
||||||
|
const fooRole = await client.role('foo').create();
|
||||||
|
expectRoles(['foo']);
|
||||||
|
await fooRole.delete();
|
||||||
|
expectRoles([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on existing roles', async () => {
|
||||||
|
await client.role('foo').create();
|
||||||
|
await expectReject(client.role('foo').create(), EtcdRoleExistsError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on deleting a non-existent role', async () => {
|
||||||
|
await expectReject(client.role('foo').delete(), EtcdRoleNotFoundError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on granting permission to a non-existent role', async () => {
|
||||||
|
await expectReject(
|
||||||
|
client.role('foo').grant({
|
||||||
|
permission: 'read',
|
||||||
|
range: client.range({ prefix: '111' }),
|
||||||
|
}),
|
||||||
|
EtcdRoleNotFoundError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('round trips permission grants', async () => {
|
||||||
|
const fooRole = await client.role('foo').create();
|
||||||
|
await fooRole.grant({
|
||||||
|
permission: 'read',
|
||||||
|
range: client.range({ prefix: '111' }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perms = await fooRole.permissions();
|
||||||
|
expect(perms).to.containSubset([
|
||||||
|
{
|
||||||
|
permission: 'read',
|
||||||
|
range: client.range({ prefix: '111' }),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await fooRole.revoke(perms[0]);
|
||||||
|
expect(await fooRole.permissions()).to.have.length(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('users', () => {
|
||||||
|
let fooRole: Role;
|
||||||
|
beforeEach(async () => {
|
||||||
|
fooRole = client.role('foo');
|
||||||
|
await fooRole.create();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await fooRole.delete();
|
||||||
|
await wipeAll(client.getUsers());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates users', async () => {
|
||||||
|
expect(await client.getUsers()).to.have.lengthOf(0);
|
||||||
|
await client.user('connor').create('password');
|
||||||
|
expect(await client.getUsers()).to.containSubset([{ name: 'connor' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on existing users', async () => {
|
||||||
|
await client.user('connor').create('password');
|
||||||
|
await expectReject(client.user('connor').create('password'), EtcdUserExistsError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on regranting the same role multiple times', async () => {
|
||||||
|
const user = await client.user('connor').create('password');
|
||||||
|
await expectReject(user.removeRole(fooRole), EtcdRoleNotGrantedError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on granting a non-existent role', async () => {
|
||||||
|
const user = await client.user('connor').create('password');
|
||||||
|
await expectReject(user.addRole('wut'), EtcdRoleNotFoundError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws on deleting a non-existent user', async () => {
|
||||||
|
await expectReject(client.user('connor').delete(), EtcdUserNotFoundError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('round trips roles', async () => {
|
||||||
|
const user = await client.user('connor').create('password');
|
||||||
|
await user.addRole(fooRole);
|
||||||
|
expect(await user.roles()).to.containSubset([{ name: 'foo' }]);
|
||||||
|
await user.removeRole(fooRole);
|
||||||
|
expect(await user.roles()).to.have.lengthOf(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('password auth', () => {
|
||||||
|
// beforeEach(async () => {
|
||||||
|
// await wipeAll(client.getUsers());
|
||||||
|
// await wipeAll(client.getRoles());
|
||||||
|
|
||||||
|
// // We need to set up a root user and root role first, otherwise etcd
|
||||||
|
// // will yell at us.
|
||||||
|
// const rootUser = await client.user('root').create('password');
|
||||||
|
// rootUser.addRole('root');
|
||||||
|
|
||||||
|
// await client.user('connor').create('password');
|
||||||
|
|
||||||
|
// const normalRole = await client.role('all').create();
|
||||||
|
// await normalRole.grant({
|
||||||
|
// permission: 'readwrite',
|
||||||
|
// range: client.range({ prefix: 'f' }),
|
||||||
|
// });
|
||||||
|
// await normalRole.addUser('connor');
|
||||||
|
// await client.auth.authEnable();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// afterEach(async () => {
|
||||||
|
// const rootClient = new Etcd3(getOptions({
|
||||||
|
// auth: {
|
||||||
|
// username: 'root',
|
||||||
|
// password: 'password',
|
||||||
|
// },
|
||||||
|
// }));
|
||||||
|
|
||||||
|
// await rootClient.auth.authDisable();
|
||||||
|
// rootClient.close();
|
||||||
|
|
||||||
|
// await wipeAll(client.getUsers());
|
||||||
|
// await wipeAll(client.getRoles());
|
||||||
|
// });
|
||||||
|
|
||||||
|
it('allows authentication using the correct credentials', async () => {
|
||||||
|
const authedClient = new Etcd3(getOptions({
|
||||||
|
auth: {
|
||||||
|
username: 'root',
|
||||||
|
password: 'password',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
await authedClient.put('foo').value('bar');
|
||||||
|
authedClient.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when using incorrect credentials', async () => {
|
||||||
|
const authedClient = new Etcd3(getOptions({
|
||||||
|
auth: {
|
||||||
|
username: 'connor',
|
||||||
|
password: 'password',
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
await expectReject(
|
||||||
|
authedClient.put('foo').value('bar').exec(),
|
||||||
|
EtcdRoleNotFoundError,
|
||||||
|
);
|
||||||
|
|
||||||
|
authedClient.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -1,9 +1,16 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
|
||||||
import { ConnectionPool } from '../src/connection-pool';
|
import { ConnectionPool } from '../src/connection-pool';
|
||||||
import { GRPCConnectFailedError } from '../src/errors';
|
|
||||||
import { KVClient } from '../src/rpc';
|
import { KVClient } from '../src/rpc';
|
||||||
import { getHosts } from './util';
|
import { GRPCConnectFailedError, IOptions } from '../src';
|
||||||
|
import { getOptions, getHost } from './util';
|
||||||
|
|
||||||
|
function getOptionsWithBadHost(options: Partial<IOptions> = {}): IOptions {
|
||||||
|
return getOptions({
|
||||||
|
hosts: ['127.0.0.1:1', getHost()],
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('connection pool', () => {
|
describe('connection pool', () => {
|
||||||
const key = new Buffer('foo');
|
const key = new Buffer('foo');
|
||||||
|
@ -18,7 +25,7 @@ describe('connection pool', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls simple methods', async () => {
|
it('calls simple methods', async () => {
|
||||||
pool = new ConnectionPool({ hosts: getHosts() });
|
pool = new ConnectionPool(getOptions());
|
||||||
const kv = new KVClient(pool);
|
const kv = new KVClient(pool);
|
||||||
await kv.put({ key, value });
|
await kv.put({ key, value });
|
||||||
const res = await kv.range({ key });
|
const res = await kv.range({ key });
|
||||||
|
@ -28,7 +35,7 @@ describe('connection pool', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects hitting invalid hosts', () => {
|
it('rejects hitting invalid hosts', () => {
|
||||||
pool = new ConnectionPool({ hosts: ['127.0.0.1:1', getHosts()] });
|
pool = new ConnectionPool(getOptionsWithBadHost());
|
||||||
const kv = new KVClient(pool);
|
const kv = new KVClient(pool);
|
||||||
return kv.range({ key })
|
return kv.range({ key })
|
||||||
.then(() => { throw new Error('expected to reject'); })
|
.then(() => { throw new Error('expected to reject'); })
|
||||||
|
@ -36,10 +43,7 @@ describe('connection pool', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('retries when requested', async () => {
|
it('retries when requested', async () => {
|
||||||
pool = new ConnectionPool({
|
pool = new ConnectionPool(getOptionsWithBadHost({ retry: true }));
|
||||||
hosts: ['127.0.0.1:1', getHosts()],
|
|
||||||
retry: true,
|
|
||||||
});
|
|
||||||
const kv = new KVClient(pool);
|
const kv = new KVClient(pool);
|
||||||
expect((await kv.range({ key })).kvs).to.deep.equal([]);
|
expect((await kv.range({ key })).kvs).to.deep.equal([]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { forget, Memoize } from '../src/memoize';
|
||||||
|
|
||||||
|
class Foo {
|
||||||
|
public incr = 0;
|
||||||
|
|
||||||
|
@Memoize()
|
||||||
|
public get incrGetter(): number {
|
||||||
|
this.incr += 1;
|
||||||
|
return this.incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Memoize()
|
||||||
|
public basicMemoized(amount: number): number {
|
||||||
|
this.incr += amount;
|
||||||
|
return this.incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Memoize((value: number) => value % 2)
|
||||||
|
public byModulo(amount: number): number {
|
||||||
|
this.incr += amount;
|
||||||
|
return this.incr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public forgetBasic() {
|
||||||
|
forget(this, this.basicMemoized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('@Memoize', () => {
|
||||||
|
it('memoizes simple methods', () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
expect(foo.basicMemoized(1)).to.equal(1);
|
||||||
|
expect(foo.basicMemoized(1)).to.equal(1);
|
||||||
|
expect(foo.basicMemoized(2)).to.equal(3);
|
||||||
|
});
|
||||||
|
it('forgets memoized values', () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
expect(foo.basicMemoized(1)).to.equal(1);
|
||||||
|
foo.forgetBasic();
|
||||||
|
expect(foo.basicMemoized(1)).to.equal(2);
|
||||||
|
expect(foo.basicMemoized(1)).to.equal(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('memoizes with custom hashers', () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
expect(foo.incr).to.equal(0);
|
||||||
|
expect(foo.byModulo(1)).to.equal(1);
|
||||||
|
expect(foo.byModulo(2)).to.equal(3);
|
||||||
|
expect(foo.byModulo(3)).to.equal(1);
|
||||||
|
expect(foo.byModulo(4)).to.equal(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('memoizes getters', () => {
|
||||||
|
const foo = new Foo();
|
||||||
|
expect(foo.incrGetter).to.equal(1);
|
||||||
|
expect(foo.incrGetter).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws when attaching to a non-memoizable type', () => {
|
||||||
|
expect(() => {
|
||||||
|
class Bar {
|
||||||
|
@Memoize()
|
||||||
|
public set foo(_value: number) { /* noop */ }
|
||||||
|
}
|
||||||
|
return Bar;
|
||||||
|
}).to.throw(/Can only attach/);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { Range } from '../src/range';
|
||||||
|
|
||||||
|
describe('Range', () => {
|
||||||
|
describe('prefix', () => {
|
||||||
|
it('generates prefixes for an empty string', () => {
|
||||||
|
const range = Range.prefix(Buffer.from([]));
|
||||||
|
expect(range.start).to.deep.equal(Buffer.from([0]));
|
||||||
|
expect(range.end).to.deep.equal(Buffer.from([0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates prefixes for a "normal" string', () => {
|
||||||
|
const range = Range.prefix(Buffer.from([1, 2]));
|
||||||
|
expect(range.start).to.deep.equal(Buffer.from([1, 2]));
|
||||||
|
expect(range.end).to.deep.equal(Buffer.from([1, 3]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rolls on a high end-bit', () => {
|
||||||
|
const range = Range.prefix(Buffer.from([1, 255]));
|
||||||
|
expect(range.start).to.deep.equal(Buffer.from([1, 255]));
|
||||||
|
expect(range.end).to.deep.equal(Buffer.from([2]));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('aborts on all high bits', () => {
|
||||||
|
const range = Range.prefix(Buffer.from([255, 255]));
|
||||||
|
expect(range.start).to.deep.equal(Buffer.from([255, 255]));
|
||||||
|
expect(range.end).to.deep.equal(Buffer.from([0]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('comparisons', () => {
|
||||||
|
const prefix: Buffer[] = [];
|
||||||
|
for (let i = 0; i < 10; i += 1) {
|
||||||
|
prefix.push(Buffer.from([i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
it('compares ranges', () => {
|
||||||
|
const r = new Range(prefix[2], prefix[5]);
|
||||||
|
expect(r.compare(new Range(prefix[2], prefix[5]))).to.equal(0);
|
||||||
|
expect(r.compare(new Range(prefix[3], prefix[6]))).to.equal(0);
|
||||||
|
expect(r.compare(new Range(prefix[0], prefix[4]))).to.equal(0);
|
||||||
|
expect(r.compare(new Range(prefix[0], prefix[9]))).to.equal(0);
|
||||||
|
expect(r.compare(new Range(prefix[3], prefix[4]))).to.equal(0);
|
||||||
|
expect(r.compare(new Range(prefix[5], prefix[7]))).to.equal(-1);
|
||||||
|
expect(r.compare(new Range(prefix[0], prefix[1]))).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if a key is included', () => {
|
||||||
|
const r = new Range(prefix[2], prefix[5]);
|
||||||
|
expect(r.includes(prefix[1])).to.be.false;
|
||||||
|
expect(r.includes(prefix[2])).to.be.true;
|
||||||
|
expect(r.includes(prefix[5])).to.be.false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
20
test/util.ts
20
test/util.ts
|
@ -1,6 +1,22 @@
|
||||||
|
import { IOptions } from '../src';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const rootCertificate = fs.readFileSync(`${__dirname}/certs/certs/ca.crt`);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns etcd hosts to test against.
|
* Returns the host to test against.
|
||||||
*/
|
*/
|
||||||
export function getHosts(): string {
|
export function getHost(): string {
|
||||||
return process.env.ETCD_ADDR || '127.0.0.1:2379';
|
return process.env.ETCD_ADDR || '127.0.0.1:2379';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns etcd options to use for connections.
|
||||||
|
*/
|
||||||
|
export function getOptions(defaults: Partial<IOptions> = {}): IOptions {
|
||||||
|
return {
|
||||||
|
hosts: getHost(),
|
||||||
|
credentials: { rootCertificate },
|
||||||
|
...defaults,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
|
|
85
tslint.json
85
tslint.json
|
@ -4,30 +4,73 @@
|
||||||
"node_modules/tslint-microsoft-contrib"
|
"node_modules/tslint-microsoft-contrib"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"align": false,
|
// Basic
|
||||||
|
"import-blacklist": [
|
||||||
|
true,
|
||||||
|
"lodash",
|
||||||
|
"rxjs/Rx",
|
||||||
|
"rxjs"
|
||||||
|
],
|
||||||
|
"no-multiline-string": false,
|
||||||
|
"arrow-parens": [true, "ban-single-arg-parens"],
|
||||||
|
"max-file-line-count": [true, 600],
|
||||||
|
"jsdoc-format": true,
|
||||||
|
"cyclomatic-complexity": [true, 20],
|
||||||
|
"space-before-function-paren": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"anonymous": "never",
|
||||||
|
"named": "never",
|
||||||
|
"asyncArrow": "always",
|
||||||
|
"constructor": "never",
|
||||||
|
"method": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// Microsoft
|
||||||
|
"no-relative-imports": false,
|
||||||
|
"mocha-no-side-effect-code": false,
|
||||||
|
"missing-jsdoc": false,
|
||||||
"chai-vague-errors": false,
|
"chai-vague-errors": false,
|
||||||
"export-name": false,
|
"export-name": false,
|
||||||
"function-name": [true, {
|
|
||||||
"method-regex": "^[a-z][\\w\\d]+$",
|
|
||||||
"private-method-regex": "^[a-z][\\w\\d]+$",
|
|
||||||
"static-method-regex": "^[A-Z][\\w\\d]+$",
|
|
||||||
"function-regex": "^[a-z][\\w\\d]+$"
|
|
||||||
}],
|
|
||||||
"missing-jsdoc": false,
|
|
||||||
"mocha-no-side-effect-code": false,
|
|
||||||
"no-any": false,
|
|
||||||
"no-constructor-vars": false,
|
|
||||||
"no-empty-interfaces": false,
|
|
||||||
"no-invalid-this": false,
|
|
||||||
"no-multiline-string": false,
|
|
||||||
"no-require-imports": false,
|
|
||||||
"no-relative-imports": false,
|
|
||||||
"no-typeof-undefined": false,
|
|
||||||
"no-var-requires": false,
|
|
||||||
"insecure-random": false,
|
|
||||||
"no-reserved-keywords": false,
|
"no-reserved-keywords": false,
|
||||||
"no-backbone-get-set-outside-model": false,
|
"no-any": false,
|
||||||
|
"no-increment-decrement": false,
|
||||||
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
|
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
|
||||||
"underscore-consistent-invocation": false
|
"no-typeof-undefined": false,
|
||||||
|
"underscore-consistent-invocation": false,
|
||||||
|
"no-backbone-get-set-outside-model": false,
|
||||||
|
"function-name": [true, {
|
||||||
|
"method-regex": "^[a-z][\\w\\d]+$",
|
||||||
|
"private-method-regex": "^[a-z][\\w\\d]+$",
|
||||||
|
"static-method-regex": "^[a-z][\\w\\d]+$",
|
||||||
|
"function-regex": "^[a-z][\\w\\d]+$"
|
||||||
|
}],
|
||||||
|
"typedef-whitespace": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call-signature": "onespace",
|
||||||
|
"index-signature": "onespace",
|
||||||
|
"parameter": "onespace",
|
||||||
|
"property-declaration": "onespace",
|
||||||
|
"variable-declaration": "onespace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-stateless-class": false,
|
||||||
|
"insecure-random": false,
|
||||||
|
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"], // for unused params
|
||||||
|
|
||||||
|
"no-suspicious-comment": false, // For the duration of pre-release development these will be fine, rm and fix on before release.
|
||||||
|
"no-string-literal": false,
|
||||||
|
"no-string-throw": true,
|
||||||
|
"no-empty-line-after-opening-brace": true,
|
||||||
|
"no-function-expression": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче