├── lib ├── index.d.ts ├── index.js ├── catalog │ ├── connect.d.ts │ ├── node.d.ts │ ├── service.d.ts │ ├── connect.js │ ├── node.js │ └── service.js ├── status.d.ts ├── constants.js ├── watch.d.ts ├── errors.js ├── acl.d.ts ├── status.js ├── event.d.ts ├── transaction.d.ts ├── acl.js ├── transaction.js ├── agent │ ├── service.d.ts │ ├── check.d.ts │ ├── service.js │ └── check.js ├── acl │ ├── legacy.d.ts │ └── legacy.js ├── consul.d.ts ├── kv.d.ts ├── health.d.ts ├── agent.d.ts ├── query.d.ts ├── session.d.ts ├── event.js ├── consul.js ├── catalog.js ├── health.js ├── catalog.d.ts ├── session.js ├── agent.js ├── kv.js ├── watch.js └── query.js ├── .npmignore ├── test-d └── index.test-d.ts ├── .gitignore ├── .prettierignore ├── .jshintrc ├── test ├── acceptance │ ├── status.js │ ├── event.js │ ├── transaction.js │ ├── catalog.js │ ├── health.js │ ├── session.js │ ├── watch.js │ ├── helper.js │ ├── kv.js │ └── agent.js ├── helper.js ├── status.js ├── errors.js ├── event.js ├── consul.js ├── health.js ├── transaction.js ├── kv.js ├── session.js ├── acl.js ├── watch.js ├── catalog.js ├── query.js └── agent.js ├── .github └── workflows │ ├── pr.yml │ └── release.yml ├── LICENSE └── package.json /lib/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Consul } from "./consul"; 2 | 3 | export = Consul; 4 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const { Consul } = require("./consul"); 2 | 3 | module.exports = Consul; 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .env 2 | .jscs.json 3 | .jshintrc 4 | .npmignore 5 | .travis.yml 6 | config 7 | coverage 8 | example*.js 9 | test 10 | tmp 11 | -------------------------------------------------------------------------------- /test-d/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from "tsd"; 2 | 3 | import Consul from "../lib"; 4 | 5 | const consul = new Consul(); 6 | 7 | expectType(consul); 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pid 3 | *swp 4 | .env 5 | .idea 6 | .nyc_output 7 | .rock.yml 8 | config 9 | coverage 10 | example*.js 11 | node_modules 12 | package-lock.json 13 | tmp 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pid 3 | *swp 4 | .env 5 | .idea 6 | .nyc_output 7 | .rock.yml 8 | config 9 | coverage 10 | example*.js 11 | node_modules 12 | package-lock.json 13 | pnpm-lock.yaml 14 | tmp 15 | -------------------------------------------------------------------------------- /lib/catalog/connect.d.ts: -------------------------------------------------------------------------------- 1 | import { Consul } from "../consul"; 2 | import { NodesOptions, NodesResult } from "./service"; 3 | 4 | declare class CatalogConnect { 5 | constructor(consul: Consul); 6 | 7 | consul: Consul; 8 | 9 | nodes(options: NodesOptions): Promise; 10 | nodes(service: string): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /lib/status.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface LeaderOptions extends CommonOptions {} 4 | 5 | type LeaderResult = string; 6 | 7 | interface PeersOptions extends CommonOptions {} 8 | 9 | type PeersResult = string[]; 10 | 11 | declare class Status { 12 | constructor(consul: Consul); 13 | 14 | consul: Consul; 15 | 16 | leader(options?: LeaderOptions): Promise; 17 | 18 | peers(options?: PeersOptions): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | exports.DEFAULT_OPTIONS = [ 2 | "consistent", 3 | "dc", 4 | "partition", 5 | "stale", 6 | "timeout", 7 | "token", 8 | "wait", 9 | "wan", 10 | ]; 11 | 12 | exports.AGENT_STATUS = ["none", "alive", "leaving", "left", "failed"]; 13 | 14 | exports.CHECK_STATE = ["unknown", "passing", "warning", "critical"]; 15 | 16 | const du = (exports.DURATION_UNITS = { ns: 1 }); 17 | du.us = 1000 * du.ns; 18 | du.ms = 1000 * du.us; 19 | du.s = 1000 * du.ms; 20 | du.m = 60 * du.s; 21 | du.h = 60 * du.m; 22 | -------------------------------------------------------------------------------- /lib/watch.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "events"; 2 | import { CommonOptions, Consul } from "./consul"; 3 | 4 | interface WatchOptions extends CommonOptions { 5 | method: Function; 6 | options: Record; 7 | backoffFactor?: number; 8 | backoffMax?: number; 9 | maxAttempts?: number; 10 | } 11 | 12 | declare class Watch extends EventEmitter { 13 | constructor(consul: Consul, options: WatchOptions); 14 | 15 | consul: Consul; 16 | 17 | isRunning(): boolean; 18 | 19 | updateTime(): number; 20 | 21 | end(): void; 22 | } 23 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esversion": 11, 4 | "globals": { 5 | // Mocha 6 | "describe": false, 7 | "it": false, 8 | "before": false, 9 | "beforeEach": false, 10 | "after": false, 11 | "afterEach": false 12 | }, 13 | "camelcase": false, 14 | "eqeqeq": true, 15 | "eqnull": true, 16 | "indent": 2, 17 | "latedef": true, 18 | "quotmark": "double", 19 | "trailing": true, 20 | "undef": true, 21 | "unused": true, 22 | "maxlen": 100, 23 | "expr": true, 24 | "laxbreak": true, 25 | "laxcomma": true 26 | } 27 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /** 4 | * Create error 5 | */ 6 | function create(message) { 7 | const error = 8 | message instanceof Error 9 | ? message 10 | : new Error(message ? message : undefined); 11 | 12 | error.isConsul = true; 13 | 14 | return error; 15 | } 16 | 17 | /** 18 | * Create validation error 19 | */ 20 | function validation(message) { 21 | const error = create(message); 22 | 23 | error.isValidation = true; 24 | 25 | return error; 26 | } 27 | 28 | exports.Consul = create; 29 | exports.Validation = validation; 30 | -------------------------------------------------------------------------------- /lib/catalog/node.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "../consul"; 2 | 3 | interface ListOptions extends CommonOptions { 4 | dc?: string; 5 | near?: string; 6 | filter?: string; 7 | } 8 | 9 | type ListResult = any[]; 10 | 11 | interface ServicesOptions extends CommonOptions { 12 | node: string; 13 | dc?: string; 14 | filter?: string; 15 | ns?: string; 16 | } 17 | 18 | type ServicesResult = any; 19 | 20 | declare class CatalogNode { 21 | constructor(consul: Consul); 22 | 23 | consul: Consul; 24 | 25 | list(options?: ListOptions): Promise; 26 | list(dc: string): Promise; 27 | 28 | services(options: ServicesOptions): Promise; 29 | services(node: string): Promise; 30 | } 31 | -------------------------------------------------------------------------------- /test/acceptance/status.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | helper.describe("Status", function () { 8 | before(async function () { 9 | await helper.before(this); 10 | }); 11 | 12 | after(async function () { 13 | await helper.after(this); 14 | }); 15 | 16 | describe("leader", function () { 17 | it("should return leader", async function () { 18 | const data = await this.c1.status.leader(); 19 | should(data).eql("127.0.0.1:8300"); 20 | }); 21 | }); 22 | 23 | describe("peers", function () { 24 | it("should return peers", async function () { 25 | const data = await this.c1.status.peers(); 26 | should(data).eql(["127.0.0.1:8300"]); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /lib/catalog/service.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "../consul"; 2 | 3 | interface ListOptions extends CommonOptions { 4 | dc?: string; 5 | ns?: string; 6 | filter?: string; 7 | } 8 | 9 | type ListResult = Record; 10 | 11 | interface NodesOptions extends CommonOptions { 12 | service: string; 13 | dc?: string; 14 | tag?: string; 15 | near?: string; 16 | filter?: string; 17 | ns?: string; 18 | } 19 | 20 | type NodesResult = any[]; 21 | 22 | declare class CatalogService { 23 | constructor(consul: Consul); 24 | 25 | consul: Consul; 26 | 27 | list(options?: ListOptions): Promise; 28 | list(dc: string): Promise; 29 | 30 | nodes(options: NodesOptions): Promise; 31 | nodes(service: string): Promise; 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | concurrency: 12 | group: ${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | env: 16 | CI: true 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | matrix: 24 | node-version: [18, 20] 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v4 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | 35 | - name: Setup pnpm 36 | uses: pnpm/action-setup@v4 37 | with: 38 | version: 9 39 | 40 | - name: Install 41 | run: pnpm install 42 | 43 | - name: Types 44 | run: pnpm types 45 | 46 | - name: Test 47 | run: pnpm test 48 | -------------------------------------------------------------------------------- /lib/acl.d.ts: -------------------------------------------------------------------------------- 1 | import { AclLegacy } from "./acl/legacy"; 2 | import { CommonOptions, Consul } from "./consul"; 3 | 4 | interface BootstrapOptions extends CommonOptions { 5 | bootstrapsecret?: string; 6 | } 7 | 8 | type BootstrapResult = any; 9 | 10 | interface ReplicationOptions extends CommonOptions { 11 | dc?: string; 12 | } 13 | 14 | interface ReplicationResult { 15 | Enabled: boolean; 16 | Running: boolean; 17 | SourceDatacenter: string; 18 | ReplicatedType: "policies" | "tokens"; 19 | ReplicatedIndex: number; 20 | ReplicatedTokenIndex: number; 21 | LastSuccess: string; 22 | LastError: string; 23 | LastErrorMessage: string; 24 | } 25 | 26 | declare class Acl { 27 | constructor(consul: Consul); 28 | 29 | consul: Consul; 30 | legacy: AclLegacy; 31 | 32 | static Legacy: typeof AclLegacy; 33 | 34 | bootstrap(options?: BootstrapOptions): Promise; 35 | 36 | replication(options?: ReplicationOptions): Promise; 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | npm: 12 | name: npm 13 | runs-on: ubuntu-latest 14 | 15 | environment: 16 | name: npm 17 | url: https://www.npmjs.com/package/consul 18 | 19 | permissions: 20 | id-token: write 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Setup Node 27 | uses: actions/setup-node@v4 28 | with: 29 | node-version: 20 30 | registry-url: https://registry.npmjs.org 31 | 32 | - name: Setup pnpm 33 | uses: pnpm/action-setup@v4 34 | with: 35 | version: 9 36 | 37 | - name: Install 38 | run: pnpm install 39 | 40 | - name: Publish 41 | run: pnpm publish --access public --no-git-checks 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | -------------------------------------------------------------------------------- /lib/catalog/connect.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class CatalogConnect { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Lists the nodes in a given Connect-capable service 11 | */ 12 | async nodes(opts) { 13 | if (typeof opts === "string") { 14 | opts = { service: opts }; 15 | } 16 | 17 | opts = utils.normalizeKeys(opts); 18 | opts = utils.defaults(opts, this.consul._defaults); 19 | 20 | const req = { 21 | name: "catalog.connect.nodes", 22 | path: "/catalog/connect/{service}", 23 | params: { service: opts.service }, 24 | query: {}, 25 | }; 26 | 27 | if (!opts.service) { 28 | throw this.consul._err(errors.Validation("service required"), req); 29 | } 30 | 31 | utils.options(req, opts); 32 | 33 | return await this.consul._get(req, utils.body); 34 | } 35 | } 36 | 37 | exports.CatalogConnect = CatalogConnect; 38 | -------------------------------------------------------------------------------- /lib/status.js: -------------------------------------------------------------------------------- 1 | const utils = require("./utils"); 2 | 3 | class Status { 4 | constructor(consul) { 5 | this.consul = consul; 6 | } 7 | 8 | /** 9 | * Returns the current Raft leader. 10 | */ 11 | async leader(opts) { 12 | opts = utils.normalizeKeys(opts); 13 | opts = utils.defaults(opts, this.consul._defaults); 14 | 15 | const req = { 16 | name: "status.leader", 17 | path: "/status/leader", 18 | }; 19 | 20 | utils.options(req, opts); 21 | 22 | return await this.consul._get(req, utils.body); 23 | } 24 | 25 | /** 26 | * Returns the current Raft peer set 27 | */ 28 | async peers(opts) { 29 | opts = utils.normalizeKeys(opts); 30 | opts = utils.defaults(opts, this.consul._defaults); 31 | 32 | const req = { 33 | name: "status.peers", 34 | path: "/status/peers", 35 | }; 36 | 37 | utils.options(req, opts); 38 | 39 | return await this.consul._get(req, utils.body); 40 | } 41 | } 42 | 43 | exports.Status = Status; 44 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("should"); 4 | 5 | const nock = require("nock"); 6 | const sinon = require("sinon"); 7 | 8 | const Consul = require("../lib"); 9 | 10 | function setup(scope) { 11 | if (scope._setup) return; 12 | scope._setup = true; 13 | 14 | beforeEach.call(scope, function () { 15 | this.sinon = sinon.createSandbox(); 16 | 17 | nock.disableNetConnect(); 18 | 19 | Object.defineProperty(this, "consul", { 20 | configurable: true, 21 | enumerable: true, 22 | get: function () { 23 | return new Consul(); 24 | }, 25 | }); 26 | 27 | Object.defineProperty(this, "nock", { 28 | configurable: true, 29 | enumerable: true, 30 | get: function () { 31 | return nock("http://127.0.0.1:8500"); 32 | }, 33 | }); 34 | }); 35 | 36 | afterEach.call(scope, function () { 37 | this.sinon.restore(); 38 | 39 | nock.cleanAll(); 40 | }); 41 | } 42 | 43 | exports.consul = (opts) => new Consul(opts); 44 | exports.setup = setup; 45 | -------------------------------------------------------------------------------- /lib/event.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface FireOptions extends CommonOptions { 4 | name: string; 5 | payload: string | Buffer; 6 | dc?: string; 7 | node?: string; 8 | service?: string; 9 | tag?: string; 10 | } 11 | 12 | interface FireResult { 13 | ID: string; 14 | Name: string; 15 | Payload: string; 16 | NodeFilter: string; 17 | ServiceFilter: string; 18 | TagFilter: string; 19 | Version: number; 20 | LTime: number; 21 | } 22 | 23 | interface ListOptions extends CommonOptions { 24 | name?: string; 25 | node?: string; 26 | service?: string; 27 | tag?: string; 28 | } 29 | 30 | type ListResult = FireResult[]; 31 | 32 | declare class Event { 33 | constructor(consul: Consul); 34 | 35 | consul: Consul; 36 | 37 | fire(name: string): Promise; 38 | fire(name: string, payload: string | Buffer): Promise; 39 | fire(options: FireOptions): Promise; 40 | 41 | list(options?: ListOptions): Promise; 42 | list(name: string): Promise; 43 | } 44 | -------------------------------------------------------------------------------- /lib/transaction.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface KVOption { 4 | verb: string; 5 | key: string; 6 | value?: string; 7 | flags?: number; 8 | index?: number; 9 | session?: string; 10 | namespace?: string; 11 | } 12 | 13 | interface NodeOption { 14 | verb: string; 15 | node: any; 16 | } 17 | 18 | interface ServiceOption { 19 | verb: string; 20 | node: string; 21 | service: any; 22 | } 23 | 24 | interface CheckOption { 25 | verb: string; 26 | check: any; 27 | } 28 | 29 | type Operation = KVOption | NodeOption | ServiceOption | CheckOption; 30 | 31 | interface CreateOptions extends CommonOptions { 32 | operations: Operation[]; 33 | } 34 | 35 | interface CreateResult { 36 | Results?: Record<"KV" | "Node" | "Service" | "Check", any>[]; 37 | Errors?: any[]; 38 | } 39 | 40 | declare class Transaction { 41 | constructor(consul: Consul); 42 | 43 | consul: Consul; 44 | 45 | create(options: CreateOptions): Promise; 46 | create( 47 | operations: Operation[], 48 | options: CreateOptions, 49 | ): Promise; 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Silas Sewell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /lib/acl.js: -------------------------------------------------------------------------------- 1 | const AclLegacy = require("./acl/legacy").AclLegacy; 2 | const utils = require("./utils"); 3 | 4 | class Acl { 5 | constructor(consul) { 6 | this.consul = consul; 7 | this.legacy = new Acl.Legacy(consul); 8 | } 9 | 10 | /** 11 | * Creates one-time management token if not configured 12 | */ 13 | async bootstrap(opts) { 14 | opts = utils.normalizeKeys(opts); 15 | opts = utils.defaults(opts, this.consul._defaults); 16 | 17 | const req = { 18 | name: "acl.bootstrap", 19 | path: "/acl/bootstrap", 20 | type: "json", 21 | }; 22 | 23 | utils.options(req, opts); 24 | 25 | return await this.consul._put(req, utils.body); 26 | } 27 | 28 | /** 29 | * Check ACL replication 30 | */ 31 | async replication(opts) { 32 | opts = utils.normalizeKeys(opts); 33 | opts = utils.defaults(opts, this.consul._defaults); 34 | 35 | const req = { 36 | name: "acl.replication", 37 | path: "/acl/replication", 38 | query: {}, 39 | }; 40 | 41 | utils.options(req, opts); 42 | 43 | return await this.consul._get(req, utils.body); 44 | } 45 | } 46 | 47 | Acl.Legacy = AclLegacy; 48 | 49 | exports.Acl = Acl; 50 | -------------------------------------------------------------------------------- /test/status.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Status", function () { 8 | helper.setup(this); 9 | 10 | describe("leader", function () { 11 | it("should work", async function () { 12 | this.nock.get("/v1/status/leader").reply(200, { ok: true }); 13 | 14 | const data = await this.consul.status.leader({}); 15 | should(data).eql({ ok: true }); 16 | }); 17 | 18 | it("should work with no arguments", async function () { 19 | this.nock.get("/v1/status/leader").reply(200, { ok: true }); 20 | 21 | const data = await this.consul.status.leader(); 22 | should(data).eql({ ok: true }); 23 | }); 24 | }); 25 | 26 | describe("peers", function () { 27 | it("should work", async function () { 28 | this.nock.get("/v1/status/peers").reply(200, [{ ok: true }]); 29 | 30 | const data = await this.consul.status.peers({}); 31 | should(data).eql([{ ok: true }]); 32 | }); 33 | 34 | it("should work with no arguments", async function () { 35 | this.nock.get("/v1/status/peers").reply(200, [{ ok: true }]); 36 | 37 | const data = await this.consul.status.peers(); 38 | should(data).eql([{ ok: true }]); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /lib/catalog/node.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class CatalogNode { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Lists nodes in a given DC 11 | */ 12 | async list(opts) { 13 | if (typeof opts === "string") { 14 | opts = { dc: opts }; 15 | } 16 | 17 | opts = utils.normalizeKeys(opts); 18 | opts = utils.defaults(opts, this.consul._defaults); 19 | 20 | const req = { 21 | name: "catalog.node.list", 22 | path: "/catalog/nodes", 23 | }; 24 | 25 | utils.options(req, opts); 26 | 27 | return await this.consul._get(req, utils.body); 28 | } 29 | 30 | /** 31 | * Lists the services provided by a node 32 | */ 33 | async services(opts) { 34 | if (typeof opts === "string") { 35 | opts = { node: opts }; 36 | } 37 | 38 | opts = utils.normalizeKeys(opts); 39 | opts = utils.defaults(opts, this.consul._defaults); 40 | 41 | const req = { 42 | name: "catalog.node.services", 43 | path: "/catalog/node/{node}", 44 | params: { node: opts.node }, 45 | }; 46 | 47 | if (!opts.node) { 48 | throw this.consul._err(errors.Validation("node required"), req); 49 | } 50 | 51 | utils.options(req, opts); 52 | 53 | return await this.consul._get(req, utils.body); 54 | } 55 | } 56 | 57 | exports.CatalogNode = CatalogNode; 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consul", 3 | "version": "2.0.1", 4 | "description": "Consul client", 5 | "main": "./lib", 6 | "types": "./lib/index.d.ts", 7 | "files": [ 8 | "./lib" 9 | ], 10 | "dependencies": { 11 | "papi": "^1.1.0" 12 | }, 13 | "devDependencies": { 14 | "@types/node": "^22.7.6", 15 | "async": "^3.2.0", 16 | "debug": "^4.3.1", 17 | "jshint": "^2.5.5", 18 | "mocha": "^10.7.3", 19 | "nock": "^13.0.7", 20 | "nyc": "^17.1.0", 21 | "prettier": "^3.3.3", 22 | "should": "^13.2.1", 23 | "sinon": "^19.0.2", 24 | "temp": "^0.9.4", 25 | "tsd": "^0.31.2", 26 | "uuid": "^10.0.0" 27 | }, 28 | "scripts": { 29 | "format": "prettier -w .", 30 | "test": "jshint lib test && prettier -c . && nyc mocha -- --recursive --check-leaks && nyc check-coverage --statements 100 --functions 100 --branches 100 --lines 100", 31 | "types": "tsd", 32 | "acceptance": "ACCEPTANCE=true nyc mocha -- test/acceptance --recursive --check-leaks --timeout 30000", 33 | "acceptanceSetupMacOS": "sudo ifconfig lo0 alias 127.0.0.2 up && sudo ifconfig lo0 alias 127.0.0.3 up" 34 | }, 35 | "keywords": [ 36 | "consul" 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/silas/node-consul.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/silas/node-consul/issues" 44 | }, 45 | "author": "Silas Sewell ", 46 | "license": "MIT" 47 | } 48 | -------------------------------------------------------------------------------- /lib/catalog/service.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class CatalogService { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Lists services in a given DC 11 | */ 12 | 13 | async list(opts) { 14 | if (typeof opts === "string") { 15 | opts = { dc: opts }; 16 | } 17 | 18 | opts = utils.normalizeKeys(opts); 19 | opts = utils.defaults(opts, this.consul._defaults); 20 | 21 | const req = { 22 | name: "catalog.service.list", 23 | path: "/catalog/services", 24 | query: {}, 25 | }; 26 | 27 | utils.options(req, opts); 28 | 29 | return await this.consul._get(req, utils.body); 30 | } 31 | 32 | /** 33 | * Lists the nodes in a given service 34 | */ 35 | async nodes(opts) { 36 | if (typeof opts === "string") { 37 | opts = { service: opts }; 38 | } 39 | 40 | opts = utils.normalizeKeys(opts); 41 | opts = utils.defaults(opts, this.consul._defaults); 42 | 43 | const req = { 44 | name: "catalog.service.nodes", 45 | path: "/catalog/service/{service}", 46 | params: { service: opts.service }, 47 | query: {}, 48 | }; 49 | 50 | if (!opts.service) { 51 | throw this.consul._err(errors.Validation("service required"), req); 52 | } 53 | if (opts.tag) req.query.tag = opts.tag; 54 | 55 | utils.options(req, opts); 56 | 57 | return await this.consul._get(req, utils.body); 58 | } 59 | } 60 | 61 | exports.CatalogService = CatalogService; 62 | -------------------------------------------------------------------------------- /lib/transaction.js: -------------------------------------------------------------------------------- 1 | const errors = require("./errors"); 2 | const utils = require("./utils"); 3 | 4 | class Transaction { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Create 11 | */ 12 | async create(operations) { 13 | let opts; 14 | switch (arguments.length) { 15 | case 2: 16 | // create(operations, opts) 17 | opts = utils.clone(arguments[1]); 18 | opts.operations = operations; 19 | break; 20 | case 1: 21 | // create(operations) 22 | opts = { operations }; 23 | break; 24 | default: 25 | throw this.consul._err( 26 | errors.Validation( 27 | "a list of operations are required as first arguments", 28 | ), 29 | { name: "Transaction.create" }, 30 | ); 31 | } 32 | 33 | opts = utils.normalizeKeys(opts); 34 | opts = utils.defaults(opts, this.consul._defaults); 35 | 36 | const req = { 37 | name: "Transaction.create", 38 | path: "/txn", 39 | params: {}, 40 | query: {}, 41 | type: "json", 42 | body: opts.operations, 43 | }; 44 | 45 | if (!(Array.isArray(opts.operations) && opts.operations.length > 0)) { 46 | throw this.consul._err( 47 | errors.Validation("operations must be an array with at least one item"), 48 | req, 49 | ); 50 | } 51 | 52 | utils.options(req, opts); 53 | 54 | return await this.consul._put(req, utils.body); 55 | } 56 | } 57 | 58 | exports.Transaction = Transaction; 59 | -------------------------------------------------------------------------------- /test/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const errors = require("../lib/errors"); 6 | 7 | const helper = require("./helper"); 8 | 9 | describe("errors", function () { 10 | helper.setup(this); 11 | 12 | describe("Consul", function () { 13 | it("should work", function () { 14 | const msg = "test message"; 15 | 16 | let err = errors.Consul(msg); 17 | should(err).have.property("isConsul", true); 18 | should(err).have.property("message", msg); 19 | 20 | const test = new Error(msg); 21 | test.isTest = true; 22 | 23 | err = errors.Consul(test); 24 | should(err).have.property("message", msg); 25 | should(err).have.property("isConsul", true); 26 | should(err).have.property("isTest", true); 27 | 28 | err = errors.Consul(null); 29 | should(err).not.have.property("message", undefined); 30 | should(err).have.property("isConsul", true); 31 | 32 | err = errors.Consul(""); 33 | should(err).not.have.property("message", undefined); 34 | should(err).have.property("isConsul", true); 35 | }); 36 | }); 37 | 38 | describe("Validation", function () { 39 | it("should work", function () { 40 | const msg = "test"; 41 | const err = errors.Validation(msg); 42 | 43 | should(err).have.property("isConsul", true); 44 | should(err).have.property("isValidation", true); 45 | should(err).have.property("message", msg); 46 | 47 | should(errors.Validation).not.have.property("message"); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /lib/agent/service.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "../consul"; 2 | import { CheckOptions } from "./check"; 3 | 4 | interface ListOptions extends CommonOptions { 5 | filter?: string; 6 | } 7 | 8 | type ListResult = Record; 9 | 10 | interface RegisterConnect { 11 | native?: boolean; 12 | proxy?: any; 13 | sidecarservice: Record; 14 | } 15 | 16 | interface RegisterOptions extends CommonOptions { 17 | name: string; 18 | id?: string; 19 | tags?: string[]; 20 | address?: string; 21 | taggedaddresses?: Record; 22 | meta?: Record; 23 | namespace?: string; 24 | port?: number; 25 | kind?: string; 26 | proxy?: any; 27 | connect?: RegisterConnect; 28 | check?: CheckOptions; 29 | checks?: CheckOptions[]; 30 | } 31 | 32 | type RegisterResult = any; 33 | 34 | interface DeregisterOptions extends CommonOptions { 35 | id: string; 36 | } 37 | 38 | type DeregisterResult = any; 39 | 40 | interface MaintenanceOptions extends CommonOptions { 41 | id: string; 42 | enable: boolean; 43 | reason?: string; 44 | ns?: string; 45 | } 46 | 47 | type MaintenanceResult = any; 48 | 49 | declare class AgentService { 50 | constructor(consul: Consul); 51 | 52 | consul: Consul; 53 | 54 | list(options?: ListOptions): Promise; 55 | 56 | register(options: RegisterOptions): Promise; 57 | register(name: string): Promise; 58 | 59 | deregister(options: DeregisterOptions): Promise; 60 | deregister(id: string): Promise; 61 | 62 | maintenance(options: MaintenanceOptions): Promise; 63 | } 64 | -------------------------------------------------------------------------------- /lib/acl/legacy.d.ts: -------------------------------------------------------------------------------- 1 | import { Consul } from "../consul"; 2 | 3 | interface CreateOptions { 4 | name?: string; 5 | type?: "client" | "management"; 6 | rules?: string; 7 | } 8 | 9 | type CreateResult = any; 10 | 11 | interface UpdateOptions { 12 | id: string; 13 | name?: string; 14 | type?: "client" | "management"; 15 | rules?: string; 16 | } 17 | 18 | type UpdateResult = any; 19 | 20 | interface DestroyOptions { 21 | id: string; 22 | } 23 | 24 | type DestroyResult = any; 25 | 26 | interface InfoOptions { 27 | id: string; 28 | } 29 | 30 | interface InfoResult { 31 | CreateIndex: number; 32 | ModifyIndex: number; 33 | ID: string; 34 | Name: string; 35 | Type: "client" | "management"; 36 | Rules: string; 37 | } 38 | 39 | type GetOptions = InfoOptions; 40 | 41 | type GetResult = InfoResult; 42 | 43 | interface CloneOptions { 44 | id: string; 45 | } 46 | 47 | type CloneResult = any; 48 | 49 | interface ListOptions {} 50 | 51 | type ListResult = GetResult[]; 52 | 53 | declare class AclLegacy { 54 | constructor(consul: Consul); 55 | 56 | consul: Consul; 57 | 58 | create(options?: CreateOptions): Promise; 59 | 60 | update(options: UpdateOptions): Promise; 61 | 62 | destroy(options: DestroyOptions): Promise; 63 | destroy(id: string): Promise; 64 | 65 | info(options: InfoOptions): Promise; 66 | info(id: string): Promise; 67 | 68 | get(options: GetOptions): Promise; 69 | get(id: string): Promise; 70 | 71 | clone(options: CloneOptions): Promise; 72 | clone(id: string): Promise; 73 | 74 | list(options?: ListOptions): Promise; 75 | } 76 | -------------------------------------------------------------------------------- /lib/consul.d.ts: -------------------------------------------------------------------------------- 1 | import { Agent as httpAgent } from "http"; 2 | import { Agent as httpsAgent } from "https"; 3 | import { Acl } from "./acl"; 4 | import { Agent } from "./agent"; 5 | import { Catalog } from "./catalog"; 6 | import { Event } from "./event"; 7 | import { Health } from "./health"; 8 | import { Kv } from "./kv"; 9 | import { Query } from "./query"; 10 | import { Session } from "./session"; 11 | import { Status } from "./status"; 12 | import { Transaction } from "./transaction"; 13 | import { Watch, WatchOptions } from "./watch"; 14 | 15 | export interface CommonOptions { 16 | token?: string; 17 | } 18 | 19 | interface DefaultOptions extends CommonOptions { 20 | dc?: string; 21 | partition?: string; 22 | wan?: boolean; 23 | consistent?: boolean; 24 | stale?: boolean; 25 | index?: string; 26 | wait?: string; 27 | near?: string; 28 | filter?: string; 29 | } 30 | 31 | interface ConsulOptions { 32 | host?: string; 33 | port?: number; 34 | secure?: boolean; 35 | defaults?: DefaultOptions; 36 | agent?: httpAgent | httpsAgent; 37 | } 38 | 39 | declare class Consul { 40 | constructor(options?: ConsulOptions); 41 | 42 | acl: Acl; 43 | agent: Agent; 44 | catalog: Catalog; 45 | event: Event; 46 | health: Health; 47 | kv: Kv; 48 | query: Query; 49 | session: Session; 50 | status: Status; 51 | transaction: Transaction; 52 | 53 | static Acl: typeof Acl; 54 | static Agent: typeof Agent; 55 | static Catalog: typeof Catalog; 56 | static Event: typeof Event; 57 | static Health: typeof Health; 58 | static Kv: typeof Kv; 59 | static Query: typeof Query; 60 | static Session: typeof Session; 61 | static Status: typeof Status; 62 | static Transaction: typeof Transaction; 63 | static Watch: typeof Watch; 64 | 65 | destroy(): void; 66 | 67 | watch(options: WatchOptions): Watch; 68 | } 69 | 70 | export { Consul }; 71 | -------------------------------------------------------------------------------- /lib/kv.d.ts: -------------------------------------------------------------------------------- 1 | import { Consul } from "./consul"; 2 | 3 | interface GetOptions { 4 | key?: string; 5 | dc?: string; 6 | raw?: boolean; 7 | keys?: boolean; 8 | separator?: string; 9 | ns?: string; 10 | } 11 | 12 | interface GetOptionsRecurse extends GetOptions { 13 | recurse: boolean; 14 | } 15 | 16 | interface GetItem { 17 | CreateIndex: number; 18 | ModifyIndex: number; 19 | LockIndex: number; 20 | Key: string; 21 | Flags: number; 22 | Value: string | null; 23 | } 24 | 25 | type GetResult = GetItem | null; 26 | 27 | type GetResultRecurse = GetItem[] | GetItem | null; 28 | 29 | interface KeysOptions extends GetOptions { 30 | recurse?: boolean; 31 | } 32 | 33 | type KeysResult = string[]; 34 | 35 | interface SetOptions { 36 | key?: string; 37 | value: string | Buffer; 38 | dc?: string; 39 | flags?: number; 40 | cas?: number; 41 | acquire?: string; 42 | release?: string; 43 | ns?: string; 44 | } 45 | 46 | type SetResult = boolean; 47 | 48 | interface DelOptions { 49 | key?: string; 50 | dc?: string; 51 | recurse?: boolean; 52 | cas?: number; 53 | ns?: string; 54 | } 55 | 56 | type DelResult = boolean; 57 | 58 | declare class Kv { 59 | constructor(consul: Consul); 60 | 61 | consul: Consul; 62 | 63 | get(options?: GetOptions): Promise; 64 | get(key: string): Promise; 65 | get(options?: GetOptionsRecurse): Promise; 66 | 67 | keys(options?: KeysOptions): Promise; 68 | keys(key: string): Promise; 69 | 70 | set(options: SetOptions): Promise; 71 | set(key: string, value: string | Buffer): Promise; 72 | set( 73 | key: string, 74 | value: string | Buffer, 75 | options: SetOptions, 76 | ): Promise; 77 | 78 | del(options: DelOptions): Promise; 79 | del(key: string): Promise; 80 | } 81 | -------------------------------------------------------------------------------- /lib/health.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface NodeOptions extends CommonOptions { 4 | name: string; 5 | dc?: string; 6 | filter?: string; 7 | ns?: string; 8 | } 9 | 10 | interface Node { 11 | ID: string; 12 | Node: string; 13 | CheckID: string; 14 | Name: string; 15 | Status: "passing" | "warning" | "critical"; 16 | Notes: string; 17 | Output: string; 18 | ServiceID: string; 19 | ServiceName: string; 20 | ServiceTags: string[]; 21 | Namespace: string; 22 | } 23 | 24 | type NodeResult = Node[]; 25 | 26 | interface ChecksOptions extends CommonOptions { 27 | service: string; 28 | dc?: string; 29 | near?: string; 30 | filter?: string; 31 | ns?: string; 32 | } 33 | 34 | type ChecksResult = Node[]; 35 | 36 | interface ServiceOptions extends CommonOptions { 37 | service: string; 38 | dc?: string; 39 | near?: string; 40 | tag?: string; 41 | passing?: boolean; 42 | filter?: string; 43 | peer?: string; 44 | ns?: string; 45 | } 46 | 47 | type ServiceResult = any[]; 48 | 49 | interface StateOptions extends CommonOptions { 50 | state: "any" | "passing" | "warning" | "critical"; 51 | dc?: string; 52 | near?: string; 53 | filter?: string; 54 | ns?: string; 55 | } 56 | 57 | type StateResult = Node[]; 58 | 59 | declare class Health { 60 | constructor(consul: Consul); 61 | 62 | consul: Consul; 63 | 64 | node(options: NodeOptions): Promise; 65 | node(name: string): Promise; 66 | 67 | checks(options: ChecksOptions): Promise; 68 | checks(service: string): Promise; 69 | 70 | service(options: ServiceOptions): Promise; 71 | service(service: string): Promise; 72 | 73 | state(options: StateOptions): Promise; 74 | state( 75 | state: "any" | "passing" | "warning" | "critical", 76 | ): Promise; 77 | } 78 | -------------------------------------------------------------------------------- /test/acceptance/event.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | const uuid = require("uuid"); 5 | 6 | const helper = require("./helper"); 7 | 8 | helper.describe("Event", function () { 9 | before(async function () { 10 | await helper.before(this); 11 | }); 12 | 13 | after(async function () { 14 | await helper.after(this); 15 | }); 16 | 17 | beforeEach(async function () { 18 | this.name = "event-" + uuid.v4(); 19 | this.payload = JSON.stringify({ hello: "world" }); 20 | this.bufferPayload = Buffer.from(this.payload); 21 | 22 | this.event = await this.c1.event.fire(this.name, this.payload); 23 | }); 24 | 25 | describe("fire", function () { 26 | it("should fire an event", async function () { 27 | const event = await this.c1.event.fire("test"); 28 | should(event).have.keys( 29 | "ID", 30 | "Name", 31 | "Payload", 32 | "NodeFilter", 33 | "ServiceFilter", 34 | "TagFilter", 35 | "Version", 36 | "LTime", 37 | ); 38 | should(event.Name).equal("test"); 39 | }); 40 | }); 41 | 42 | describe("list", function () { 43 | it("should return events", async function () { 44 | const events = await this.c1.event.list(); 45 | should(events).not.be.empty(); 46 | }); 47 | 48 | it("should return event with given name", async function () { 49 | const events = await this.c1.event.list(this.name); 50 | should(events).not.be.empty(); 51 | should(events.length).equal(1); 52 | should(events[0].ID).equal(this.event.ID); 53 | should(events[0].Name).equal(this.name); 54 | should(events[0].Payload).equal(this.payload); 55 | }); 56 | 57 | it("should return payload as buffer", async function () { 58 | const events = await this.c1.event.list({ 59 | name: this.name, 60 | buffer: true, 61 | }); 62 | should(events).not.be.empty(); 63 | should(events[0].Payload).eql(this.bufferPayload); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /lib/agent.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | import { 3 | AgentCheck, 4 | ListOptions as CheckListOptions, 5 | ListResult as CheckListResult, 6 | } from "./agent/check"; 7 | import { 8 | AgentService, 9 | ListOptions as ServiceListOptions, 10 | ListResult as ServiceListResult, 11 | } from "./agent/service"; 12 | 13 | interface MembersOptions extends CommonOptions { 14 | wan?: boolean; 15 | segment?: string; 16 | } 17 | 18 | type MembersResult = any[]; 19 | 20 | interface ReloadOptions extends CommonOptions {} 21 | 22 | type ReloadResult = any; 23 | 24 | interface SelfOptions extends CommonOptions {} 25 | 26 | type SelfResult = any; 27 | 28 | interface MaintenanceOptions extends CommonOptions { 29 | enable: boolean; 30 | reason?: string; 31 | } 32 | 33 | type MaintenanceResult = any; 34 | 35 | interface JoinOptions extends CommonOptions { 36 | address: string; 37 | wan?: boolean; 38 | } 39 | 40 | type JoinResult = any; 41 | 42 | interface ForceLeaveOptions extends CommonOptions { 43 | node: string; 44 | prune?: boolean; 45 | wan?: boolean; 46 | } 47 | 48 | type ForceLeaveResult = any; 49 | 50 | declare class Agent { 51 | constructor(consul: Consul); 52 | 53 | consul: Consul; 54 | check: AgentCheck; 55 | service: AgentService; 56 | 57 | static Check: typeof AgentCheck; 58 | static Service: typeof AgentService; 59 | 60 | checks(options?: CheckListOptions): Promise; 61 | 62 | services(options?: ServiceListOptions): Promise; 63 | 64 | members(options?: MembersOptions): Promise; 65 | 66 | reload(options?: ReloadOptions): Promise; 67 | 68 | self(options?: SelfOptions): Promise; 69 | 70 | maintenance(options: MaintenanceOptions): Promise; 71 | maintenance(enable: boolean): Promise; 72 | 73 | join(options: JoinOptions): Promise; 74 | join(address: string): Promise; 75 | 76 | forceLeave(options: ForceLeaveOptions | string): Promise; 77 | } 78 | -------------------------------------------------------------------------------- /lib/query.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface ListOptions extends CommonOptions {} 4 | 5 | type ListResult = any[]; 6 | 7 | interface CreateServiceOptions { 8 | service: string; 9 | namespace?: string; 10 | failover?: { 11 | nearestn?: number; 12 | datacenters?: string[]; 13 | targets?: { peer?: string; datacenter?: string }[]; 14 | }; 15 | ignorecheckids?: string[]; 16 | onlypassing?: boolean; 17 | near?: string; 18 | } 19 | 20 | interface CreateDnsOptions { 21 | ttl?: string; 22 | } 23 | 24 | interface CreateOptions extends CommonOptions { 25 | name?: string; 26 | session?: string; 27 | token?: string; 28 | service: CreateServiceOptions; 29 | tags?: string[]; 30 | nodemeta?: Record; 31 | servicemeta?: Record; 32 | connect?: boolean; 33 | dns?: CreateDnsOptions; 34 | } 35 | 36 | interface CreateResult { 37 | ID: string; 38 | } 39 | 40 | interface GetOptions extends CommonOptions { 41 | query: string; 42 | } 43 | 44 | type GetResult = any; 45 | 46 | interface UpdateOptions extends CreateOptions { 47 | query: string; 48 | } 49 | 50 | type UpdateResult = any; 51 | 52 | interface ExecuteOptions extends CommonOptions { 53 | query: string; 54 | } 55 | 56 | type ExecuteResult = any; 57 | 58 | interface ExplainOptions extends CommonOptions { 59 | query: string; 60 | } 61 | 62 | type ExplainResult = any; 63 | 64 | declare class Query { 65 | constructor(consul: Consul); 66 | 67 | consul: Consul; 68 | 69 | list(options?: ListOptions): Promise; 70 | 71 | create(options: CreateOptions): Promise; 72 | create(service: string): Promise; 73 | 74 | get(options: GetOptions): Promise; 75 | get(query: string): Promise; 76 | 77 | update(options: UpdateOptions): Promise; 78 | 79 | execute(options: ExecuteOptions): Promise; 80 | execute(query: string): Promise; 81 | 82 | explain(options: ExplainOptions): Promise; 83 | explain(query: string): Promise; 84 | } 85 | -------------------------------------------------------------------------------- /lib/session.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | 3 | interface CreateOptions extends CommonOptions { 4 | dc?: string; 5 | lockdelay?: string; 6 | node?: string; 7 | name?: string; 8 | checks?: string[]; 9 | nodechecks?: string[]; 10 | servicechecks?: { id: string; namespace?: string }[]; 11 | behavior?: "release" | "delete"; 12 | ttl?: string; 13 | } 14 | 15 | interface CreateResult { 16 | ID: string; 17 | } 18 | 19 | interface DestroyOptions extends CommonOptions { 20 | id: string; 21 | dc?: string; 22 | ns?: string; 23 | } 24 | 25 | type DestroyResult = boolean; 26 | 27 | interface InfoOptions extends CommonOptions { 28 | id: string; 29 | dc?: string; 30 | } 31 | 32 | interface InfoResult { 33 | ID: string; 34 | Name: string; 35 | Node: string; 36 | LockDelay: number; 37 | Behavior: "release" | "delete"; 38 | TTL: string; 39 | NodeChecks: string[] | null; 40 | ServiceChecks: string[] | null; 41 | CreateIndex: number; 42 | ModifyIndex: number; 43 | } 44 | 45 | type GetOptions = InfoOptions; 46 | 47 | type GetResult = InfoResult; 48 | 49 | interface NodeOptions extends CommonOptions { 50 | node: string; 51 | dc?: string; 52 | } 53 | 54 | type NodeResult = InfoResult[]; 55 | 56 | interface ListOptions extends CommonOptions { 57 | dc?: string; 58 | } 59 | 60 | type ListResult = InfoResult[]; 61 | 62 | interface RenewOptions extends CommonOptions { 63 | id: string; 64 | dc?: string; 65 | } 66 | 67 | type RenewResult = InfoResult[]; 68 | 69 | declare class Session { 70 | constructor(consul: Consul); 71 | 72 | consul: Consul; 73 | 74 | create(options?: CreateOptions): Promise; 75 | 76 | destroy(options: DestroyOptions): Promise; 77 | destroy(id: string): Promise; 78 | 79 | info(options: InfoOptions): Promise; 80 | info(id: string): Promise; 81 | 82 | get(options: GetOptions): Promise; 83 | get(id: string): Promise; 84 | 85 | node(options: NodeOptions): Promise; 86 | node(node: string): Promise; 87 | 88 | list(options?: ListOptions): Promise; 89 | 90 | renew(options: RenewOptions): Promise; 91 | renew(id: string): Promise; 92 | } 93 | -------------------------------------------------------------------------------- /test/acceptance/transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | helper.describe("Transaction", function () { 8 | before(async function () { 9 | await helper.before(this); 10 | }); 11 | 12 | after(async function () { 13 | await helper.after(this); 14 | }); 15 | 16 | beforeEach(async function () { 17 | this.key = "hello"; 18 | this.value = "world"; 19 | 20 | await this.c1.kv.del({ recurse: true }); 21 | 22 | const ok = await this.c1.kv.set(this.key, this.value); 23 | if (!ok) throw new Error("not setup"); 24 | }); 25 | 26 | describe("create", function () { 27 | it("should create two kv pairs", async function () { 28 | const key1 = "key1"; 29 | const value1 = "value1"; 30 | const key2 = "key2"; 31 | const value2 = "value2"; 32 | 33 | const response = await this.c1.transaction.create([ 34 | { 35 | KV: { 36 | Verb: "set", 37 | Key: key1, 38 | Value: Buffer.from(value1).toString("base64"), 39 | }, 40 | }, 41 | { 42 | KV: { 43 | Verb: "set", 44 | Key: key2, 45 | Value: Buffer.from(value2).toString("base64"), 46 | }, 47 | }, 48 | ]); 49 | 50 | should(response).have.property("Results"); 51 | should(response.Results).be.Array(); 52 | 53 | const results = response.Results; 54 | should(results).have.length(2); 55 | should(results[0]).have.property("KV"); 56 | should(results[1]).have.property("KV"); 57 | should(results[0].KV).have.keys( 58 | "CreateIndex", 59 | "ModifyIndex", 60 | "LockIndex", 61 | "Key", 62 | "Flags", 63 | ); 64 | should(results[1].KV).have.keys( 65 | "CreateIndex", 66 | "ModifyIndex", 67 | "LockIndex", 68 | "Key", 69 | "Flags", 70 | ); 71 | 72 | const data1 = await this.c1.kv.get(key1); 73 | should(data1).have.property("Value"); 74 | should(data1.Value).eql(value1); 75 | 76 | const data2 = await this.c1.kv.get(key2); 77 | should(data2).have.property("Value"); 78 | should(data2.Value).eql(value2); 79 | }); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /lib/event.js: -------------------------------------------------------------------------------- 1 | const errors = require("./errors"); 2 | const utils = require("./utils"); 3 | 4 | class Event { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Fires a new user event 11 | */ 12 | async fire(opts) { 13 | let options; 14 | if (arguments.length === 2) { 15 | options = { 16 | name: arguments[0], 17 | payload: arguments[1], 18 | }; 19 | } else if (typeof opts === "string") { 20 | options = { name: opts }; 21 | } else { 22 | options = opts; 23 | } 24 | 25 | options = utils.normalizeKeys(options); 26 | options = utils.defaults(options, this.consul._defaults); 27 | 28 | const req = { 29 | name: "event.fire", 30 | path: "/event/fire/{name}", 31 | params: { name: options.name }, 32 | query: {}, 33 | }; 34 | 35 | if (!options.name) { 36 | throw this.consul._err(errors.Validation("name required"), req); 37 | } 38 | 39 | let buffer; 40 | 41 | if (options.hasOwnProperty("payload")) { 42 | buffer = Buffer.isBuffer(options.payload); 43 | req.body = buffer ? options.payload : Buffer.from(options.payload); 44 | } 45 | if (options.node) req.query.node = options.node; 46 | if (options.service) req.query.service = options.service; 47 | if (options.tag) req.query.tag = options.tag; 48 | 49 | utils.options(req, options); 50 | 51 | return await this.consul._put(req, utils.body).then((data) => { 52 | if (data.hasOwnProperty("Payload")) { 53 | data.Payload = utils.decode(data.Payload, { buffer: buffer }); 54 | } 55 | return data; 56 | }); 57 | } 58 | 59 | /** 60 | * Lists the most recent events an agent has seen 61 | */ 62 | async list(opts) { 63 | if (typeof opts === "string") { 64 | opts = { name: opts }; 65 | } 66 | 67 | opts = utils.normalizeKeys(opts); 68 | opts = utils.defaults(opts, this.consul._defaults); 69 | 70 | const req = { 71 | name: "event.list", 72 | path: "/event/list", 73 | query: {}, 74 | }; 75 | 76 | if (opts.name) req.query.name = opts.name; 77 | 78 | utils.options(req, opts); 79 | 80 | return await this.consul._get(req, utils.body).then((data) => { 81 | data.forEach((item) => { 82 | if (item.hasOwnProperty("Payload")) { 83 | item.Payload = utils.decode(item.Payload, opts); 84 | } 85 | }); 86 | return data; 87 | }); 88 | } 89 | } 90 | 91 | exports.Event = Event; 92 | -------------------------------------------------------------------------------- /lib/consul.js: -------------------------------------------------------------------------------- 1 | const papi = require("papi"); 2 | 3 | const Acl = require("./acl").Acl; 4 | const Agent = require("./agent").Agent; 5 | const Catalog = require("./catalog").Catalog; 6 | const Event = require("./event").Event; 7 | const Health = require("./health").Health; 8 | const Kv = require("./kv").Kv; 9 | const Query = require("./query").Query; 10 | const Session = require("./session").Session; 11 | const Status = require("./status").Status; 12 | const Watch = require("./watch").Watch; 13 | const Transaction = require("./transaction").Transaction; 14 | const utils = require("./utils"); 15 | 16 | class Consul extends papi.Client { 17 | constructor(opts) { 18 | opts = utils.defaults({}, opts); 19 | 20 | if (!opts.baseUrl) { 21 | opts.baseUrl = 22 | (opts.secure ? "https:" : "http:") + 23 | "//" + 24 | (opts.host || "127.0.0.1") + 25 | ":" + 26 | (opts.port || 8500) + 27 | "/v1"; 28 | } 29 | opts.name = "consul"; 30 | opts.type = "json"; 31 | 32 | let agent; 33 | if (!opts.agent) { 34 | agent = utils.getAgent(opts.baseUrl); 35 | if (agent) { 36 | opts.agent = agent; 37 | } 38 | } 39 | 40 | let defaults; 41 | if (opts.defaults) { 42 | defaults = utils.defaultCommonOptions(opts.defaults); 43 | } 44 | delete opts.defaults; 45 | 46 | super(opts); 47 | 48 | if (defaults) this._defaults = defaults; 49 | 50 | this.acl = new Consul.Acl(this); 51 | this.agent = new Consul.Agent(this); 52 | this.catalog = new Consul.Catalog(this); 53 | this.event = new Consul.Event(this); 54 | this.health = new Consul.Health(this); 55 | this.kv = new Consul.Kv(this); 56 | this.query = new Consul.Query(this); 57 | this.session = new Consul.Session(this); 58 | this.status = new Consul.Status(this); 59 | this.transaction = new Consul.Transaction(this); 60 | } 61 | 62 | destroy() { 63 | if (this._opts.agent && this._opts.agent.destroy) { 64 | this._opts.agent.destroy(); 65 | } 66 | } 67 | 68 | watch(opts) { 69 | return new Consul.Watch(this, opts); 70 | } 71 | 72 | static parseQueryMeta(res) { 73 | return utils.parseQueryMeta(res); 74 | } 75 | } 76 | 77 | Consul.Acl = Acl; 78 | Consul.Agent = Agent; 79 | Consul.Catalog = Catalog; 80 | Consul.Event = Event; 81 | Consul.Health = Health; 82 | Consul.Kv = Kv; 83 | Consul.Query = Query; 84 | Consul.Session = Session; 85 | Consul.Status = Status; 86 | Consul.Transaction = Transaction; 87 | Consul.Watch = Watch; 88 | 89 | exports.Consul = Consul; 90 | -------------------------------------------------------------------------------- /test/event.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Event", function () { 8 | helper.setup(this); 9 | 10 | describe("fire", function () { 11 | it("should work", async function () { 12 | this.nock 13 | .put("/v1/event/fire/name?node=node1&service=service1&tag=tag1", "test") 14 | .reply(200, { ok: true, Payload: "dGVzdA==" }); 15 | 16 | const data = await this.consul.event.fire({ 17 | name: "name", 18 | payload: "test", 19 | node: "node1", 20 | service: "service1", 21 | tag: "tag1", 22 | }); 23 | should(data).eql({ ok: true, Payload: "test" }); 24 | }); 25 | 26 | it("should work with two arguments", async function () { 27 | this.nock.put("/v1/event/fire/name", "test").reply(200, { ok: true }); 28 | 29 | const data = await this.consul.event.fire("name", Buffer.from("test")); 30 | should(data).eql({ ok: true }); 31 | }); 32 | 33 | it("should work with one argument", async function () { 34 | this.nock.put("/v1/event/fire/name").reply(500); 35 | 36 | try { 37 | await this.consul.event.fire("name"); 38 | should.ok(false); 39 | } catch (err) { 40 | should(err).have.property("message", "internal server error"); 41 | } 42 | }); 43 | 44 | it("should require name", async function () { 45 | try { 46 | await this.consul.event.fire({}); 47 | should.ok(false); 48 | } catch (err) { 49 | should(err).have.property( 50 | "message", 51 | "consul: event.fire: name required", 52 | ); 53 | } 54 | }); 55 | }); 56 | 57 | describe("list", function () { 58 | it("should work", async function () { 59 | this.nock 60 | .get("/v1/event/list?name=name1") 61 | .reply(200, [{ ok: true, Payload: "dGVzdA==" }, { ok: true }]); 62 | 63 | const data = await this.consul.event.list({ name: "name1" }); 64 | should(data).eql([{ ok: true, Payload: "test" }, { ok: true }]); 65 | }); 66 | 67 | it("should work with one argument", async function () { 68 | this.nock.get("/v1/event/list?name=name1").reply(200, []); 69 | 70 | await this.consul.event.list("name1"); 71 | }); 72 | 73 | it("should work with no arguments", async function () { 74 | this.nock.get("/v1/event/list").reply(500); 75 | 76 | try { 77 | await this.consul.event.list(); 78 | should.ok(false); 79 | } catch (err) { 80 | should(err).have.property("message", "internal server error"); 81 | } 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /lib/agent/check.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "../consul"; 2 | 3 | interface ListOptions extends CommonOptions { 4 | filter?: string; 5 | ns?: string; 6 | } 7 | 8 | interface Check { 9 | Node: string; 10 | CheckID: string; 11 | Name: string; 12 | Status: "passing" | "warning" | "critical"; 13 | Notes: string; 14 | Output: string; 15 | ServiceID: string; 16 | ServiceName: string; 17 | ServiceTags: string[]; 18 | Interval: string; 19 | Timeout: string; 20 | Type: string; 21 | ExposedPort: number; 22 | Definition: any; 23 | Namespace: string; 24 | CreateIndex: number; 25 | ModifyIndex: number; 26 | } 27 | 28 | type ListResult = Record; 29 | 30 | export interface CheckOptions { 31 | name: string; 32 | checkid?: string; 33 | serviceid?: string; 34 | http?: string; 35 | body?: string; 36 | header?: Record; 37 | disableredirects?: boolean; 38 | h2ping?: string; 39 | h2pingusetls?: boolean; 40 | tlsskipverify?: boolean; 41 | tcp?: string; 42 | udp?: string; 43 | args?: string[]; 44 | script?: string; 45 | dockercontainerid?: string; 46 | grpc?: string; 47 | grpcusetls?: boolean; 48 | shell?: string; 49 | timeout: string; 50 | interval?: string; 51 | ttl?: string; 52 | aliasnode?: string; 53 | aliasservice?: string; 54 | notes?: string; 55 | status?: string; 56 | deregistercriticalserviceafter?: string; 57 | failuresbeforewarning?: number; 58 | successbeforepassing?: number; 59 | failuresbeforecritical?: number; 60 | } 61 | 62 | interface RegisterOptions extends CheckOptions, CommonOptions {} 63 | 64 | type RegisterResult = any; 65 | 66 | interface DeregisterOptions extends CommonOptions { 67 | id: string; 68 | } 69 | 70 | type DeregisterResult = any; 71 | 72 | interface PassOptions extends CommonOptions { 73 | id: string; 74 | note?: string; 75 | } 76 | 77 | type PassResult = any; 78 | 79 | interface WarnOptions extends CommonOptions { 80 | id: string; 81 | note?: string; 82 | } 83 | 84 | type WarnResult = any; 85 | 86 | interface FailOptions extends CommonOptions { 87 | id: string; 88 | note?: string; 89 | } 90 | 91 | type FailResult = any; 92 | 93 | declare class AgentCheck { 94 | constructor(consul: Consul); 95 | 96 | consul: Consul; 97 | 98 | list(options?: ListOptions): Promise; 99 | 100 | register(options: RegisterOptions): Promise; 101 | 102 | deregister(options: DeregisterOptions): Promise; 103 | deregister(id: string): Promise; 104 | 105 | pass(options: PassOptions): Promise; 106 | pass(id: string): Promise; 107 | 108 | warn(options: WarnOptions): Promise; 109 | warn(id: string): Promise; 110 | 111 | fail(options: FailOptions): Promise; 112 | fail(id: string): Promise; 113 | } 114 | -------------------------------------------------------------------------------- /lib/catalog.js: -------------------------------------------------------------------------------- 1 | const CatalogConnect = require("./catalog/connect").CatalogConnect; 2 | const CatalogNode = require("./catalog/node").CatalogNode; 3 | const CatalogService = require("./catalog/service").CatalogService; 4 | const utils = require("./utils"); 5 | const errors = require("./errors"); 6 | 7 | class Catalog { 8 | constructor(consul) { 9 | this.consul = consul; 10 | 11 | this.connect = new Catalog.Connect(consul); 12 | this.node = new Catalog.Node(consul); 13 | this.service = new Catalog.Service(consul); 14 | } 15 | 16 | /** 17 | * Lists known datacenters 18 | */ 19 | async datacenters(opts) { 20 | opts = utils.normalizeKeys(opts); 21 | opts = utils.defaults(opts, this.consul._defaults); 22 | 23 | const req = { 24 | name: "catalog.datacenters", 25 | path: "/catalog/datacenters", 26 | }; 27 | 28 | utils.options(req, opts); 29 | 30 | return await this.consul._get(req, utils.body); 31 | } 32 | 33 | /** 34 | * Lists nodes in a given DC 35 | */ 36 | nodes(...args) { 37 | return this.node.list(...args); 38 | } 39 | 40 | /** 41 | * Registers or updates entries in the catalog 42 | */ 43 | async register(opts) { 44 | if (typeof opts === "string") { 45 | opts = { node: opts, address: "127.0.0.1" }; 46 | } 47 | 48 | opts = utils.normalizeKeys(opts); 49 | opts = utils.defaults(opts, this.consul._defaults); 50 | 51 | const req = { 52 | name: "catalog.register", 53 | path: "/catalog/register", 54 | type: "json", 55 | body: {}, 56 | }; 57 | 58 | if (!opts.node || !opts.address) { 59 | throw this.consul._err( 60 | errors.Validation("node and address required"), 61 | req, 62 | ); 63 | } 64 | 65 | req.body = utils.createCatalogRegistration(opts); 66 | 67 | utils.options(req, opts); 68 | 69 | return await this.consul._put(req, utils.empty); 70 | } 71 | 72 | /** 73 | * Deregister entries in the catalog 74 | */ 75 | async deregister(opts) { 76 | if (typeof opts === "string") { 77 | opts = { node: opts }; 78 | } 79 | 80 | opts = utils.normalizeKeys(opts); 81 | opts = utils.defaults(opts, this.consul._defaults); 82 | 83 | const req = { 84 | name: "catalog.deregister", 85 | path: "/catalog/deregister", 86 | type: "json", 87 | body: {}, 88 | }; 89 | 90 | if (!opts.node) { 91 | throw this.consul._err(errors.Validation("node required"), req); 92 | } 93 | 94 | req.body = utils.createCatalogDeregistration(opts); 95 | 96 | utils.options(req, opts); 97 | 98 | return await this.consul._put(req, utils.empty); 99 | } 100 | 101 | /** 102 | * Lists services in a given DC 103 | */ 104 | services(...args) { 105 | return this.service.list(...args); 106 | } 107 | } 108 | 109 | Catalog.Connect = CatalogConnect; 110 | Catalog.Node = CatalogNode; 111 | Catalog.Service = CatalogService; 112 | 113 | exports.Catalog = Catalog; 114 | -------------------------------------------------------------------------------- /lib/agent/service.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class AgentService { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Returns the services local agent is managing 11 | */ 12 | async list(opts) { 13 | opts = utils.normalizeKeys(opts); 14 | opts = utils.defaults(opts, this.consul._defaults); 15 | 16 | const req = { 17 | name: "agent.service.list", 18 | path: "/agent/services", 19 | }; 20 | 21 | utils.options(req, opts); 22 | 23 | return await this.consul._get(req, utils.body); 24 | } 25 | 26 | /** 27 | * Registers a new local service 28 | */ 29 | async register(opts) { 30 | if (typeof opts === "string") { 31 | opts = { name: opts }; 32 | } 33 | 34 | opts = utils.normalizeKeys(opts); 35 | opts = utils.defaults(opts, this.consul._defaults); 36 | 37 | const req = { 38 | name: "agent.service.register", 39 | path: "/agent/service/register", 40 | type: "json", 41 | body: {}, 42 | }; 43 | 44 | if (!opts.name) { 45 | throw this.consul._err(errors.Validation("name required"), req); 46 | } 47 | 48 | try { 49 | req.body = utils.createService(opts); 50 | } catch (err) { 51 | throw this.consul._err(errors.Validation(err.message), req); 52 | } 53 | 54 | utils.options(req, opts); 55 | 56 | return await this.consul._put(req, utils.empty); 57 | } 58 | 59 | /** 60 | * Deregister a local service 61 | */ 62 | async deregister(opts) { 63 | if (typeof opts === "string") { 64 | opts = { id: opts }; 65 | } 66 | 67 | opts = utils.normalizeKeys(opts); 68 | opts = utils.defaults(opts, this.consul._defaults); 69 | 70 | const req = { 71 | name: "agent.service.deregister", 72 | path: "/agent/service/deregister/{id}", 73 | params: { id: opts.id }, 74 | }; 75 | 76 | if (!opts.id) { 77 | throw this.consul._err(errors.Validation("id required"), req); 78 | } 79 | 80 | utils.options(req, opts); 81 | 82 | return await this.consul._put(req, utils.empty); 83 | } 84 | 85 | /** 86 | * Manages node maintenance mode 87 | */ 88 | async maintenance(opts) { 89 | opts = utils.normalizeKeys(opts); 90 | opts = utils.defaults(opts, this.consul._defaults); 91 | 92 | const req = { 93 | name: "agent.service.maintenance", 94 | path: "/agent/service/maintenance/{id}", 95 | params: { id: opts.id }, 96 | query: { enable: opts.enable }, 97 | }; 98 | 99 | if (!opts.id) { 100 | throw this.consul._err(errors.Validation("id required"), req); 101 | } 102 | if (typeof opts.enable !== "boolean") { 103 | throw this.consul._err(errors.Validation("enable required"), req); 104 | } 105 | if (opts.reason) req.query.reason = opts.reason; 106 | 107 | utils.options(req, opts); 108 | 109 | return await this.consul._put(req, utils.empty); 110 | } 111 | } 112 | 113 | exports.AgentService = AgentService; 114 | -------------------------------------------------------------------------------- /test/acceptance/catalog.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async_ = require("async"); 4 | const should = require("should"); 5 | const uuid = require("uuid"); 6 | 7 | const helper = require("./helper"); 8 | 9 | helper.describe("Catalog", function () { 10 | before(async function () { 11 | await helper.before(this); 12 | 13 | this.service = { 14 | name: "service-" + uuid.v4(), 15 | tag: "tag-" + uuid.v4(), 16 | }; 17 | 18 | await this.c1.agent.service.register({ 19 | name: this.service.name, 20 | tags: [this.service.tag], 21 | }); 22 | 23 | await async_.retry({ times: 100, interval: 100 }, async () => { 24 | const data = await this.c1.catalog.services(); 25 | 26 | if (!data || !data.hasOwnProperty(this.service.name)) { 27 | throw new Error("Service not created: " + this.service.name); 28 | } 29 | }); 30 | }); 31 | 32 | after(async function () { 33 | await helper.after(this); 34 | }); 35 | 36 | describe("datacenters", function () { 37 | it("should return all known datacenters", async function () { 38 | const data = await this.c1.catalog.datacenters(); 39 | should(data).eql(["dc1"]); 40 | }); 41 | }); 42 | 43 | describe("node", function () { 44 | describe("list", function () { 45 | it("should return all nodes in the current dc", async function () { 46 | const data = await this.c1.catalog.node.list(); 47 | should(data).match([{ Node: "node1", Address: "127.0.0.1" }]); 48 | }); 49 | 50 | it("should return all nodes in specified dc", async function () { 51 | const data = await this.c1.catalog.nodes("dc1"); 52 | should(data).match([{ Node: "node1", Address: "127.0.0.1" }]); 53 | }); 54 | }); 55 | 56 | describe("services", function () { 57 | it("should return all services for a given node", async function () { 58 | const data = await this.c1.catalog.node.services("node1"); 59 | 60 | should.exist(data); 61 | should.exist(data.Services); 62 | should.exist(data.Services[this.service.name]); 63 | should(data.Services[this.service.name]).have.properties( 64 | "ID", 65 | "Service", 66 | "Tags", 67 | ); 68 | should(data.Services[this.service.name].Service).eql(this.service.name); 69 | should(data.Services[this.service.name].Tags).eql([this.service.tag]); 70 | }); 71 | }); 72 | }); 73 | 74 | describe("service", function () { 75 | describe("list", function () { 76 | it("should return all services in the current dc", async function () { 77 | const data = await this.c1.catalog.service.list(); 78 | const services = { consul: [] }; 79 | services[this.service.name] = [this.service.tag]; 80 | should(data).eql(services); 81 | }); 82 | }); 83 | 84 | describe("nodes", function () { 85 | it("should return all nodes for a given service", async function () { 86 | const data = await this.c1.catalog.service.nodes(this.service.name); 87 | should(data).be.instanceof(Array); 88 | 89 | const nodes = data.map((n) => { 90 | return n.Node; 91 | }); 92 | should(nodes).eql(["node1"]); 93 | }); 94 | }); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /lib/health.js: -------------------------------------------------------------------------------- 1 | const constants = require("./constants"); 2 | const errors = require("./errors"); 3 | const utils = require("./utils"); 4 | 5 | class Health { 6 | constructor(consul) { 7 | this.consul = consul; 8 | } 9 | 10 | /** 11 | * Returns the health info of a node 12 | */ 13 | async node(opts) { 14 | if (typeof opts === "string") { 15 | opts = { node: opts }; 16 | } 17 | 18 | opts = utils.normalizeKeys(opts); 19 | opts = utils.defaults(opts, this.consul._defaults); 20 | 21 | const req = { 22 | name: "health.node", 23 | path: "/health/node/{node}", 24 | params: { node: opts.node }, 25 | }; 26 | 27 | if (!opts.node) { 28 | throw this.consul._err(errors.Validation("node required"), req); 29 | } 30 | 31 | utils.options(req, opts); 32 | 33 | return await this.consul._get(req, utils.body); 34 | } 35 | 36 | /** 37 | * Returns the checks of a service 38 | */ 39 | async checks(opts) { 40 | if (typeof opts === "string") { 41 | opts = { service: opts }; 42 | } 43 | 44 | opts = utils.normalizeKeys(opts); 45 | opts = utils.defaults(opts, this.consul._defaults); 46 | 47 | const req = { 48 | name: "health.checks", 49 | path: "/health/checks/{service}", 50 | params: { service: opts.service }, 51 | }; 52 | 53 | if (!opts.service) { 54 | throw this.consul._err(errors.Validation("service required"), req); 55 | } 56 | 57 | utils.options(req, opts); 58 | 59 | return await this.consul._get(req, utils.body); 60 | } 61 | 62 | /** 63 | * Returns the nodes and health info of a service 64 | */ 65 | async service(opts) { 66 | if (typeof opts === "string") { 67 | opts = { service: opts }; 68 | } 69 | 70 | opts = utils.normalizeKeys(opts); 71 | opts = utils.defaults(opts, this.consul._defaults); 72 | 73 | const req = { 74 | name: "health.service", 75 | path: "/health/service/{service}", 76 | params: { service: opts.service }, 77 | query: {}, 78 | }; 79 | 80 | if (!opts.service) { 81 | throw this.consul._err(errors.Validation("service required"), req); 82 | } 83 | 84 | if (opts.tag) req.query.tag = opts.tag; 85 | if (opts.passing) req.query.passing = "true"; 86 | 87 | utils.options(req, opts); 88 | 89 | return await this.consul._get(req, utils.body); 90 | } 91 | 92 | /** 93 | * Returns the checks in a given state 94 | */ 95 | async state(opts) { 96 | if (typeof opts === "string") { 97 | opts = { state: opts }; 98 | } 99 | 100 | opts = utils.normalizeKeys(opts); 101 | opts = utils.defaults(opts, this.consul._defaults); 102 | 103 | const req = { 104 | name: "health.state", 105 | path: "/health/state/{state}", 106 | params: { state: opts.state }, 107 | }; 108 | 109 | if (!opts.state) { 110 | throw this.consul._err(errors.Validation("state required"), req); 111 | } 112 | 113 | if (opts.state !== "any" && constants.CHECK_STATE.indexOf(opts.state) < 0) { 114 | throw this.consul._err( 115 | errors.Validation("state invalid: " + opts.state), 116 | req, 117 | ); 118 | } 119 | 120 | utils.options(req, opts); 121 | 122 | return await this.consul._get(req, utils.body); 123 | } 124 | } 125 | 126 | exports.Health = Health; 127 | -------------------------------------------------------------------------------- /lib/catalog.d.ts: -------------------------------------------------------------------------------- 1 | import { CommonOptions, Consul } from "./consul"; 2 | import { CatalogConnect } from "./catalog/connect"; 3 | import { 4 | CatalogNode, 5 | ListOptions as NodeListOptions, 6 | ListResult as NodeListResult, 7 | } from "./catalog/node"; 8 | import { 9 | CatalogService, 10 | ListOptions as ServiceListOptions, 11 | ListResult as ServiceListResult, 12 | } from "./catalog/service"; 13 | 14 | interface DatacentersOptions extends CommonOptions {} 15 | 16 | type DatacentersResult = string[]; 17 | 18 | type NodesOptions = NodeListOptions; 19 | 20 | type NodesResult = NodeListResult; 21 | 22 | interface RegisterService { 23 | id?: string; 24 | service: string; 25 | address?: string; 26 | port?: number; 27 | tags?: string[]; 28 | meta?: Record; 29 | } 30 | 31 | interface RegisterCheckHealthDefinitionBase { 32 | intervalduration: string; 33 | timeoutduration?: string; 34 | deregistercriticalserviceafterduration?: string; 35 | } 36 | 37 | interface RegisterCheckHealthDefinitionHttp 38 | extends RegisterCheckHealthDefinitionBase { 39 | http: string; 40 | tlsskipverify?: boolean; 41 | tlsservername?: string; 42 | } 43 | 44 | interface RegisterCheckHealthDefinitionTcp 45 | extends RegisterCheckHealthDefinitionBase { 46 | tcp: string; 47 | } 48 | 49 | type RegisterCheckHealthDefinition = 50 | | RegisterCheckHealthDefinitionHttp 51 | | RegisterCheckHealthDefinitionTcp; 52 | 53 | interface RegisterCheck { 54 | node?: string; 55 | name?: string; 56 | checkid?: string; 57 | serviceid?: string; 58 | notes?: string; 59 | status?: "passing" | "warning" | "critical"; 60 | definition?: RegisterCheckHealthDefinition; 61 | } 62 | 63 | interface RegisterOptions extends CommonOptions { 64 | id?: string; 65 | node: string; 66 | address: string; 67 | datacenter?: string; 68 | taggedaddresses?: Record; 69 | nodemeta?: Record; 70 | service?: RegisterService; 71 | check?: RegisterCheck; 72 | checks?: RegisterCheck[]; 73 | skipnodeupdate?: boolean; 74 | namespace?: string; 75 | } 76 | 77 | type RegisterResult = any; 78 | 79 | interface DeregisterOptions extends CommonOptions { 80 | node: string; 81 | datacenter?: string; 82 | checkid?: string; 83 | serviceid?: string; 84 | namespace?: string; 85 | } 86 | 87 | type DeregisterResult = any; 88 | 89 | type ServicesOptions = ServiceListOptions; 90 | 91 | type ServicesResult = ServiceListResult; 92 | 93 | declare class Catalog { 94 | constructor(consul: Consul); 95 | 96 | consul: Consul; 97 | connect: CatalogConnect; 98 | node: CatalogNode; 99 | service: CatalogService; 100 | 101 | static Connect: typeof CatalogConnect; 102 | static Node: typeof CatalogNode; 103 | static Service: typeof CatalogService; 104 | 105 | datacenters(options?: DatacentersOptions): Promise; 106 | 107 | nodes(options: NodesOptions): Promise; 108 | nodes(service: string): Promise; 109 | 110 | register(options: RegisterOptions): Promise; 111 | register(node: string): Promise; 112 | 113 | deregister(options: DeregisterOptions): Promise; 114 | deregister(node: string): Promise; 115 | 116 | services(options?: ServicesOptions): Promise; 117 | services(dc: string): Promise; 118 | } 119 | -------------------------------------------------------------------------------- /test/acceptance/health.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async_ = require("async"); 4 | const should = require("should"); 5 | const uuid = require("uuid"); 6 | 7 | const helper = require("./helper"); 8 | 9 | helper.describe("Health", function () { 10 | before(async function () { 11 | this.service = "service-" + uuid.v4(); 12 | 13 | await helper.before(this); 14 | 15 | await this.c1.agent.service.register({ 16 | name: this.service, 17 | check: { ttl: "60s" }, 18 | }); 19 | 20 | await async_.retry({ times: 100, interval: 100 }, async () => { 21 | let data = await this.c1.health.node("node1"); 22 | if (data && Array.isArray(data)) { 23 | data = data.find((c) => c.ServiceName === this.service); 24 | } 25 | 26 | if (!data) throw new Error("Check not for service: " + this.service); 27 | }); 28 | }); 29 | 30 | after(async function () { 31 | await helper.after(this); 32 | }); 33 | 34 | describe("node", function () { 35 | it("should return checks for given node", async function () { 36 | const data = await this.c1.health.node("node1"); 37 | 38 | should(data).be.instanceof(Array); 39 | 40 | should.exist(data[0]); 41 | 42 | should(data[0]).have.properties("Node", "CheckID", "Status"); 43 | 44 | should(data[0].Node).eql("node1"); 45 | should(data[0].CheckID).eql("serfHealth"); 46 | should(data[0].Status).eql("passing"); 47 | }); 48 | }); 49 | 50 | describe("checks", function () { 51 | it("should return all checks for a given service", async function () { 52 | let data = await this.c1.health.checks(this.service); 53 | 54 | should(data).be.instanceof(Array); 55 | data = data.find((c) => c.ServiceName === this.service); 56 | 57 | should.exist(data); 58 | 59 | should(data).have.properties("Node", "CheckID"); 60 | 61 | should(data.CheckID).eql("service:" + this.service); 62 | }); 63 | }); 64 | 65 | describe("service", function () { 66 | it("should return health information for given service", async function () { 67 | const data = await this.c1.health.service(this.service); 68 | 69 | should.exist(data); 70 | 71 | should(data).be.instanceof(Array); 72 | should.exist(data[0]); 73 | 74 | should(data[0]).have.properties("Node", "Service", "Checks"); 75 | 76 | should(data[0].Node).match({ 77 | Node: "node1", 78 | Address: "127.0.0.1", 79 | }); 80 | 81 | should(data[0].Service).have.properties("ID", "Service", "Tags"); 82 | 83 | should(data[0].Service.ID).equal(this.service); 84 | should(data[0].Service.Service).equal(this.service); 85 | 86 | const checks = data[0].Checks.map((c) => c.CheckID).sort(); 87 | 88 | should(checks).eql(["serfHealth", "service:" + this.service]); 89 | }); 90 | }); 91 | 92 | describe("state", function () { 93 | it("should return checks with a given state", async function () { 94 | const data = await this.c1.health.state("critical"); 95 | should(data).be.instanceof(Array); 96 | should.exist(data[0]); 97 | 98 | should(data[0]).have.property("ServiceName"); 99 | should(data[0].ServiceName).eql(this.service); 100 | 101 | should(data.length).eql(1); 102 | }); 103 | 104 | it("should return all checks", async function () { 105 | const data = await this.c1.health.state("any"); 106 | should(data).be.instanceof(Array); 107 | should(data.length).eql(2); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/consul.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const http = require("http"); 4 | 5 | const should = require("should"); 6 | 7 | const consul = require("../lib"); 8 | 9 | const helper = require("./helper"); 10 | 11 | describe("Consul", function () { 12 | helper.setup(this); 13 | 14 | it("should work", function () { 15 | should(helper.consul()).not.have.property("_defaults"); 16 | 17 | should(helper.consul({ defaults: { foo: "bar" } })).not.have.property( 18 | "_defaults", 19 | ); 20 | 21 | should(helper.consul({ defaults: { token: "123" } })) 22 | .have.property("_defaults") 23 | .eql({ 24 | token: "123", 25 | }); 26 | 27 | should( 28 | helper.consul({ 29 | defaults: { token: "123", dc: "test", foo: "bar" }, 30 | }), 31 | ) 32 | .have.property("_defaults") 33 | .eql({ 34 | token: "123", 35 | dc: "test", 36 | }); 37 | 38 | should(helper.consul()._opts.baseUrl).eql({ 39 | protocol: "http:", 40 | port: "8500", 41 | hostname: "127.0.0.1", 42 | path: "/v1", 43 | }); 44 | 45 | should( 46 | helper.consul({ 47 | host: "127.0.0.2", 48 | port: "8501", 49 | secure: true, 50 | })._opts.baseUrl, 51 | ).eql({ 52 | protocol: "https:", 53 | port: "8501", 54 | hostname: "127.0.0.2", 55 | path: "/v1", 56 | }); 57 | 58 | should( 59 | helper.consul({ 60 | baseUrl: "https://user:pass@example.org:8502/proxy/v1", 61 | })._opts.baseUrl, 62 | ).eql({ 63 | protocol: "https:", 64 | auth: "user:pass", 65 | port: "8502", 66 | hostname: "example.org", 67 | path: "/proxy/v1", 68 | }); 69 | 70 | should(() => { 71 | helper.consul({ baseUrl: {} }); 72 | }).throwError(/baseUrl must be a string.*/); 73 | 74 | const agent = new http.Agent(); 75 | should( 76 | helper.consul({ 77 | agent, 78 | })._opts.agent, 79 | ).equal(agent); 80 | }); 81 | 82 | it("should not mutate options", function () { 83 | const opts = { test: "opts" }; 84 | const client = helper.consul(opts); 85 | 86 | should(client._opts).not.exactly(opts); 87 | should(client._opts).containEql({ test: "opts" }); 88 | client._opts.test = "fail"; 89 | 90 | should(opts).eql({ test: "opts" }); 91 | }); 92 | 93 | describe("destroy", function () { 94 | it("should work", function () { 95 | const client = helper.consul(); 96 | should(client._opts.agent).not.be.null(); 97 | 98 | client.destroy(); 99 | delete client._opts.agent.destroy; 100 | client.destroy(); 101 | delete client._opts.agent; 102 | client.destroy(); 103 | }); 104 | }); 105 | 106 | describe("parseQueryMeta", function () { 107 | it("should work", function () { 108 | should(consul.parseQueryMeta()).eql({}); 109 | should(consul.parseQueryMeta({})).eql({}); 110 | should(consul.parseQueryMeta({ headers: {} })).eql({}); 111 | should( 112 | consul.parseQueryMeta({ 113 | headers: { 114 | "x-consul-index": "5", 115 | "x-consul-lastcontact": "100", 116 | "x-consul-knownleader": "true", 117 | "x-consul-translate-addresses": "true", 118 | }, 119 | }), 120 | ).eql({ 121 | LastIndex: "5", 122 | LastContact: 100, 123 | KnownLeader: true, 124 | AddressTranslationEnabled: true, 125 | }); 126 | }); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /test/acceptance/session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | helper.describe("Session", function () { 8 | before(async function () { 9 | await helper.before(this); 10 | }); 11 | 12 | after(async function () { 13 | await helper.after(this); 14 | }); 15 | 16 | beforeEach(async function () { 17 | const session = await this.c1.session.create(); 18 | this.id = session.ID; 19 | }); 20 | 21 | afterEach(async function () { 22 | try { 23 | await this.c1.session.destroy(this.id); 24 | } catch (err) { 25 | // ignore 26 | } 27 | }); 28 | 29 | describe("create", function () { 30 | it("should create session", async function () { 31 | const session = await this.c1.session.create(); 32 | should(session).have.keys("ID"); 33 | }); 34 | }); 35 | 36 | describe("destroy", function () { 37 | it("should destroy session", async function () { 38 | await this.c1.session.destroy(this.id); 39 | 40 | const session = await this.c1.session.get(this.id); 41 | should.not.exist(session); 42 | }); 43 | }); 44 | 45 | describe("get", function () { 46 | it("should return session information", async function () { 47 | const session = await this.c1.session.get(this.id); 48 | 49 | should(session).have.properties( 50 | "CreateIndex", 51 | "ID", 52 | "Name", 53 | "Node", 54 | "NodeChecks", 55 | "LockDelay", 56 | "Behavior", 57 | "TTL", 58 | ); 59 | }); 60 | }); 61 | 62 | describe("node", function () { 63 | it("should return sessions for node", async function () { 64 | const sessions = await this.c1.session.node("node1"); 65 | 66 | should(sessions).be.an.instanceof(Array); 67 | should(sessions.length).be.above(0); 68 | 69 | for (const session of sessions) { 70 | should(session).have.properties( 71 | "CreateIndex", 72 | "ID", 73 | "Name", 74 | "Node", 75 | "NodeChecks", 76 | "LockDelay", 77 | "Behavior", 78 | "TTL", 79 | ); 80 | } 81 | }); 82 | 83 | it("should return an empty list when no node found", async function () { 84 | const sessions = await this.c1.session.node("node"); 85 | 86 | should(sessions).be.an.instanceof(Array); 87 | should(sessions.length).be.eql(0); 88 | }); 89 | }); 90 | 91 | describe("list", function () { 92 | it("should return all sessions", async function () { 93 | const sessions = await this.c1.session.list(); 94 | 95 | should(sessions).be.an.instanceof(Array); 96 | should(sessions.length).be.above(0); 97 | 98 | for (const session of sessions) { 99 | should(session).have.properties( 100 | "CreateIndex", 101 | "ID", 102 | "Name", 103 | "Node", 104 | "NodeChecks", 105 | "LockDelay", 106 | "Behavior", 107 | "TTL", 108 | ); 109 | } 110 | }); 111 | }); 112 | 113 | describe("renew", function () { 114 | it("should renew session", async function () { 115 | const renew = await this.c1.session.renew(this.id); 116 | 117 | should(renew).be.an.Array(); 118 | 119 | should(renew).not.be.empty(); 120 | 121 | should(renew[0]).properties( 122 | "CreateIndex", 123 | "ID", 124 | "Name", 125 | "Node", 126 | "NodeChecks", 127 | "LockDelay", 128 | "Behavior", 129 | "TTL", 130 | ); 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /test/acceptance/watch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async_ = require("async"); 4 | const should = require("should"); 5 | 6 | const helper = require("./helper"); 7 | 8 | helper.describe("Watch", function () { 9 | before(async function () { 10 | await helper.before(this); 11 | }); 12 | 13 | after(async function () { 14 | await helper.after(this); 15 | }); 16 | 17 | beforeEach(async function () { 18 | await this.c1.kv.del({ recurse: true }); 19 | }); 20 | 21 | it("should work", async function () { 22 | const key = "test"; 23 | let count = 0; 24 | 25 | const changes = []; 26 | const updateTimes = []; 27 | const errors = []; 28 | 29 | const watch = this.c1.watch({ 30 | method: this.c1.kv.get, 31 | options: { key: key, wait: "1ms" }, 32 | }); 33 | 34 | watch.on("change", (data) => { 35 | updateTimes.push(watch.updateTime()); 36 | changes.push(data); 37 | }); 38 | 39 | watch.on("error", (err) => { 40 | errors.push(err); 41 | }); 42 | 43 | count++; 44 | await this.c1.kv.set(key, "1"); 45 | 46 | count++; 47 | await this.c1.kv.set(key, "2"); 48 | 49 | count++; 50 | await this.c1.kv.set(key, "3"); 51 | 52 | count++; 53 | await this.c1.kv.del(key); 54 | 55 | await async_.until( 56 | (next) => { 57 | return next(null, changes.length === count + 1); 58 | }, 59 | (next) => { 60 | setTimeout(next, 50); 61 | }, 62 | ); 63 | 64 | const values = changes.map((data) => data && data.Value); 65 | 66 | should(values).eql([undefined, "1", "2", "3", undefined]); 67 | 68 | should(watch.isRunning()).be.true(); 69 | 70 | watch.end(); 71 | should(watch.isRunning()).be.false(); 72 | 73 | watch._run(); 74 | 75 | should(watch.isRunning()).be.false(); 76 | watch.end(); 77 | 78 | should(updateTimes).not.be.empty(); 79 | 80 | updateTimes.forEach(function (updateTime, i) { 81 | if (i === 0) return; 82 | should(updateTime).have.above(updateTimes[i - 1]); 83 | }); 84 | }); 85 | 86 | it("should not retry on 400 errors", async function () { 87 | const errors = []; 88 | 89 | const watch = this.c1.watch({ method: this.c1.kv.get }); 90 | 91 | watch.on("error", (err) => { 92 | errors.push(err); 93 | }); 94 | 95 | await async_.until( 96 | (next) => { 97 | return next(null, errors.length === 1); 98 | }, 99 | (next) => { 100 | setTimeout(next, 50); 101 | }, 102 | ); 103 | 104 | should(watch).have.property("_attempts", 0); 105 | should(watch).have.property("_end", true); 106 | }); 107 | 108 | it("should exponential retry", async function () { 109 | const todo = ["one", "two", "three"]; 110 | 111 | const method = async function () { 112 | throw new Error(todo.shift()); 113 | }; 114 | 115 | let oneErr, twoErr; 116 | const time = +new Date(); 117 | 118 | const watch = this.c1.watch({ method: method }); 119 | 120 | return new Promise((resolve) => { 121 | watch.on("error", (err) => { 122 | err.time = +new Date(); 123 | 124 | if (err.message === "one") { 125 | oneErr = err; 126 | } else if (err.message === "two") { 127 | twoErr = err; 128 | } else if (err.message === "three") { 129 | should(oneErr.time - time).be.approximately(0, 20); 130 | should(twoErr.time - oneErr.time).be.approximately(200, 20); 131 | should(err.time - twoErr.time).be.approximately(400, 20); 132 | 133 | watch.end(); 134 | 135 | resolve(); 136 | } 137 | }); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /lib/session.js: -------------------------------------------------------------------------------- 1 | const errors = require("./errors"); 2 | const utils = require("./utils"); 3 | 4 | class Session { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Creates a new session 11 | */ 12 | async create(opts) { 13 | opts = utils.normalizeKeys(opts); 14 | opts = utils.defaults(opts, this.consul._defaults); 15 | 16 | const req = { 17 | name: "session.create", 18 | path: "/session/create", 19 | query: {}, 20 | type: "json", 21 | body: {}, 22 | }; 23 | 24 | if (opts.lockdelay) req.body.LockDelay = opts.lockdelay; 25 | if (opts.name) req.body.Name = opts.name; 26 | if (opts.node) req.body.Node = opts.node; 27 | if (opts.checks) req.body.Checks = opts.checks; 28 | if (opts.behavior) req.body.Behavior = opts.behavior; 29 | if (opts.ttl) req.body.TTL = opts.ttl; 30 | 31 | utils.options(req, opts); 32 | 33 | return await this.consul._put(req, utils.body); 34 | } 35 | 36 | /** 37 | * Destroys a given session 38 | */ 39 | async destroy(opts) { 40 | if (typeof opts === "string") { 41 | opts = { id: opts }; 42 | } 43 | 44 | opts = utils.normalizeKeys(opts); 45 | opts = utils.defaults(opts, this.consul._defaults); 46 | 47 | const req = { 48 | name: "session.destroy", 49 | path: "/session/destroy/{id}", 50 | params: { id: opts.id }, 51 | query: {}, 52 | }; 53 | 54 | if (!opts.id) { 55 | throw this.consul._err(errors.Validation("id required"), req); 56 | } 57 | 58 | utils.options(req, opts); 59 | 60 | return await this.consul._put(req, utils.empty); 61 | } 62 | 63 | /** 64 | * Queries a given session 65 | */ 66 | async info(opts) { 67 | if (typeof opts === "string") { 68 | opts = { id: opts }; 69 | } 70 | 71 | opts = utils.normalizeKeys(opts); 72 | opts = utils.defaults(opts, this.consul._defaults); 73 | 74 | const req = { 75 | name: "session.info", 76 | path: "/session/info/{id}", 77 | params: { id: opts.id }, 78 | query: {}, 79 | }; 80 | 81 | if (!opts.id) { 82 | throw this.consul._err(errors.Validation("id required"), req); 83 | } 84 | 85 | utils.options(req, opts); 86 | 87 | return await this.consul._get(req, utils.bodyItem); 88 | } 89 | 90 | get(opts) { 91 | return this.info(opts); 92 | } 93 | 94 | /** 95 | * Lists sessions belonging to a node 96 | */ 97 | async node(opts) { 98 | if (typeof opts === "string") { 99 | opts = { node: opts }; 100 | } 101 | 102 | opts = utils.normalizeKeys(opts); 103 | opts = utils.defaults(opts, this.consul._defaults); 104 | 105 | const req = { 106 | name: "session.node", 107 | path: "/session/node/{node}", 108 | params: { node: opts.node }, 109 | }; 110 | 111 | if (!opts.node) { 112 | throw this.consul._err(errors.Validation("node required"), req); 113 | } 114 | 115 | utils.options(req, opts); 116 | 117 | return await this.consul._get(req, utils.body); 118 | } 119 | 120 | /** 121 | * Lists all the active sessions 122 | */ 123 | async list(opts) { 124 | opts = utils.normalizeKeys(opts); 125 | opts = utils.defaults(opts, this.consul._defaults); 126 | 127 | const req = { 128 | name: "session.list", 129 | path: "/session/list", 130 | }; 131 | 132 | utils.options(req, opts); 133 | 134 | return await this.consul._get(req, utils.body); 135 | } 136 | 137 | /** 138 | * Renews a TTL-based session 139 | */ 140 | async renew(opts) { 141 | if (typeof opts === "string") { 142 | opts = { id: opts }; 143 | } 144 | 145 | opts = utils.normalizeKeys(opts); 146 | opts = utils.defaults(opts, this.consul._defaults); 147 | 148 | const req = { 149 | name: "session.renew", 150 | path: "/session/renew/{id}", 151 | params: { id: opts.id }, 152 | }; 153 | 154 | if (!opts.id) { 155 | throw this.consul._err(errors.Validation("id required"), req); 156 | } 157 | 158 | utils.options(req, opts); 159 | 160 | return await this.consul._put(req, utils.body); 161 | } 162 | } 163 | 164 | exports.Session = Session; 165 | -------------------------------------------------------------------------------- /lib/agent/check.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class AgentCheck { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Returns the checks the local agent is managing 11 | */ 12 | async list(opts) { 13 | opts = utils.normalizeKeys(opts); 14 | opts = utils.defaults(opts, this.consul._defaults); 15 | 16 | const req = { 17 | name: "agent.check.list", 18 | path: "/agent/checks", 19 | }; 20 | 21 | utils.options(req, opts); 22 | 23 | return await this.consul._get(req, utils.body); 24 | } 25 | 26 | /** 27 | * Registers a new local check 28 | */ 29 | async register(opts) { 30 | opts = utils.normalizeKeys(opts); 31 | opts = utils.defaults(opts, this.consul._defaults); 32 | 33 | const req = { 34 | name: "agent.check.register", 35 | path: "/agent/check/register", 36 | type: "json", 37 | }; 38 | 39 | try { 40 | req.body = utils.createCheck(opts); 41 | } catch (err) { 42 | throw this.consul._err(errors.Validation(err.message), req); 43 | } 44 | 45 | utils.options(req, opts); 46 | 47 | return await this.consul._put(req, utils.empty); 48 | } 49 | 50 | /** 51 | * Deregister a local check 52 | */ 53 | async deregister(opts) { 54 | if (typeof opts === "string") { 55 | opts = { id: opts }; 56 | } 57 | 58 | opts = utils.normalizeKeys(opts); 59 | opts = utils.defaults(opts, this.consul._defaults); 60 | 61 | const req = { 62 | name: "agent.check.deregister", 63 | path: "/agent/check/deregister/{id}", 64 | params: { id: opts.id }, 65 | }; 66 | 67 | if (!opts.id) { 68 | throw this.consul._err(errors.Validation("id required"), req); 69 | } 70 | 71 | utils.options(req, opts); 72 | 73 | return await this.consul._put(req, utils.empty); 74 | } 75 | 76 | /** 77 | * Mark a local test as passing 78 | */ 79 | async pass(opts) { 80 | if (typeof opts === "string") { 81 | opts = { id: opts }; 82 | } 83 | 84 | opts = utils.normalizeKeys(opts); 85 | opts = utils.defaults(opts, this.consul._defaults); 86 | 87 | const req = { 88 | name: "agent.check.pass", 89 | path: "/agent/check/pass/{id}", 90 | params: { id: opts.id }, 91 | query: {}, 92 | }; 93 | 94 | if (!opts.id) { 95 | throw this.consul._err(errors.Validation("id required"), req); 96 | } 97 | 98 | if (opts.note) req.query.note = opts.note; 99 | 100 | utils.options(req, opts); 101 | 102 | return await this.consul._put(req, utils.empty); 103 | } 104 | 105 | /** 106 | * Mark a local test as warning 107 | */ 108 | async warn(opts) { 109 | if (typeof opts === "string") { 110 | opts = { id: opts }; 111 | } 112 | 113 | opts = utils.normalizeKeys(opts); 114 | opts = utils.defaults(opts, this.consul._defaults); 115 | 116 | const req = { 117 | name: "agent.check.warn", 118 | path: "/agent/check/warn/{id}", 119 | params: { id: opts.id }, 120 | query: {}, 121 | }; 122 | 123 | if (!opts.id) { 124 | throw this.consul._err(errors.Validation("id required"), req); 125 | } 126 | 127 | if (opts.note) req.query.note = opts.note; 128 | 129 | utils.options(req, opts); 130 | 131 | return await this.consul._put(req, utils.empty); 132 | } 133 | 134 | /** 135 | * Mark a local test as critical 136 | */ 137 | async fail(opts) { 138 | if (typeof opts === "string") { 139 | opts = { id: opts }; 140 | } 141 | 142 | opts = utils.normalizeKeys(opts); 143 | opts = utils.defaults(opts, this.consul._defaults); 144 | 145 | const req = { 146 | name: "agent.check.fail", 147 | path: "/agent/check/fail/{id}", 148 | params: { id: opts.id }, 149 | query: {}, 150 | }; 151 | 152 | if (!opts.id) { 153 | throw this.consul._err(errors.Validation("id required"), req); 154 | } 155 | 156 | if (opts.note) req.query.note = opts.note; 157 | 158 | utils.options(req, opts); 159 | 160 | return await this.consul._put(req, utils.empty); 161 | } 162 | } 163 | 164 | exports.AgentCheck = AgentCheck; 165 | -------------------------------------------------------------------------------- /lib/acl/legacy.js: -------------------------------------------------------------------------------- 1 | const errors = require("../errors"); 2 | const utils = require("../utils"); 3 | 4 | class AclLegacy { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Creates a new token with policy 11 | */ 12 | async create(opts) { 13 | opts = utils.normalizeKeys(opts); 14 | opts = utils.defaults(opts, this.consul._defaults); 15 | 16 | const req = { 17 | name: "acl.legacy.create", 18 | path: "/acl/create", 19 | query: {}, 20 | type: "json", 21 | body: {}, 22 | }; 23 | 24 | if (opts.name) req.body.Name = opts.name; 25 | if (opts.type) req.body.Type = opts.type; 26 | if (opts.rules) req.body.Rules = opts.rules; 27 | 28 | utils.options(req, opts); 29 | 30 | return await this.consul._put(req, utils.body); 31 | } 32 | 33 | /** 34 | * Update the policy of a token 35 | */ 36 | async update(opts) { 37 | opts = utils.normalizeKeys(opts); 38 | opts = utils.defaults(opts, this.consul._defaults); 39 | 40 | const req = { 41 | name: "acl.legacy.update", 42 | path: "/acl/update", 43 | query: {}, 44 | type: "json", 45 | body: {}, 46 | }; 47 | 48 | if (!opts.id) { 49 | throw this.consul._err(errors.Validation("id required"), req); 50 | } 51 | 52 | req.body.ID = opts.id; 53 | 54 | if (opts.name) req.body.Name = opts.name; 55 | if (opts.type) req.body.Type = opts.type; 56 | if (opts.rules) req.body.Rules = opts.rules; 57 | 58 | utils.options(req, opts); 59 | 60 | return await this.consul._put(req, utils.empty); 61 | } 62 | 63 | /** 64 | * Destroys a given token 65 | */ 66 | async destroy(opts) { 67 | if (typeof opts === "string") { 68 | opts = { id: opts }; 69 | } 70 | 71 | opts = utils.normalizeKeys(opts); 72 | opts = utils.defaults(opts, this.consul._defaults); 73 | 74 | const req = { 75 | name: "acl.legacy.destroy", 76 | path: "/acl/destroy/{id}", 77 | params: { id: opts.id }, 78 | query: {}, 79 | }; 80 | 81 | if (!opts.id) { 82 | throw this.consul._err(errors.Validation("id required"), req); 83 | } 84 | 85 | utils.options(req, opts); 86 | 87 | return await this.consul._put(req, utils.empty); 88 | } 89 | 90 | /** 91 | * Queries the policy of a given token 92 | */ 93 | async info(opts) { 94 | if (typeof opts === "string") { 95 | opts = { id: opts }; 96 | } 97 | 98 | opts = utils.normalizeKeys(opts); 99 | opts = utils.defaults(opts, this.consul._defaults); 100 | 101 | const req = { 102 | name: "acl.legacy.info", 103 | path: "/acl/info/{id}", 104 | params: { id: opts.id }, 105 | query: {}, 106 | }; 107 | 108 | if (!opts.id) { 109 | throw this.consul._err(errors.Validation("id required"), req); 110 | } 111 | 112 | utils.options(req, opts); 113 | 114 | return await this.consul._get(req, utils.bodyItem); 115 | } 116 | 117 | get(...args) { 118 | return this.info(...args); 119 | } 120 | 121 | /** 122 | * Creates a new token by cloning an existing token 123 | */ 124 | async clone(opts) { 125 | if (typeof opts === "string") { 126 | opts = { id: opts }; 127 | } 128 | 129 | opts = utils.normalizeKeys(opts); 130 | opts = utils.defaults(opts, this.consul._defaults); 131 | 132 | const req = { 133 | name: "acl.legacy.clone", 134 | path: "/acl/clone/{id}", 135 | params: { id: opts.id }, 136 | query: {}, 137 | }; 138 | 139 | if (!opts.id) { 140 | throw this.consul._err(errors.Validation("id required"), req); 141 | } 142 | 143 | utils.options(req, opts); 144 | 145 | return await this.consul._put(req, utils.body); 146 | } 147 | 148 | /** 149 | * Lists all the active tokens 150 | */ 151 | async list(opts) { 152 | opts = utils.normalizeKeys(opts); 153 | opts = utils.defaults(opts, this.consul._defaults); 154 | 155 | const req = { 156 | name: "acl.legacy.list", 157 | path: "/acl/list", 158 | query: {}, 159 | }; 160 | 161 | utils.options(req, opts); 162 | 163 | return await this.consul._get(req, utils.body); 164 | } 165 | } 166 | 167 | exports.AclLegacy = AclLegacy; 168 | -------------------------------------------------------------------------------- /lib/agent.js: -------------------------------------------------------------------------------- 1 | const AgentCheck = require("./agent/check").AgentCheck; 2 | const AgentService = require("./agent/service").AgentService; 3 | const errors = require("./errors"); 4 | const utils = require("./utils"); 5 | 6 | class Agent { 7 | constructor(consul) { 8 | this.consul = consul; 9 | this.check = new Agent.Check(consul); 10 | this.service = new Agent.Service(consul); 11 | } 12 | 13 | /** 14 | * Returns the checks the local agent is managing 15 | */ 16 | checks() { 17 | return this.check.list.apply(this.check, arguments); 18 | } 19 | 20 | /** 21 | * Returns the services local agent is managing 22 | */ 23 | services() { 24 | return this.service.list.apply(this.service, arguments); 25 | } 26 | 27 | /** 28 | * Returns the members as seen by the local consul agent 29 | */ 30 | async members(opts) { 31 | opts = utils.normalizeKeys(opts); 32 | opts = utils.defaults(opts, this.consul._defaults); 33 | 34 | const req = { 35 | name: "agent.members", 36 | path: "/agent/members", 37 | query: {}, 38 | }; 39 | 40 | utils.options(req, opts); 41 | 42 | return await this.consul._get(req, utils.body); 43 | } 44 | 45 | /** 46 | * Reload agent configuration 47 | */ 48 | async reload(opts) { 49 | opts = utils.normalizeKeys(opts); 50 | opts = utils.defaults(opts, this.consul._defaults); 51 | 52 | const req = { 53 | name: "agent.reload", 54 | path: "/agent/reload", 55 | }; 56 | 57 | utils.options(req, opts); 58 | 59 | return await this.consul._put(req, utils.empty); 60 | } 61 | 62 | /** 63 | * Returns the local node configuration 64 | */ 65 | async self(opts) { 66 | opts = utils.normalizeKeys(opts); 67 | opts = utils.defaults(opts, this.consul._defaults); 68 | 69 | const req = { 70 | name: "agent.self", 71 | path: "/agent/self", 72 | }; 73 | 74 | utils.options(req, opts); 75 | 76 | return await this.consul._get(req, utils.body); 77 | } 78 | 79 | /** 80 | * Manages node maintenance mode 81 | */ 82 | async maintenance(opts) { 83 | if (typeof opts === "boolean") { 84 | opts = { enable: opts }; 85 | } 86 | 87 | opts = utils.normalizeKeys(opts); 88 | opts = utils.defaults(opts, this.consul._defaults); 89 | 90 | const req = { 91 | name: "agent.maintenance", 92 | path: "/agent/maintenance", 93 | query: { enable: opts.enable }, 94 | }; 95 | 96 | if (typeof opts.enable !== "boolean") { 97 | throw this.consul._err(errors.Validation("enable required"), req); 98 | } 99 | if (opts.reason) req.query.reason = opts.reason; 100 | 101 | utils.options(req, opts); 102 | 103 | return await this.consul._put(req, utils.empty); 104 | } 105 | 106 | /** 107 | * Trigger local agent to join a node 108 | */ 109 | async join(opts) { 110 | if (typeof opts === "string") { 111 | opts = { address: opts }; 112 | } 113 | 114 | opts = utils.normalizeKeys(opts); 115 | opts = utils.defaults(opts, this.consul._defaults); 116 | 117 | const req = { 118 | name: "agent.join", 119 | path: "/agent/join/{address}", 120 | params: { address: opts.address }, 121 | }; 122 | 123 | if (!opts.address) { 124 | throw this.consul._err(errors.Validation("address required"), req); 125 | } 126 | 127 | utils.options(req, opts); 128 | 129 | return await this.consul._put(req, utils.empty); 130 | } 131 | 132 | /** 133 | * Force remove node 134 | */ 135 | async forceLeave(opts) { 136 | if (typeof opts === "string") { 137 | opts = { node: opts }; 138 | } 139 | 140 | opts = utils.normalizeKeys(opts); 141 | opts = utils.defaults(opts, this.consul._defaults); 142 | 143 | const req = { 144 | name: "agent.forceLeave", 145 | path: "/agent/force-leave/{node}", 146 | params: { node: opts.node }, 147 | }; 148 | 149 | if (!opts.node) { 150 | throw this.consul._err(errors.Validation("node required"), req); 151 | } 152 | 153 | utils.options(req, opts); 154 | 155 | return await this.consul._put(req, utils.empty); 156 | } 157 | } 158 | 159 | Agent.Check = AgentCheck; 160 | Agent.Service = AgentService; 161 | 162 | exports.Agent = Agent; 163 | -------------------------------------------------------------------------------- /test/health.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Health", function () { 8 | helper.setup(this); 9 | 10 | describe("node", function () { 11 | it("should work", async function () { 12 | this.nock.get("/v1/health/node/node1").reply(200, { ok: true }); 13 | 14 | const data = await this.consul.health.node({ node: "node1" }); 15 | should(data).eql({ ok: true }); 16 | }); 17 | 18 | it("should work with one argument", async function () { 19 | this.nock.get("/v1/health/node/node1").reply(200, { ok: true }); 20 | 21 | const data = await this.consul.health.node("node1"); 22 | should(data).eql({ ok: true }); 23 | }); 24 | 25 | it("should require node", async function () { 26 | try { 27 | await this.consul.health.node({}); 28 | should.ok(false); 29 | } catch (err) { 30 | should(err).have.property( 31 | "message", 32 | "consul: health.node: node required", 33 | ); 34 | } 35 | }); 36 | }); 37 | 38 | describe("checks", function () { 39 | it("should work", async function () { 40 | this.nock.get("/v1/health/checks/service1").reply(200, { ok: true }); 41 | 42 | const data = await this.consul.health.checks({ service: "service1" }); 43 | should(data).eql({ ok: true }); 44 | }); 45 | 46 | it("should work with one argument", async function () { 47 | this.nock.get("/v1/health/checks/service1").reply(200, { ok: true }); 48 | 49 | const data = await this.consul.health.checks("service1"); 50 | should(data).eql({ ok: true }); 51 | }); 52 | 53 | it("should require service", async function () { 54 | try { 55 | await this.consul.health.checks({}); 56 | should.ok(false); 57 | } catch (err) { 58 | should(err).have.property( 59 | "message", 60 | "consul: health.checks: service required", 61 | ); 62 | } 63 | }); 64 | }); 65 | 66 | describe("service", function () { 67 | it("should work", async function () { 68 | this.nock 69 | .get("/v1/health/service/service1?tag=tag1&passing=true") 70 | .reply(200, { ok: true }); 71 | 72 | const data = await this.consul.health.service({ 73 | service: "service1", 74 | tag: "tag1", 75 | passing: "true", 76 | }); 77 | should(data).eql({ ok: true }); 78 | }); 79 | 80 | it("should work with one argument", async function () { 81 | this.nock.get("/v1/health/service/service1").reply(200, { ok: true }); 82 | 83 | const data = await this.consul.health.service("service1"); 84 | should(data).eql({ ok: true }); 85 | }); 86 | 87 | it("should require service", async function () { 88 | try { 89 | await this.consul.health.service({}); 90 | should.ok(false); 91 | } catch (err) { 92 | should(err).have.property( 93 | "message", 94 | "consul: health.service: service required", 95 | ); 96 | } 97 | }); 98 | }); 99 | 100 | describe("state", function () { 101 | it("should work", async function () { 102 | this.nock.get("/v1/health/state/any").reply(200, { ok: true }); 103 | 104 | const data = await this.consul.health.state({ state: "any" }); 105 | should(data).eql({ ok: true }); 106 | }); 107 | 108 | it("should work with one argument", async function () { 109 | this.nock.get("/v1/health/state/warning").reply(200, { ok: true }); 110 | 111 | const data = await this.consul.health.state("warning"); 112 | should(data).eql({ ok: true }); 113 | }); 114 | 115 | it("should require state", async function () { 116 | try { 117 | await this.consul.health.state({}); 118 | should.ok(false); 119 | } catch (err) { 120 | should(err).have.property( 121 | "message", 122 | "consul: health.state: state required", 123 | ); 124 | } 125 | }); 126 | 127 | it("should require valid state", async function () { 128 | try { 129 | await this.consul.health.state("foo"); 130 | should.ok(false); 131 | } catch (err) { 132 | should(err).have.property( 133 | "message", 134 | "consul: health.state: state invalid: foo", 135 | ); 136 | } 137 | }); 138 | }); 139 | }); 140 | -------------------------------------------------------------------------------- /test/transaction.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Transaction", function () { 8 | helper.setup(this); 9 | 10 | describe("create", function () { 11 | it("should work", async function () { 12 | const operations = [ 13 | { 14 | KV: { 15 | Verb: "create", 16 | Key: "key", 17 | Value: Buffer.from("value").toString("base64"), 18 | }, 19 | }, 20 | { 21 | Node: { 22 | Verb: "set", 23 | Node: { 24 | ID: "67539c9d-b948-ba67-edd4-d07a676d6673", 25 | Node: "bar", 26 | Address: "192.168.0.1", 27 | Datacenter: "dc1", 28 | Meta: { 29 | instance_type: "m2.large", 30 | }, 31 | }, 32 | }, 33 | }, 34 | { 35 | Service: { 36 | Verb: "delete", 37 | Node: "foo", 38 | Service: { 39 | ID: "db1", 40 | }, 41 | }, 42 | }, 43 | { 44 | Check: { 45 | Verb: "cas", 46 | Check: { 47 | Node: "bar", 48 | CheckID: "service:web1", 49 | Name: "Web HTTP Check", 50 | Status: "critical", 51 | ServiceID: "web1", 52 | ServiceName: "web", 53 | ServiceTags: null, 54 | Definition: { 55 | HTTP: "http://localhost:8080", 56 | Interval: "10s", 57 | }, 58 | ModifyIndex: 22, 59 | }, 60 | }, 61 | }, 62 | ]; 63 | 64 | this.nock.put("/v1/txn", operations).reply(200, { ok: true }); 65 | 66 | const data = await this.consul.transaction.create(operations); 67 | should(data).eql({ ok: true }); 68 | }); 69 | 70 | it("should accept an option as arguments", async function () { 71 | const operations = [ 72 | { 73 | KV: { 74 | Verb: "create", 75 | Key: "key", 76 | Value: Buffer.from("value").toString("base64"), 77 | }, 78 | }, 79 | { 80 | Node: { 81 | Verb: "set", 82 | Node: { 83 | ID: "67539c9d-b948-ba67-edd4-d07a676d6673", 84 | Node: "bar", 85 | Address: "192.168.0.1", 86 | Datacenter: "dc1", 87 | Meta: { 88 | instance_type: "m2.large", 89 | }, 90 | }, 91 | }, 92 | }, 93 | { 94 | Service: { 95 | Verb: "delete", 96 | Node: "foo", 97 | Service: { 98 | ID: "db1", 99 | }, 100 | }, 101 | }, 102 | { 103 | Check: { 104 | Verb: "cas", 105 | Check: { 106 | Node: "bar", 107 | CheckID: "service:web1", 108 | Name: "Web HTTP Check", 109 | Status: "critical", 110 | ServiceID: "web1", 111 | ServiceName: "web", 112 | ServiceTags: null, 113 | Definition: { 114 | HTTP: "http://localhost:8080", 115 | Interval: "10s", 116 | }, 117 | ModifyIndex: 22, 118 | }, 119 | }, 120 | }, 121 | ]; 122 | 123 | this.nock.put("/v1/txn?stale=1", operations).reply(200, { ok: true }); 124 | 125 | const data = await this.consul.transaction.create(operations, { 126 | stale: true, 127 | }); 128 | should(data).eql({ ok: true }); 129 | }); 130 | 131 | it("should require a list of operations", async function () { 132 | try { 133 | await this.consul.transaction.create(); 134 | should.ok(false); 135 | } catch (err) { 136 | should(err).have.property( 137 | "message", 138 | "consul: Transaction.create: a list of operations are required as first arguments", 139 | ); 140 | } 141 | }); 142 | 143 | it("should require a list of operations", async function () { 144 | try { 145 | await this.consul.transaction.create([]); 146 | should.ok(false); 147 | } catch (err) { 148 | should(err).have.property( 149 | "message", 150 | "consul: Transaction.create: operations must be an array with at least one item", 151 | ); 152 | } 153 | }); 154 | }); 155 | }); 156 | -------------------------------------------------------------------------------- /lib/kv.js: -------------------------------------------------------------------------------- 1 | const errors = require("./errors"); 2 | const utils = require("./utils"); 3 | 4 | class Kv { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Get 11 | */ 12 | async get(opts) { 13 | if (typeof opts === "string") { 14 | opts = { key: opts }; 15 | } 16 | 17 | opts = utils.normalizeKeys(opts); 18 | opts = utils.defaults(opts, this.consul._defaults); 19 | 20 | const req = { 21 | name: "kv.get", 22 | path: "/kv/{key}", 23 | params: { key: opts.key || "" }, 24 | query: {}, 25 | }; 26 | 27 | if (opts.recurse) req.query.recurse = "true"; 28 | if (opts.raw) { 29 | req.query.raw = "true"; 30 | req.buffer = true; 31 | } 32 | 33 | utils.options(req, opts); 34 | 35 | return await this.consul._get(req, function (request, next) { 36 | const res = request.res; 37 | 38 | if (res && res.statusCode === 404) { 39 | return next(false, undefined, utils.responseResult(request)); 40 | } 41 | if (request.err) { 42 | utils.applyErrorResponse(request); 43 | return next(request.err); 44 | } 45 | if (opts.raw) { 46 | return next(false, undefined, utils.responseResult(request, res.body)); 47 | } 48 | 49 | if (res.body && Array.isArray(res.body) && res.body.length) { 50 | res.body.forEach((item) => { 51 | if (item.hasOwnProperty("Value")) { 52 | item.Value = utils.decode(item.Value, opts); 53 | } 54 | }); 55 | } else { 56 | return next(false, undefined, utils.responseResult(request)); 57 | } 58 | 59 | if (!opts.recurse) { 60 | return next( 61 | false, 62 | undefined, 63 | utils.responseResult(request, res.body[0]), 64 | ); 65 | } 66 | 67 | next(false, undefined, utils.responseResult(request, res.body)); 68 | }); 69 | } 70 | 71 | /** 72 | * Keys 73 | */ 74 | async keys(opts) { 75 | if (typeof opts === "string") { 76 | opts = { key: opts }; 77 | } 78 | 79 | opts = utils.normalizeKeys(opts); 80 | opts = utils.defaults(opts, this.consul._defaults); 81 | 82 | const req = { 83 | name: "kv.keys", 84 | path: "/kv/{key}", 85 | params: { key: opts.key || "" }, 86 | query: { keys: true }, 87 | }; 88 | 89 | if (opts.separator) req.query.separator = opts.separator; 90 | 91 | utils.options(req, opts); 92 | 93 | return await this.consul._get(req, utils.body); 94 | } 95 | 96 | /** 97 | * Set 98 | */ 99 | async set(opts) { 100 | let options; 101 | switch (arguments.length) { 102 | case 3: 103 | // set(key, value, opts) 104 | options = arguments[2]; 105 | options.key = arguments[0]; 106 | options.value = arguments[1]; 107 | break; 108 | case 2: 109 | // set(key, value) 110 | options = { 111 | key: arguments[0], 112 | value: arguments[1], 113 | }; 114 | break; 115 | default: 116 | options = opts; 117 | } 118 | 119 | options = utils.normalizeKeys(options); 120 | options = utils.defaults(options, this.consul._defaults); 121 | 122 | const req = { 123 | name: "kv.set", 124 | path: "/kv/{key}", 125 | params: { key: options.key }, 126 | query: {}, 127 | type: "text", 128 | body: options.value || "", 129 | }; 130 | 131 | if (!options.key) { 132 | throw this.consul._err(errors.Validation("key required"), req); 133 | } 134 | if (!options.hasOwnProperty("value")) { 135 | throw this.consul._err(errors.Validation("value required"), req); 136 | } 137 | 138 | if (options.hasOwnProperty("cas")) req.query.cas = options.cas; 139 | if (options.hasOwnProperty("flags")) req.query.flags = options.flags; 140 | if (options.hasOwnProperty("acquire")) req.query.acquire = options.acquire; 141 | if (options.hasOwnProperty("release")) req.query.release = options.release; 142 | 143 | utils.options(req, options); 144 | 145 | return await this.consul._put(req, utils.body); 146 | } 147 | 148 | /** 149 | * Delete 150 | */ 151 | async del(opts) { 152 | if (typeof opts === "string") { 153 | opts = { key: opts }; 154 | } 155 | 156 | opts = utils.normalizeKeys(opts); 157 | opts = utils.defaults(opts, this.consul._defaults); 158 | 159 | const req = { 160 | name: "kv.del", 161 | path: "/kv/{key}", 162 | params: { key: opts.key || "" }, 163 | query: {}, 164 | }; 165 | 166 | if (opts.recurse) req.query.recurse = "true"; 167 | 168 | if (opts.hasOwnProperty("cas")) req.query.cas = opts.cas; 169 | 170 | utils.options(req, opts); 171 | 172 | return await this.consul._delete(req, utils.body); 173 | } 174 | 175 | delete() { 176 | return this.del.apply(this, arguments); 177 | } 178 | } 179 | 180 | exports.Kv = Kv; 181 | -------------------------------------------------------------------------------- /lib/watch.js: -------------------------------------------------------------------------------- 1 | const events = require("events"); 2 | 3 | const errors = require("./errors"); 4 | const utils = require("./utils"); 5 | 6 | /** 7 | * Initialize a new `Watch` instance. 8 | */ 9 | 10 | class Watch extends events.EventEmitter { 11 | constructor(consul, opts) { 12 | super(); 13 | 14 | this.consul = consul; 15 | 16 | opts = utils.normalizeKeys(opts); 17 | 18 | let options = utils.normalizeKeys(opts.options || {}); 19 | options = utils.defaults(options, consul._defaults); 20 | options.wait = options.wait || "30s"; 21 | options.index = utils.safeBigInt(options.index || "0"); 22 | 23 | if ( 24 | typeof options.timeout !== "string" && 25 | typeof options.timeout !== "number" 26 | ) { 27 | const wait = utils.parseDuration(options.wait); 28 | // A small random amount of additional wait time is added to the supplied 29 | // maximum wait time to spread out the wake up time of any concurrent 30 | // requests. This adds up to wait / 16 additional time to the maximum duration. 31 | options.timeout = Math.ceil(wait + Math.max(wait * 0.1, 500)); 32 | } 33 | 34 | let backoffFactor = 100; 35 | if ( 36 | opts.hasOwnProperty("backofffactor") && 37 | typeof opts.backofffactor === "number" 38 | ) { 39 | backoffFactor = opts.backofffactor; 40 | } 41 | let backoffMax = 30 * 1000; 42 | if ( 43 | opts.hasOwnProperty("backoffmax") && 44 | typeof opts.backoffmax === "number" 45 | ) { 46 | backoffMax = opts.backoffmax; 47 | } 48 | let maxAttempts = -1; 49 | if ( 50 | opts.hasOwnProperty("maxattempts") && 51 | typeof opts.maxattempts === "number" 52 | ) { 53 | maxAttempts = opts.maxattempts; 54 | } 55 | 56 | this._context = { consul: consul }; 57 | this._options = options; 58 | this._attempts = 0; 59 | this._maxAttempts = maxAttempts; 60 | this._backoffMax = backoffMax; 61 | this._backoffFactor = backoffFactor; 62 | this._method = opts.method; 63 | this.includeResponse = true; 64 | 65 | if (typeof opts.method !== "function") { 66 | throw errors.Validation("method required"); 67 | } 68 | 69 | process.nextTick(() => this._run()); 70 | } 71 | 72 | /** 73 | * Is running 74 | */ 75 | isRunning() { 76 | return !this._end; 77 | } 78 | 79 | /** 80 | * Update time 81 | */ 82 | updateTime() { 83 | return this._updateTime; 84 | } 85 | 86 | /** 87 | * End watch 88 | */ 89 | end() { 90 | if (this._end) return; 91 | this._end = true; 92 | 93 | this.emit("cancel"); 94 | this.emit("end"); 95 | } 96 | 97 | /** 98 | * Wait 99 | */ 100 | _wait() { 101 | this._attempts += 1; 102 | if (this._attemptsMaxed) { 103 | return this._backoffMax; 104 | } 105 | const timeout = Math.pow(2, this._attempts) * this._backoffFactor; 106 | if (timeout < this._backoffMax) { 107 | return timeout; 108 | } else { 109 | this._attemptsMaxed = true; 110 | return this._backoffMax; 111 | } 112 | } 113 | 114 | /** 115 | * Error helper 116 | */ 117 | _err(err, res) { 118 | if (this._end) return; 119 | 120 | this.emit("error", err, res); 121 | 122 | if (err && err.isValidation) return this.end(); 123 | if (res && res.statusCode === 400) return this.end(); 124 | if (this._maxAttempts >= 0 && this._attempts >= this._maxAttempts) 125 | return this.end(); 126 | 127 | utils.setTimeoutContext( 128 | () => { 129 | this._run(); 130 | }, 131 | this, 132 | this._wait(), 133 | ); 134 | } 135 | 136 | /** 137 | * Run 138 | */ 139 | _run() { 140 | if (this._end) return; 141 | 142 | const opts = utils.clone(this._options); 143 | opts.ctx = this; 144 | 145 | this._method 146 | .call(this._context, opts) 147 | .then(([res, data]) => { 148 | this._updateTime = +new Date(); 149 | 150 | this._attempts = 0; 151 | this._attemptsMaxed = false; 152 | 153 | const newIndex = utils.safeBigInt(res.headers["x-consul-index"]); 154 | if (newIndex === undefined) { 155 | return this._err(errors.Validation("Watch not supported"), res); 156 | } 157 | if (newIndex === 0n) { 158 | return this._err( 159 | errors.Consul("Consul returned zero index value"), 160 | res, 161 | ); 162 | } 163 | 164 | const prevIndex = this._options.index; 165 | const reset = prevIndex !== undefined && newIndex < prevIndex; 166 | 167 | if (reset || utils.hasIndexChanged(newIndex, prevIndex)) { 168 | this._options.index = reset ? 0n : newIndex; 169 | 170 | this.emit("change", data, res); 171 | } 172 | 173 | process.nextTick(() => { 174 | this._run(); 175 | }); 176 | }) 177 | .catch((err) => { 178 | this._err(err, err.response); 179 | }); 180 | } 181 | } 182 | 183 | exports.Watch = Watch; 184 | -------------------------------------------------------------------------------- /test/acceptance/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | require("should"); 4 | 5 | const async_ = require("async"); 6 | const fs = require("fs"); 7 | const path = require("path"); 8 | const spawn = require("child_process").spawn; 9 | const temp = require("temp").track(); 10 | const util = require("util"); 11 | 12 | const Consul = require("../../lib"); 13 | 14 | function bufferToString(value, depth) { 15 | if (!value) return value; 16 | if (!depth) depth = 0; 17 | if (depth > 10) return value; 18 | 19 | if (Buffer.isBuffer(value)) return value.toString(); 20 | 21 | if (Array.isArray(value)) { 22 | return value.map((v) => bufferToString(v, depth + 1)); 23 | } 24 | 25 | if (typeof value === "object") { 26 | for (const [k, v] of Object.entries(value)) { 27 | value[k] = bufferToString(v, depth + 1); 28 | } 29 | } 30 | 31 | return value; 32 | } 33 | 34 | function debugBuffer(name) { 35 | const debug = require("debug")(name); 36 | 37 | return function () { 38 | debug.apply(debug, bufferToString(Array.prototype.slice.call(arguments))); 39 | }; 40 | } 41 | 42 | class Cluster { 43 | constructor() { 44 | this._started = false; 45 | this.process = {}; 46 | } 47 | 48 | async spawn(opts) { 49 | const binPath = process.env.CONSUL_BIN || "consul"; 50 | 51 | const args = ["agent"]; 52 | for (const [key, value] of Object.entries(opts)) { 53 | args.push("-" + key); 54 | if (typeof value !== "boolean" && value !== undefined) { 55 | args.push("" + value); 56 | } 57 | } 58 | 59 | const serverDataPath = await util.promisify(temp.mkdir)({}); 60 | const serverConfigPath = path.join(serverDataPath, "config.json"); 61 | 62 | const serverConfig = { 63 | primary_datacenter: "dc1", 64 | acl: { 65 | enabled: false, 66 | }, 67 | enable_script_checks: true, 68 | }; 69 | await util.promisify(fs.writeFile)( 70 | serverConfigPath, 71 | JSON.stringify(serverConfig), 72 | ); 73 | 74 | args.push("-config-file"); 75 | args.push(serverConfigPath); 76 | args.push("-data-dir"); 77 | args.push(path.join(serverDataPath, opts.node, "data")); 78 | args.push("-pid-file"); 79 | args.push(path.join(serverDataPath, opts.node, "pid")); 80 | 81 | const server = spawn(binPath, args); 82 | 83 | server.destroy = () => { 84 | server._destroyed = true; 85 | server.kill("SIGKILL"); 86 | }; 87 | 88 | this.process[opts.node] = server; 89 | 90 | const serverLog = debugBuffer("consul:server:" + opts.node); 91 | server.stdout.on("data", (data) => serverLog(data)); 92 | server.stderr.on("data", (data) => serverLog(data)); 93 | 94 | server.on("exit", (code) => { 95 | if (code !== 0 && !server._destroyed) { 96 | const err = new Error( 97 | "Server exited (" + opts.node + "): " + code + "\n", 98 | ); 99 | err.message += "Command: " + binPath + " " + JSON.stringify(args); 100 | throw err; 101 | } 102 | }); 103 | 104 | const token = opts.bootstrap ? "root" : "agent_master"; 105 | const client = new Consul({ 106 | host: opts.bind, 107 | defaults: { token: token }, 108 | }); 109 | 110 | const clientLog = debugBuffer("consul:client:" + opts.node); 111 | client.on("log", clientLog); 112 | 113 | try { 114 | await async_.retry({ times: 100, interval: 100 }, async () => { 115 | // wait until server starts 116 | if (opts.bootstrap) { 117 | await client.kv.set("check", "ok"); 118 | } else { 119 | await client.agent.self(); 120 | } 121 | }); 122 | } catch (err) { 123 | server.destroy(); 124 | } 125 | } 126 | 127 | async setup() { 128 | if (this._started) return new Error("already started"); 129 | this._started = true; 130 | 131 | const nodes = ["node1", "node2", "node3"].map((node, i) => { 132 | i = i + 1; 133 | 134 | const opts = { 135 | node: node, 136 | datacenter: "dc1", 137 | bind: "127.0.0." + i, 138 | client: "127.0.0." + i, 139 | }; 140 | 141 | if (i === 1) { 142 | opts.bootstrap = true; 143 | opts.server = true; 144 | } 145 | 146 | return this.spawn(opts); 147 | }); 148 | 149 | await Promise.all(nodes); 150 | } 151 | 152 | async teardown() { 153 | for (const server of Object.values(this.process)) { 154 | server.destroy(); 155 | } 156 | 157 | await util.promisify(temp.cleanup)(); 158 | } 159 | } 160 | 161 | async function before(test) { 162 | test.cluster = new Cluster(); 163 | 164 | await test.cluster.setup(); 165 | 166 | for (let i = 1; i <= 3; i++) { 167 | const client = (test["c" + i] = new Consul({ 168 | host: "127.0.0." + i, 169 | defaults: { token: "root" }, 170 | })); 171 | client.on("log", debugBuffer("consul:" + "127.0.0." + i)); 172 | } 173 | } 174 | 175 | async function after(test) { 176 | await test.cluster.teardown(); 177 | } 178 | 179 | function skip() {} 180 | skip.skip = skip; 181 | 182 | exports.describe = process.env.ACCEPTANCE === "true" ? describe : skip; 183 | exports.before = before; 184 | exports.after = after; 185 | -------------------------------------------------------------------------------- /test/acceptance/kv.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | helper.describe("Kv", function () { 8 | before(async function () { 9 | await helper.before(this); 10 | }); 11 | 12 | after(async function () { 13 | await helper.after(this); 14 | }); 15 | 16 | beforeEach(async function () { 17 | this.key = "hello"; 18 | this.value = "world"; 19 | 20 | await this.c1.kv.del({ recurse: true }); 21 | 22 | const ok = await this.c1.kv.set(this.key, this.value); 23 | if (!ok) throw new Error("not setup"); 24 | }); 25 | 26 | describe("get", function () { 27 | it("should return one kv pair", async function () { 28 | const data = await this.c1.kv.get(this.key); 29 | 30 | should(data).have.keys( 31 | "CreateIndex", 32 | "ModifyIndex", 33 | "LockIndex", 34 | "Key", 35 | "Flags", 36 | "Value", 37 | ); 38 | should(data.Key).eql(this.key); 39 | should(data.Flags).eql(0); 40 | should(data.Value).eql(this.value); 41 | }); 42 | 43 | it("should return raw value", async function () { 44 | const data = await this.c1.kv.get({ key: this.key, raw: true }); 45 | should(Buffer.from(this.value)).eql(data); 46 | }); 47 | 48 | it("should return no kv pair", async function () { 49 | const data = await this.c1.kv.get("none"); 50 | should.not.exist(data); 51 | }); 52 | 53 | it("should return list of kv pairs", async function () { 54 | const data = await this.c1.kv.get({ recurse: true }); 55 | should(data).be.instanceof(Array); 56 | should(data.length).eql(1); 57 | 58 | const item = data[0]; 59 | should(item).have.keys( 60 | "CreateIndex", 61 | "ModifyIndex", 62 | "LockIndex", 63 | "Key", 64 | "Flags", 65 | "Value", 66 | ); 67 | should(item.Key).eql(this.key); 68 | should(item.Flags).eql(0); 69 | should(item.Value).eql(this.value); 70 | }); 71 | 72 | it("should wait for update", async function () { 73 | const update = "new-value"; 74 | 75 | const get = await this.c1.kv.get(this.key); 76 | 77 | const waitingGet = this.c1.kv.get({ 78 | key: this.key, 79 | index: get.ModifyIndex, 80 | wait: "3s", 81 | }); 82 | 83 | await this.c1.kv.set(this.key, update); 84 | 85 | const data = await waitingGet; 86 | should(data.Value).eql(update); 87 | }); 88 | }); 89 | 90 | describe("keys", function () { 91 | beforeEach(async function () { 92 | this.keys = ["a/x/1", "a/y/2", "a/z/3"]; 93 | 94 | await Promise.all(this.keys.map((key) => this.c1.kv.set(key, "value"))); 95 | }); 96 | 97 | it("should return keys", async function () { 98 | const data = await this.c1.kv.keys("a"); 99 | should(data).be.instanceof(Array); 100 | 101 | should(data.length).equal(3); 102 | 103 | should(data).eql( 104 | this.keys.filter((key) => { 105 | return key.match(/^a/); 106 | }), 107 | ); 108 | }); 109 | 110 | it("should return keys with separator", async function () { 111 | const data = await this.c1.kv.keys({ 112 | key: "a/", 113 | separator: "/", 114 | }); 115 | should(data).be.instanceof(Array); 116 | 117 | should(data.length).equal(3); 118 | 119 | should(data).eql( 120 | this.keys 121 | .filter((key) => { 122 | return key.match(/^a\//); 123 | }) 124 | .map(function (v) { 125 | return v.slice(0, 4); 126 | }), 127 | ); 128 | }); 129 | 130 | it("should return all keys", async function () { 131 | const data = await this.c1.kv.keys(); 132 | should(data).be.instanceof(Array); 133 | 134 | should(data.length).equal(4); 135 | }); 136 | }); 137 | 138 | describe("set", function () { 139 | it("should create kv pair", async function () { 140 | const c = this.c1; 141 | const key = "one"; 142 | const value = "two"; 143 | 144 | const ok = await c.kv.set(key, value); 145 | should(ok).be.true(); 146 | 147 | const data = await c.kv.get(key); 148 | should(data).have.keys( 149 | "CreateIndex", 150 | "ModifyIndex", 151 | "LockIndex", 152 | "Key", 153 | "Flags", 154 | "Value", 155 | ); 156 | should(data.Value).eql(value); 157 | }); 158 | 159 | it("should create kv pair with null value", async function () { 160 | const c = this.c1; 161 | const key = "one"; 162 | const value = null; 163 | 164 | const ok = await c.kv.set(key, value); 165 | should(ok).be.true(); 166 | 167 | const data = await c.kv.get(key); 168 | should(data).have.keys( 169 | "CreateIndex", 170 | "ModifyIndex", 171 | "LockIndex", 172 | "Key", 173 | "Flags", 174 | "Value", 175 | ); 176 | should(data.Value).be.null(); 177 | }); 178 | }); 179 | 180 | describe("del", function () { 181 | it("should delete kv pair", async function () { 182 | const del = await this.c1.kv.del(this.key); 183 | should(del).equal(true); 184 | 185 | const data = await this.c1.kv.get(this.key); 186 | should.not.exist(data); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/kv.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Kv", function () { 8 | helper.setup(this); 9 | 10 | describe("get", function () { 11 | it("should work", async function () { 12 | this.nock.get("/v1/kv/key1").reply(200, [{ ok: true }]); 13 | 14 | const data = await this.consul.kv.get({ key: "key1" }); 15 | should(data).eql({ ok: true }); 16 | }); 17 | 18 | it("should return raw", async function () { 19 | this.nock.get("/v1/kv/key1?raw=true").reply(200, "value1"); 20 | 21 | const data = await this.consul.kv.get({ 22 | key: "key1", 23 | raw: true, 24 | }); 25 | should(data).eql(Buffer.from("value1")); 26 | }); 27 | 28 | it("should return handle not found", async function () { 29 | this.nock.get("/v1/kv/key1").reply(404, "value1"); 30 | 31 | const data = await this.consul.kv.get("key1"); 32 | should.not.exist(data); 33 | }); 34 | 35 | it("should decode values", async function () { 36 | this.nock 37 | .get("/v1/kv/?recurse=true") 38 | .reply(200, [{ Value: "dmFsdWUx" }, { ok: true }]); 39 | 40 | const data = await this.consul.kv.get({ recurse: true }); 41 | should(data).eql([{ Value: "value1" }, { ok: true }]); 42 | }); 43 | 44 | it("should empty response", async function () { 45 | this.nock.get("/v1/kv/?recurse=true").reply(200, []); 46 | 47 | const data = await this.consul.kv.get({ recurse: true }); 48 | should.not.exist(data); 49 | }); 50 | 51 | it("should handle errors", async function () { 52 | this.nock.get("/v1/kv/key1").reply(500); 53 | 54 | try { 55 | await this.consul.kv.get("key1"); 56 | should.ok(false); 57 | } catch (err) { 58 | should(err).have.property( 59 | "message", 60 | "consul: kv.get: internal server error", 61 | ); 62 | } 63 | }); 64 | }); 65 | 66 | describe("keys", function () { 67 | it("should work", async function () { 68 | this.nock.get("/v1/kv/key1?keys=true&separator=%3A").reply(200, ["test"]); 69 | 70 | const data = await this.consul.kv.keys({ key: "key1", separator: ":" }); 71 | should(data).eql(["test"]); 72 | }); 73 | 74 | it("should work string argument", async function () { 75 | this.nock.get("/v1/kv/key1?keys=true").reply(200, ["test"]); 76 | 77 | const data = await this.consul.kv.keys("key1"); 78 | should(data).eql(["test"]); 79 | }); 80 | 81 | it("should work with no arguments", async function () { 82 | this.nock.get("/v1/kv/?keys=true").reply(200, ["test"]); 83 | 84 | const data = await this.consul.kv.keys(); 85 | should(data).eql(["test"]); 86 | }); 87 | }); 88 | 89 | describe("set", function () { 90 | it("should work", async function () { 91 | this.nock 92 | .put("/v1/kv/key1?cas=1&flags=2&acquire=session", "value1") 93 | .reply(200, { ok: true }); 94 | 95 | const data = await this.consul.kv.set({ 96 | key: "key1", 97 | value: "value1", 98 | cas: 1, 99 | flags: 2, 100 | acquire: "session", 101 | }); 102 | should(data).eql({ ok: true }); 103 | }); 104 | 105 | it("should work with 4 arguments", async function () { 106 | this.nock.put("/v1/kv/key1?release=session", "").reply(200, { ok: true }); 107 | 108 | const opts = { release: "session" }; 109 | const data = await this.consul.kv.set("key1", null, opts); 110 | should(data).eql({ ok: true }); 111 | }); 112 | 113 | it("should work with 3 arguments", async function () { 114 | this.nock.put("/v1/kv/key1", "value1").reply(200, { ok: true }); 115 | 116 | const data = await this.consul.kv.set("key1", "value1"); 117 | should(data).eql({ ok: true }); 118 | }); 119 | 120 | it("should require key", async function () { 121 | try { 122 | await this.consul.kv.set({}); 123 | should.ok(false); 124 | } catch (err) { 125 | should(err).have.property("message", "consul: kv.set: key required"); 126 | } 127 | }); 128 | 129 | it("should require value", async function () { 130 | try { 131 | await this.consul.kv.set({ key: "key1" }); 132 | should.ok(false); 133 | } catch (err) { 134 | should(err).have.property("message", "consul: kv.set: value required"); 135 | } 136 | }); 137 | }); 138 | 139 | describe("del", function () { 140 | it("should work", async function () { 141 | this.nock.delete("/v1/kv/key1?cas=1").reply(200, true); 142 | 143 | const result = await this.consul.kv.del({ key: "key1", cas: 1 }); 144 | should(result).equal(true); 145 | }); 146 | 147 | it("should work using delete alias", async function () { 148 | this.nock.delete("/v1/kv/key1?cas=1").reply(200, true); 149 | 150 | const result = await this.consul.kv.delete({ key: "key1", cas: 1 }); 151 | should(result).equal(true); 152 | }); 153 | 154 | it("should work with string", async function () { 155 | this.nock.delete("/v1/kv/key1").reply(200); 156 | 157 | await this.consul.kv.del("key1"); 158 | }); 159 | 160 | it("should work support recurse", async function () { 161 | this.nock.delete("/v1/kv/?recurse=true").reply(200); 162 | 163 | await this.consul.kv.del({ recurse: true }); 164 | }); 165 | }); 166 | }); 167 | -------------------------------------------------------------------------------- /test/session.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Session", function () { 8 | helper.setup(this); 9 | 10 | describe("create", function () { 11 | it("should work", async function () { 12 | this.nock 13 | .put("/v1/session/create", { 14 | LockDelay: "15s", 15 | Name: "name1", 16 | Node: "node1", 17 | Checks: ["a", "b"], 18 | Behavior: "release", 19 | TTL: "5s", 20 | }) 21 | .reply(200, { ok: true }); 22 | 23 | const data = await this.consul.session.create({ 24 | lockdelay: "15s", 25 | name: "name1", 26 | node: "node1", 27 | checks: ["a", "b"], 28 | behavior: "release", 29 | ttl: "5s", 30 | }); 31 | should(data).eql({ ok: true }); 32 | }); 33 | 34 | it("should work with no arguments", async function () { 35 | this.nock.put("/v1/session/create", {}).reply(200, { ok: true }); 36 | 37 | const data = await this.consul.session.create(); 38 | should(data).eql({ ok: true }); 39 | }); 40 | }); 41 | 42 | describe("destroy", function () { 43 | it("should work", async function () { 44 | this.nock.put("/v1/session/destroy/123").reply(200); 45 | 46 | await this.consul.session.destroy({ id: "123" }); 47 | }); 48 | 49 | it("should work with string ID", async function () { 50 | this.nock.put("/v1/session/destroy/123").reply(200); 51 | 52 | await this.consul.session.destroy("123"); 53 | }); 54 | 55 | it("should require ID", async function () { 56 | try { 57 | await this.consul.session.destroy({}); 58 | should.ok(false); 59 | } catch (err) { 60 | should(err).have.property( 61 | "message", 62 | "consul: session.destroy: id required", 63 | ); 64 | should(err).have.property("isValidation", true); 65 | } 66 | }); 67 | }); 68 | 69 | describe("info", function () { 70 | it("should work", async function () { 71 | this.nock.get("/v1/session/info/123").reply(200, [{ ok: true }]); 72 | 73 | const data = await this.consul.session.info({ id: "123" }); 74 | should(data).eql({ ok: true }); 75 | }); 76 | 77 | it("should work using get alias", async function () { 78 | this.nock.get("/v1/session/info/123").reply(200, [{ ok: true }]); 79 | 80 | const data = await this.consul.session.get({ id: "123" }); 81 | should(data).eql({ ok: true }); 82 | }); 83 | 84 | it("should work with string ID", async function () { 85 | this.nock.get("/v1/session/info/123").reply(200, []); 86 | 87 | const data = await this.consul.session.info("123"); 88 | should.not.exist(data); 89 | }); 90 | 91 | it("should require ID", async function () { 92 | try { 93 | await this.consul.session.info({}); 94 | should.ok(false); 95 | } catch (err) { 96 | should(err).have.property( 97 | "message", 98 | "consul: session.info: id required", 99 | ); 100 | should(err).have.property("isValidation", true); 101 | } 102 | }); 103 | }); 104 | 105 | describe("node", function () { 106 | it("should work", async function () { 107 | this.nock.get("/v1/session/node/node1").reply(200, { ok: true }); 108 | 109 | const data = await this.consul.session.node({ node: "node1" }); 110 | should(data).eql({ ok: true }); 111 | }); 112 | 113 | it("should work with string ID", async function () { 114 | this.nock.get("/v1/session/node/node1").reply(200, { ok: true }); 115 | 116 | const data = await this.consul.session.node("node1"); 117 | should(data).eql({ ok: true }); 118 | }); 119 | 120 | it("should require node", async function () { 121 | try { 122 | await this.consul.session.node({}); 123 | } catch (err) { 124 | should(err).have.property( 125 | "message", 126 | "consul: session.node: node required", 127 | ); 128 | should(err).have.property("isValidation", true); 129 | } 130 | }); 131 | }); 132 | 133 | describe("list", function () { 134 | it("should work", async function () { 135 | this.nock.get("/v1/session/list").reply(200, [{ ok: true }]); 136 | 137 | const data = await this.consul.session.list({}); 138 | should(data).eql([{ ok: true }]); 139 | }); 140 | 141 | it("should work with string ID", async function () { 142 | this.nock.get("/v1/session/list").reply(200, [{ ok: true }]); 143 | 144 | const data = await this.consul.session.list(); 145 | should(data).eql([{ ok: true }]); 146 | }); 147 | }); 148 | 149 | describe("renew", function () { 150 | it("should work", async function () { 151 | this.nock.put("/v1/session/renew/123").reply(200, { ok: true }); 152 | 153 | const data = await this.consul.session.renew({ id: "123" }); 154 | should(data).eql({ ok: true }); 155 | }); 156 | 157 | it("should work with string", async function () { 158 | this.nock.put("/v1/session/renew/123").reply(200, { ok: true }); 159 | 160 | const data = await this.consul.session.renew("123"); 161 | should(data).eql({ ok: true }); 162 | }); 163 | 164 | it("should require ID", async function () { 165 | try { 166 | await this.consul.session.renew({}); 167 | } catch (err) { 168 | should(err).have.property( 169 | "message", 170 | "consul: session.renew: id required", 171 | ); 172 | should(err).have.property("isValidation", true); 173 | } 174 | }); 175 | }); 176 | }); 177 | -------------------------------------------------------------------------------- /lib/query.js: -------------------------------------------------------------------------------- 1 | const errors = require("./errors"); 2 | const utils = require("./utils"); 3 | 4 | class Query { 5 | constructor(consul) { 6 | this.consul = consul; 7 | } 8 | 9 | /** 10 | * Lists all queries 11 | */ 12 | async list(opts) { 13 | opts = utils.normalizeKeys(opts); 14 | opts = utils.defaults(opts, this.consul._defaults); 15 | 16 | const req = { 17 | name: "query.list", 18 | path: "/query", 19 | }; 20 | 21 | utils.options(req, opts); 22 | 23 | return await this.consul._get(req, utils.body); 24 | } 25 | 26 | /** 27 | * Create a new query 28 | */ 29 | async create(opts) { 30 | if (typeof opts === "string") { 31 | opts = { service: { service: opts } }; 32 | } 33 | 34 | opts = utils.normalizeKeys(opts); 35 | opts = utils.defaults(opts, this.consul._defaults); 36 | 37 | const req = { 38 | name: "query.create", 39 | path: "/query", 40 | query: {}, 41 | type: "json", 42 | }; 43 | 44 | this._params(req, opts); 45 | 46 | if (!req.body.Service || !req.body.Service.Service) { 47 | throw this.consul._err(errors.Validation("service required"), req); 48 | } 49 | 50 | utils.options(req, opts, { near: true }); 51 | 52 | return await this.consul._post(req, utils.body); 53 | } 54 | 55 | /** 56 | * Gets a given query 57 | */ 58 | async get(opts) { 59 | if (typeof opts === "string") { 60 | opts = { query: opts }; 61 | } 62 | 63 | opts = utils.normalizeKeys(opts); 64 | opts = utils.defaults(opts, this.consul._defaults); 65 | 66 | const req = { 67 | name: "query.get", 68 | path: "/query/{query}", 69 | params: { query: opts.query }, 70 | query: {}, 71 | }; 72 | 73 | if (!opts.query) { 74 | throw this.consul._err(errors.Validation("query required"), req); 75 | } 76 | 77 | utils.options(req, opts); 78 | 79 | return await this.consul._get(req, utils.bodyItem); 80 | } 81 | 82 | /** 83 | * Update existing query 84 | */ 85 | async update(opts) { 86 | opts = utils.normalizeKeys(opts); 87 | opts = utils.defaults(opts, this.consul._defaults); 88 | 89 | const req = { 90 | name: "query.update", 91 | path: "/query/{query}", 92 | params: { query: opts.query }, 93 | query: {}, 94 | type: "json", 95 | }; 96 | 97 | if (!opts.query) { 98 | throw this.consul._err(errors.Validation("query required"), req); 99 | } 100 | 101 | this._params(req, opts); 102 | 103 | if (!req.body.Service || !req.body.Service.Service) { 104 | throw this.consul._err(errors.Validation("service required"), req); 105 | } 106 | 107 | utils.options(req, opts, { near: true }); 108 | 109 | return await this.consul._put(req, utils.empty); 110 | } 111 | 112 | /** 113 | * Destroys a given query 114 | */ 115 | async destroy(opts) { 116 | if (typeof opts === "string") { 117 | opts = { query: opts }; 118 | } 119 | 120 | opts = utils.normalizeKeys(opts); 121 | opts = utils.defaults(opts, this.consul._defaults); 122 | 123 | const req = { 124 | name: "query.destroy", 125 | path: "/query/{query}", 126 | params: { query: opts.query }, 127 | query: {}, 128 | }; 129 | 130 | if (!opts.query) { 131 | throw this.consul._err(errors.Validation("query required"), req); 132 | } 133 | 134 | utils.options(req, opts); 135 | 136 | return await this.consul._delete(req, utils.empty); 137 | } 138 | 139 | /** 140 | * Executes a given query 141 | */ 142 | async execute(opts) { 143 | if (typeof opts === "string") { 144 | opts = { query: opts }; 145 | } 146 | 147 | opts = utils.normalizeKeys(opts); 148 | opts = utils.defaults(opts, this.consul._defaults); 149 | 150 | const req = { 151 | name: "query.execute", 152 | path: "/query/{query}/execute", 153 | params: { query: opts.query }, 154 | query: {}, 155 | }; 156 | 157 | if (!opts.query) { 158 | throw this.consul._err(errors.Validation("query required"), req); 159 | } 160 | 161 | utils.options(req, opts); 162 | 163 | return await this.consul._get(req, utils.body); 164 | } 165 | 166 | /** 167 | * Explain a given query 168 | */ 169 | async explain(opts) { 170 | if (typeof opts === "string") { 171 | opts = { query: opts }; 172 | } 173 | 174 | opts = utils.normalizeKeys(opts); 175 | opts = utils.defaults(opts, this.consul._defaults); 176 | 177 | const req = { 178 | name: "query.explain", 179 | path: "/query/{query}/explain", 180 | params: { query: opts.query }, 181 | query: {}, 182 | }; 183 | 184 | if (!opts.query) { 185 | throw this.consul._err(errors.Validation("query required"), req); 186 | } 187 | 188 | utils.options(req, opts); 189 | 190 | return await this.consul._get(req, utils.bodyItem); 191 | } 192 | 193 | /** 194 | * Generate body for query create and update 195 | */ 196 | _params(req, opts) { 197 | const body = req.body || {}; 198 | 199 | if (opts.name) body.Name = opts.name; 200 | if (opts.session) body.Session = opts.session; 201 | if (opts.token) { 202 | body.Token = opts.token; 203 | delete opts.token; 204 | } 205 | if (opts.near) body.Near = opts.near; 206 | if (opts.template) { 207 | const template = utils.normalizeKeys(opts.template); 208 | if (template.type || template.regexp) { 209 | body.Template = {}; 210 | if (template.type) body.Template.Type = template.type; 211 | if (template.regexp) body.Template.Regexp = template.regexp; 212 | } 213 | } 214 | if (opts.service) { 215 | const service = utils.normalizeKeys(opts.service); 216 | body.Service = {}; 217 | if (service.service) body.Service.Service = service.service; 218 | if (service.failover) { 219 | const failover = utils.normalizeKeys(service.failover); 220 | if (typeof failover.nearestn === "number" || failover.datacenters) { 221 | body.Service.Failover = {}; 222 | if (typeof failover.nearestn === "number") { 223 | body.Service.Failover.NearestN = failover.nearestn; 224 | } 225 | if (failover.datacenters) { 226 | body.Service.Failover.Datacenters = failover.datacenters; 227 | } 228 | } 229 | } 230 | if (typeof service.onlypassing === "boolean") { 231 | body.Service.OnlyPassing = service.onlypassing; 232 | } 233 | if (service.tags) body.Service.Tags = service.tags; 234 | } 235 | if (opts.dns) { 236 | const dns = utils.normalizeKeys(opts.dns); 237 | if (dns.ttl) body.DNS = { TTL: dns.ttl }; 238 | } 239 | 240 | req.body = body; 241 | } 242 | } 243 | 244 | exports.Query = Query; 245 | -------------------------------------------------------------------------------- /test/acl.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Acl", function () { 8 | helper.setup(this); 9 | 10 | describe("bootstrap", function () { 11 | it("should work", async function () { 12 | this.nock.put("/v1/acl/bootstrap").reply(200, { ok: true }); 13 | 14 | const data = await this.consul.acl.bootstrap({}); 15 | should(data).eql({ ok: true }); 16 | }); 17 | 18 | describe("legacy", function () { 19 | describe("create", function () { 20 | it("should work", async function () { 21 | this.nock 22 | .put("/v1/acl/create", { 23 | Name: "name", 24 | Type: "type", 25 | Rules: "rules", 26 | }) 27 | .reply(200, { ok: true }); 28 | 29 | const data = await this.consul.acl.legacy.create({ 30 | name: "name", 31 | type: "type", 32 | rules: "rules", 33 | }); 34 | should(data).eql({ ok: true }); 35 | }); 36 | 37 | it("should work with no arguments", async function () { 38 | this.nock.put("/v1/acl/create", {}).reply(200, { ok: true }); 39 | 40 | const data = await this.consul.acl.legacy.create(); 41 | should(data).eql({ ok: true }); 42 | }); 43 | }); 44 | 45 | describe("update", function () { 46 | it("should work", async function () { 47 | this.nock 48 | .put("/v1/acl/update", { 49 | ID: "123", 50 | Name: "name", 51 | Type: "type", 52 | Rules: "rules", 53 | }) 54 | .reply(200); 55 | 56 | await this.consul.acl.legacy.update({ 57 | id: "123", 58 | name: "name", 59 | type: "type", 60 | rules: "rules", 61 | }); 62 | }); 63 | 64 | it("should work with just ID", async function () { 65 | this.nock.put("/v1/acl/update", { ID: "123" }).reply(200); 66 | 67 | await this.consul.acl.legacy.update({ id: "123" }); 68 | }); 69 | 70 | it("should require ID", async function () { 71 | try { 72 | await this.consul.acl.legacy.update({}); 73 | should.ok(false); 74 | } catch (err) { 75 | should(err).have.property( 76 | "message", 77 | "consul: acl.legacy.update: id required", 78 | ); 79 | should(err).have.property("isValidation", true); 80 | } 81 | }); 82 | }); 83 | 84 | describe("destroy", function () { 85 | it("should work", async function () { 86 | this.nock.put("/v1/acl/destroy/123").reply(200); 87 | 88 | await this.consul.acl.legacy.destroy({ id: "123" }); 89 | }); 90 | 91 | it("should work with string ID", async function () { 92 | this.nock.put("/v1/acl/destroy/123").reply(200); 93 | 94 | await this.consul.acl.legacy.destroy("123"); 95 | }); 96 | 97 | it("should require ID", async function () { 98 | try { 99 | await this.consul.acl.legacy.destroy({}); 100 | should.ok(false); 101 | } catch (err) { 102 | should(err).have.property( 103 | "message", 104 | "consul: acl.legacy.destroy: id required", 105 | ); 106 | should(err).have.property("isValidation", true); 107 | } 108 | }); 109 | }); 110 | 111 | describe("info", function () { 112 | it("should work", async function () { 113 | this.nock.get("/v1/acl/info/123").reply(200, [{ ok: true }]); 114 | 115 | const data = await this.consul.acl.legacy.info({ id: "123" }); 116 | should(data).eql({ ok: true }); 117 | }); 118 | 119 | it("should work using get alias", async function () { 120 | this.nock.get("/v1/acl/info/123").reply(200, [{ ok: true }]); 121 | 122 | const data = await this.consul.acl.legacy.get({ id: "123" }); 123 | should(data).eql({ ok: true }); 124 | }); 125 | 126 | it("should work with string ID", async function () { 127 | this.nock.get("/v1/acl/info/123").reply(200, [{ ok: true }]); 128 | 129 | const data = await this.consul.acl.legacy.info("123"); 130 | should(data).eql({ ok: true }); 131 | }); 132 | 133 | it("should require ID", async function () { 134 | try { 135 | await this.consul.acl.legacy.info({}); 136 | should.ok(false); 137 | } catch (err) { 138 | should(err).have.property( 139 | "message", 140 | "consul: acl.legacy.info: id required", 141 | ); 142 | should(err).have.property("isValidation", true); 143 | } 144 | }); 145 | }); 146 | 147 | describe("clone", function () { 148 | it("should work", async function () { 149 | this.nock.put("/v1/acl/clone/123").reply(200, { ID: "124" }); 150 | 151 | const data = await this.consul.acl.legacy.clone({ id: "123" }); 152 | should(data).eql({ ID: "124" }); 153 | }); 154 | 155 | it("should work with string ID", async function () { 156 | this.nock.put("/v1/acl/clone/123").reply(200, { ID: "124" }); 157 | 158 | const data = await this.consul.acl.legacy.clone("123"); 159 | should(data).eql({ ID: "124" }); 160 | }); 161 | 162 | it("should require ID", async function () { 163 | try { 164 | await this.consul.acl.legacy.clone({}); 165 | should.ok(false); 166 | } catch (err) { 167 | should(err).have.property( 168 | "message", 169 | "consul: acl.legacy.clone: id required", 170 | ); 171 | should(err).have.property("isValidation", true); 172 | } 173 | }); 174 | }); 175 | 176 | describe("list", function () { 177 | it("should work", async function () { 178 | this.nock.get("/v1/acl/list").reply(200, [{ ok: true }]); 179 | 180 | const data = await this.consul.acl.legacy.list({}); 181 | should(data).eql([{ ok: true }]); 182 | }); 183 | 184 | it("should work with no arguments", async function () { 185 | this.nock.get("/v1/acl/list").reply(200, [{ ok: true }]); 186 | 187 | const data = await this.consul.acl.legacy.list(); 188 | should(data).eql([{ ok: true }]); 189 | }); 190 | }); 191 | }); 192 | 193 | it("should work with no arguments", async function () { 194 | this.nock.put("/v1/acl/bootstrap").reply(200, { ok: true }); 195 | 196 | const data = await this.consul.acl.bootstrap(); 197 | should(data).eql({ ok: true }); 198 | }); 199 | }); 200 | 201 | describe("replication", function () { 202 | it("should work", async function () { 203 | this.nock.get("/v1/acl/replication?dc=dc1").reply(200, [{ ok: true }]); 204 | 205 | const data = await this.consul.acl.replication({ dc: "dc1" }); 206 | should(data).eql([{ ok: true }]); 207 | }); 208 | 209 | it("should work with no arguments", async function () { 210 | this.nock.get("/v1/acl/replication").reply(200, [{ ok: true }]); 211 | 212 | const data = await this.consul.acl.replication(); 213 | should(data).eql([{ ok: true }]); 214 | }); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /test/watch.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Watch", function () { 8 | helper.setup(this); 9 | 10 | it("should work", function (done) { 11 | this.nock 12 | .get("/v1/kv/key1?index=0&wait=30s") 13 | .reply(200, [{ n: 1 }], { "X-Consul-Index": "5" }) 14 | .get("/v1/kv/key1?index=5&wait=30s") 15 | .reply(200, [{ n: 2 }], { "X-Consul-Index": "5" }) 16 | .get("/v1/kv/key1?index=5&wait=30s") 17 | .reply(500, [{ n: 3 }]) 18 | .get("/v1/kv/key1?index=5&wait=30s") 19 | .reply(200, [{ n: 4 }], { "X-Consul-Index": "10" }) 20 | .get("/v1/kv/key1?index=10&wait=30s") 21 | .reply(200, [{ n: 5 }], { "X-Consul-Index": "15" }) 22 | .get("/v1/kv/key1?index=15&wait=30s") 23 | .reply(200, [{ n: 6 }], { "X-Consul-Index": "14" }) 24 | .get("/v1/kv/key1?index=0&wait=30s") 25 | .reply(200, [{ n: 7 }], { "X-Consul-Index": "6" }) 26 | .get("/v1/kv/key1?index=6&wait=30s") 27 | .reply(200, [{ n: 8 }], { "X-Consul-Index": "0" }) 28 | .get("/v1/kv/key1?index=6&wait=30s") 29 | .reply(400); 30 | 31 | const watch = this.consul.watch({ 32 | method: this.consul.kv.get, 33 | options: { key: "key1" }, 34 | }); 35 | 36 | let doneCalled = false; 37 | const safeDone = (err) => { 38 | if (doneCalled) return; 39 | doneCalled = true; 40 | done(err); 41 | watch.end(); 42 | }; 43 | 44 | should(watch.isRunning()).be.true(); 45 | should(watch.updateTime()).be.undefined(); 46 | 47 | // make tests run fast 48 | watch._wait = () => { 49 | return 1; 50 | }; 51 | 52 | const errors = []; 53 | const list = []; 54 | const called = {}; 55 | 56 | watch.on("error", (err) => { 57 | if (err.message.includes("Nock")) { 58 | return safeDone(err); 59 | } 60 | 61 | called.error = true; 62 | 63 | errors.push(err); 64 | }); 65 | 66 | watch.on("cancel", () => { 67 | called.cancel = true; 68 | 69 | try { 70 | should(list).eql([1, 4, 5, 6, 7]); 71 | 72 | watch._run(); 73 | watch._err(); 74 | 75 | watch.end(); 76 | should(watch.isRunning()).be.false(); 77 | } catch (err) { 78 | safeDone(err); 79 | } 80 | }); 81 | 82 | watch.on("change", (data, res) => { 83 | called.change = true; 84 | 85 | list.push(data.n); 86 | 87 | try { 88 | switch (res.headers["x-consul-index"]) { 89 | case "5": 90 | should(watch.isRunning()).be.true(); 91 | should(watch.updateTime()).be.a.Number(); 92 | should(errors).be.empty(); 93 | break; 94 | case "10": 95 | should(watch.isRunning()).be.true(); 96 | should(watch.updateTime()).be.a.Number(); 97 | should(errors).have.length(1); 98 | should(errors[0]).have.property( 99 | "message", 100 | "consul: kv.get: internal server error", 101 | ); 102 | break; 103 | case "15": 104 | break; 105 | default: 106 | break; 107 | } 108 | } catch (err) { 109 | safeDone(err); 110 | } 111 | }); 112 | 113 | watch.on("end", () => { 114 | try { 115 | should(called).have.property("cancel", true); 116 | should(called).have.property("change", true); 117 | should(called).have.property("error", true); 118 | 119 | should(errors).have.length(3); 120 | should(errors[1]).have.property( 121 | "message", 122 | "Consul returned zero index value", 123 | ); 124 | 125 | safeDone(); 126 | } catch (err) { 127 | safeDone(err); 128 | } 129 | }); 130 | }); 131 | 132 | it("should error when endpoint does not support watch", function (done) { 133 | this.nock.get("/v1/agent/members?index=0&wait=30s").reply(200, [{ n: 1 }]); 134 | 135 | const watch = this.consul.watch({ 136 | method: this.consul.agent.members, 137 | }); 138 | 139 | const errors = []; 140 | const called = {}; 141 | 142 | watch.on("error", (err) => { 143 | called.error = true; 144 | 145 | errors.push(err); 146 | }); 147 | 148 | watch.on("cancel", () => { 149 | called.cancel = true; 150 | }); 151 | 152 | watch.on("change", () => { 153 | called.change = true; 154 | }); 155 | 156 | watch.on("end", () => { 157 | should(called).eql({ cancel: true, error: true }); 158 | should(errors).have.length(1); 159 | should(errors[0]).have.property("isValidation", true); 160 | should(errors[0]).have.property("message", "Watch not supported"); 161 | 162 | done(); 163 | }); 164 | }); 165 | 166 | it("should use maxAttempts", function (done) { 167 | this.nock 168 | .get("/v1/kv/key1?index=0&wait=30s") 169 | .reply(500) 170 | .get("/v1/kv/key1?index=0&wait=30s") 171 | .reply(500); 172 | 173 | const watch = this.consul.watch({ 174 | method: this.consul.kv.get, 175 | options: { key: "key1" }, 176 | backoffFactor: 0, 177 | maxAttempts: 2, 178 | }); 179 | 180 | should(watch.isRunning()).be.true; 181 | should(watch.updateTime()).be.undefined; 182 | 183 | const errors = []; 184 | 185 | watch.on("error", (err) => { 186 | errors.push(err); 187 | }); 188 | 189 | watch.on("end", () => { 190 | should(errors).have.length(3); 191 | 192 | done(); 193 | }); 194 | }); 195 | 196 | it("should require method", function () { 197 | should(() => { 198 | this.consul.watch({}); 199 | }).throw("method required"); 200 | }); 201 | 202 | it("should set timeout correctly", async function () { 203 | const test = (options) => { 204 | const opts = { key: "test", method: async () => null }; 205 | if (options) opts.options = options; 206 | return this.consul.watch(opts)._options.timeout; 207 | }; 208 | 209 | should(test()).equal(33000); 210 | should(test({ timeout: 1000 })).equal(1000); 211 | should(test({ timeout: "1s" })).equal("1s"); 212 | should(test({ wait: "60s" })).equal(66000); 213 | should(test({ wait: "1s" })).equal(1500); 214 | should(test({ wait: "33s" })).equal(36300); 215 | }); 216 | 217 | describe("wait", function () { 218 | it("should work", function () { 219 | const watch = this.consul.watch({ 220 | key: "test", 221 | method: async () => null, 222 | }); 223 | 224 | should(watch._wait()).equal(200); 225 | should(watch._wait()).equal(400); 226 | should(watch._wait()).equal(800); 227 | should(watch._wait()).equal(1600); 228 | should(watch._wait()).equal(3200); 229 | 230 | for (let i = 0; i < 100; i++) { 231 | should(watch._wait()).be.below(30001); 232 | } 233 | }); 234 | 235 | it("should use custom backoff settings", function () { 236 | const watch = this.consul.watch({ 237 | key: "test", 238 | method: async () => null, 239 | backoffFactor: 500, 240 | backoffMax: 20000, 241 | }); 242 | 243 | should(watch._wait()).equal(1000); 244 | should(watch._wait()).equal(2000); 245 | should(watch._wait()).equal(4000); 246 | should(watch._wait()).equal(8000); 247 | should(watch._wait()).equal(16000); 248 | 249 | for (let i = 0; i < 100; i++) { 250 | should(watch._wait()).be.below(20001); 251 | } 252 | }); 253 | }); 254 | 255 | describe("err", function () { 256 | it("should handle method throw", function (done) { 257 | const watch = this.consul.watch({ 258 | method: async () => { 259 | throw new Error("ok"); 260 | }, 261 | }); 262 | 263 | watch.on("error", (err) => { 264 | watch.end(); 265 | if (err.message === "ok") { 266 | done(); 267 | } 268 | }); 269 | }); 270 | }); 271 | }); 272 | -------------------------------------------------------------------------------- /test/catalog.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Catalog", function () { 8 | helper.setup(this); 9 | 10 | describe("register", function () { 11 | it("should work (http) with just string", async function () { 12 | this.nock 13 | .put("/v1/catalog/register", { 14 | Node: "node", 15 | Address: "127.0.0.1", 16 | }) 17 | .reply(200); 18 | 19 | await this.consul.catalog.register("node"); 20 | }); 21 | 22 | it("should error (http) on missing required fields", async function () { 23 | try { 24 | await this.consul.catalog.register({ 25 | name: "test", 26 | serviceid: "service", 27 | }); 28 | should.ok(false); 29 | } catch (err) { 30 | should(err).property( 31 | "message", 32 | "consul: catalog.register: node and address required", 33 | ); 34 | } 35 | }); 36 | }); 37 | 38 | describe("deregister", function () { 39 | it("should work (http) with just string", async function () { 40 | this.nock 41 | .put("/v1/catalog/deregister", { 42 | Node: "node", 43 | }) 44 | .reply(200); 45 | 46 | await this.consul.catalog.deregister("node"); 47 | }); 48 | 49 | it("should error (http) on missing required fields", async function () { 50 | try { 51 | await this.consul.catalog.deregister({ 52 | name: "test", 53 | }); 54 | should.ok(false); 55 | } catch (err) { 56 | should(err).property( 57 | "message", 58 | "consul: catalog.deregister: node required", 59 | ); 60 | } 61 | }); 62 | }); 63 | 64 | describe("datacenters", function () { 65 | it("should work", async function () { 66 | this.nock.get("/v1/catalog/datacenters").reply(200, [{ ok: true }]); 67 | 68 | const data = await this.consul.catalog.datacenters({}); 69 | should(data).eql([{ ok: true }]); 70 | }); 71 | 72 | it("should work with no arguments", async function () { 73 | this.nock.get("/v1/catalog/datacenters").reply(200, [{ ok: true }]); 74 | 75 | const data = await this.consul.catalog.datacenters(); 76 | should(data).eql([{ ok: true }]); 77 | }); 78 | }); 79 | 80 | describe("nodes", function () { 81 | it("should work", async function () { 82 | this.nock.get("/v1/catalog/nodes").reply(200, [{ ok: true }]); 83 | 84 | const data = await this.consul.catalog.nodes({}); 85 | should(data).eql([{ ok: true }]); 86 | }); 87 | 88 | it("should work with just string", async function () { 89 | this.nock.get("/v1/catalog/nodes?dc=dc1").reply(200, [{ ok: true }]); 90 | 91 | const data = await this.consul.catalog.nodes("dc1"); 92 | should(data).eql([{ ok: true }]); 93 | }); 94 | 95 | it("should work with no arguments", async function () { 96 | this.nock.get("/v1/catalog/nodes").reply(200, [{ ok: true }]); 97 | 98 | const data = await this.consul.catalog.nodes(); 99 | should(data).eql([{ ok: true }]); 100 | }); 101 | }); 102 | 103 | describe("node", function () { 104 | describe("list", function () { 105 | it("should work", async function () { 106 | this.nock.get("/v1/catalog/nodes").reply(200, [{ ok: true }]); 107 | 108 | const data = await this.consul.catalog.node.list({}); 109 | should(data).eql([{ ok: true }]); 110 | }); 111 | 112 | it("should work with just string", async function () { 113 | this.nock.get("/v1/catalog/nodes?dc=dc1").reply(200, [{ ok: true }]); 114 | 115 | const data = await this.consul.catalog.node.list("dc1"); 116 | should(data).eql([{ ok: true }]); 117 | }); 118 | 119 | it("should work with no arguments", async function () { 120 | this.nock.get("/v1/catalog/nodes").reply(200, [{ ok: true }]); 121 | 122 | const data = await this.consul.catalog.node.list(); 123 | should(data).eql([{ ok: true }]); 124 | }); 125 | }); 126 | 127 | describe("services", function () { 128 | it("should work", async function () { 129 | this.nock.get("/v1/catalog/node/node1").reply(200, [{ ok: true }]); 130 | 131 | const data = await this.consul.catalog.node.services({ node: "node1" }); 132 | should(data).eql([{ ok: true }]); 133 | }); 134 | 135 | it("should work with just string", async function () { 136 | this.nock.get("/v1/catalog/node/node1").reply(200, [{ ok: true }]); 137 | 138 | const data = await this.consul.catalog.node.services("node1"); 139 | should(data).eql([{ ok: true }]); 140 | }); 141 | 142 | it("should require node", async function () { 143 | try { 144 | await this.consul.catalog.node.services({}); 145 | should.ok(false); 146 | } catch (err) { 147 | should(err).property( 148 | "message", 149 | "consul: catalog.node.services: node required", 150 | ); 151 | } 152 | }); 153 | }); 154 | }); 155 | 156 | describe("services", function () { 157 | it("should work", async function () { 158 | this.nock.get("/v1/catalog/services").reply(200, [{ ok: true }]); 159 | 160 | const data = await this.consul.catalog.services({}); 161 | should(data).eql([{ ok: true }]); 162 | }); 163 | 164 | it("should work with just string", async function () { 165 | this.nock.get("/v1/catalog/services?dc=dc1").reply(200, [{ ok: true }]); 166 | 167 | const data = await this.consul.catalog.services("dc1"); 168 | should(data).eql([{ ok: true }]); 169 | }); 170 | 171 | it("should work with no arguments", async function () { 172 | this.nock.get("/v1/catalog/services").reply(200, [{ ok: true }]); 173 | 174 | const data = await this.consul.catalog.services(); 175 | should(data).eql([{ ok: true }]); 176 | }); 177 | }); 178 | 179 | describe("services", function () { 180 | describe("list", function () { 181 | it("should work", async function () { 182 | this.nock.get("/v1/catalog/services").reply(200, [{ ok: true }]); 183 | 184 | const data = await this.consul.catalog.service.list({}); 185 | should(data).eql([{ ok: true }]); 186 | }); 187 | 188 | it("should work with just string", async function () { 189 | this.nock.get("/v1/catalog/services?dc=dc1").reply(200, [{ ok: true }]); 190 | 191 | const data = await this.consul.catalog.service.list("dc1"); 192 | should(data).eql([{ ok: true }]); 193 | }); 194 | 195 | it("should work with no arguments", async function () { 196 | this.nock.get("/v1/catalog/services").reply(200, [{ ok: true }]); 197 | 198 | const data = await this.consul.catalog.service.list(); 199 | should(data).eql([{ ok: true }]); 200 | }); 201 | }); 202 | 203 | describe("nodes", function () { 204 | it("should work", async function () { 205 | this.nock 206 | .get("/v1/catalog/service/service1?tag=web") 207 | .reply(200, [{ ok: true }]); 208 | 209 | const data = await this.consul.catalog.service.nodes({ 210 | service: "service1", 211 | tag: "web", 212 | }); 213 | should(data).eql([{ ok: true }]); 214 | }); 215 | 216 | it("should work with just string", async function () { 217 | this.nock 218 | .get("/v1/catalog/service/service1") 219 | .reply(200, [{ ok: true }]); 220 | 221 | const data = await this.consul.catalog.service.nodes("service1"); 222 | should(data).eql([{ ok: true }]); 223 | }); 224 | 225 | it("should require service", async function () { 226 | try { 227 | await this.consul.catalog.service.nodes({}); 228 | should.ok(false); 229 | } catch (err) { 230 | should(err).property( 231 | "message", 232 | "consul: catalog.service.nodes: service required", 233 | ); 234 | } 235 | }); 236 | }); 237 | 238 | describe("connect nodes", function () { 239 | it("should work with just string", async function () { 240 | this.nock 241 | .get("/v1/catalog/connect/service1") 242 | .reply(200, [{ ok: true }]); 243 | 244 | const data = await this.consul.catalog.connect.nodes("service1"); 245 | should(data).eql([{ ok: true }]); 246 | }); 247 | 248 | it("should require service", async function () { 249 | try { 250 | await this.consul.catalog.connect.nodes({}); 251 | } catch (err) { 252 | should(err).property( 253 | "message", 254 | "consul: catalog.connect.nodes: service required", 255 | ); 256 | } 257 | }); 258 | }); 259 | }); 260 | }); 261 | -------------------------------------------------------------------------------- /test/query.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Query", function () { 8 | helper.setup(this); 9 | 10 | describe("list", function () { 11 | it("should work", async function () { 12 | this.nock 13 | .get("/v1/query") 14 | .reply(200, { ok: true }, { "x-consul-token": "token1" }); 15 | 16 | const data = await this.consul.query.list({ token: "token1" }); 17 | should(data).eql({ ok: true }); 18 | }); 19 | 20 | it("should work with no options", async function () { 21 | this.nock.get("/v1/query").reply(200, { ok: true }); 22 | 23 | const data = await this.consul.query.list(); 24 | should(data).eql({ ok: true }); 25 | }); 26 | }); 27 | 28 | describe("create", function () { 29 | it("should work", async function () { 30 | this.nock 31 | .post("/v1/query", { 32 | Name: "name1", 33 | Session: "session1", 34 | Token: "token1", 35 | Near: "near1", 36 | Template: { 37 | Type: "type1", 38 | Regexp: "regexp1", 39 | }, 40 | Service: { 41 | Service: "service1", 42 | Failover: { 43 | NearestN: 2, 44 | Datacenters: ["dc1"], 45 | }, 46 | OnlyPassing: true, 47 | Tags: ["tag1"], 48 | }, 49 | DNS: { 50 | TTL: "9s", 51 | }, 52 | }) 53 | .reply(200, { ok: true }); 54 | 55 | const data = await this.consul.query.create({ 56 | name: "name1", 57 | session: "session1", 58 | token: "token1", 59 | near: "near1", 60 | template: { 61 | type: "type1", 62 | regexp: "regexp1", 63 | }, 64 | service: { 65 | service: "service1", 66 | failover: { 67 | nearestn: 2, 68 | datacenters: ["dc1"], 69 | }, 70 | onlypassing: true, 71 | tags: ["tag1"], 72 | }, 73 | dns: { 74 | ttl: "9s", 75 | }, 76 | }); 77 | should(data).eql({ ok: true }); 78 | }); 79 | 80 | it("should work with just service", async function () { 81 | this.nock 82 | .post("/v1/query", { 83 | Service: { 84 | Service: "service1", 85 | }, 86 | }) 87 | .reply(200, { ok: true }); 88 | 89 | const data = await this.consul.query.create("service1"); 90 | should(data).eql({ ok: true }); 91 | }); 92 | 93 | it("should require service", async function () { 94 | try { 95 | await this.consul.query.create({}); 96 | should.ok(false); 97 | } catch (err) { 98 | should(err).have.property( 99 | "message", 100 | "consul: query.create: service required", 101 | ); 102 | } 103 | }); 104 | }); 105 | 106 | describe("get", function () { 107 | it("should work", async function () { 108 | this.nock.get("/v1/query/query1").reply(200, [{ ok: true }]); 109 | 110 | const data = await this.consul.query.get("query1"); 111 | should(data).eql({ ok: true }); 112 | }); 113 | 114 | it("should require query", async function () { 115 | try { 116 | await this.consul.query.get({}); 117 | should.ok(false); 118 | } catch (err) { 119 | should(err).have.property( 120 | "message", 121 | "consul: query.get: query required", 122 | ); 123 | } 124 | }); 125 | }); 126 | 127 | describe("update", function () { 128 | it("should work", async function () { 129 | this.nock 130 | .put("/v1/query/query1", { 131 | Name: "name1", 132 | Session: "session1", 133 | Token: "token1", 134 | Near: "near1", 135 | Template: { 136 | Type: "type1", 137 | Regexp: "regexp1", 138 | }, 139 | Service: { 140 | Service: "service1", 141 | Failover: { 142 | NearestN: 2, 143 | Datacenters: ["dc1"], 144 | }, 145 | OnlyPassing: true, 146 | Tags: ["tag1"], 147 | }, 148 | DNS: { 149 | TTL: "9s", 150 | }, 151 | }) 152 | .reply(200); 153 | 154 | await this.consul.query.update({ 155 | query: "query1", 156 | name: "name1", 157 | session: "session1", 158 | token: "token1", 159 | near: "near1", 160 | template: { 161 | type: "type1", 162 | regexp: "regexp1", 163 | }, 164 | service: { 165 | service: "service1", 166 | failover: { 167 | nearestn: 2, 168 | datacenters: ["dc1"], 169 | }, 170 | onlypassing: true, 171 | tags: ["tag1"], 172 | }, 173 | dns: { 174 | ttl: "9s", 175 | }, 176 | }); 177 | }); 178 | 179 | it("should require query", async function () { 180 | try { 181 | await this.consul.query.update(); 182 | should.ok(false); 183 | } catch (err) { 184 | should(err).have.property( 185 | "message", 186 | "consul: query.update: query required", 187 | ); 188 | } 189 | }); 190 | 191 | it("should require service", async function () { 192 | try { 193 | await this.consul.query.update({ query: "query1", service: {} }); 194 | should.ok(false); 195 | } catch (err) { 196 | should(err).have.property( 197 | "message", 198 | "consul: query.update: service required", 199 | ); 200 | } 201 | }); 202 | }); 203 | 204 | describe("destroy", function () { 205 | it("should work", async function () { 206 | this.nock.delete("/v1/query/query1").reply(200, { ok: true }); 207 | 208 | await this.consul.query.destroy("query1"); 209 | }); 210 | 211 | it("should require query", async function () { 212 | try { 213 | await this.consul.query.destroy({}); 214 | should.ok(false); 215 | } catch (err) { 216 | should(err).have.property( 217 | "message", 218 | "consul: query.destroy: query required", 219 | ); 220 | } 221 | }); 222 | }); 223 | 224 | describe("execute", function () { 225 | it("should work", async function () { 226 | this.nock.get("/v1/query/query1/execute").reply(200, { ok: true }); 227 | 228 | await this.consul.query.execute("query1"); 229 | }); 230 | 231 | it("should require query", async function () { 232 | try { 233 | await this.consul.query.execute({}); 234 | should.ok(false); 235 | } catch (err) { 236 | should(err).have.property( 237 | "message", 238 | "consul: query.execute: query required", 239 | ); 240 | } 241 | }); 242 | }); 243 | 244 | describe("explain", function () { 245 | it("should work", async function () { 246 | this.nock.get("/v1/query/query1/explain").reply(200, { ok: true }); 247 | 248 | await this.consul.query.explain("query1"); 249 | }); 250 | 251 | it("should require query", async function () { 252 | try { 253 | await this.consul.query.explain({}); 254 | should.ok(false); 255 | } catch (err) { 256 | should(err).have.property( 257 | "message", 258 | "consul: query.explain: query required", 259 | ); 260 | } 261 | }); 262 | }); 263 | 264 | describe("params", function () { 265 | it("should work", function () { 266 | let req = {}; 267 | let opts = { 268 | name: "name1", 269 | session: "session1", 270 | token: "token1", 271 | near: "near1", 272 | template: { 273 | regexp: "regexp1", 274 | }, 275 | service: { 276 | service: "service1", 277 | failover: { 278 | datacenters: ["dc1"], 279 | }, 280 | onlypassing: true, 281 | tags: ["tag1"], 282 | }, 283 | dns: { 284 | ttl: "9s", 285 | }, 286 | }; 287 | 288 | this.consul.query._params(req, opts); 289 | 290 | should(req).eql({ 291 | body: { 292 | Name: "name1", 293 | Session: "session1", 294 | Token: "token1", 295 | Near: "near1", 296 | Template: { 297 | Regexp: "regexp1", 298 | }, 299 | Service: { 300 | Service: "service1", 301 | Failover: { 302 | Datacenters: ["dc1"], 303 | }, 304 | OnlyPassing: true, 305 | Tags: ["tag1"], 306 | }, 307 | DNS: { 308 | TTL: "9s", 309 | }, 310 | }, 311 | }); 312 | 313 | req = {}; 314 | opts = { 315 | template: { type: "type1" }, 316 | service: { 317 | service: "service1", 318 | failover: { nearestn: 0 }, 319 | }, 320 | }; 321 | 322 | this.consul.query._params(req, opts); 323 | 324 | should(req).eql({ 325 | body: { 326 | Template: { Type: "type1" }, 327 | Service: { 328 | Service: "service1", 329 | Failover: { NearestN: 0 }, 330 | }, 331 | }, 332 | }); 333 | 334 | req = {}; 335 | opts = { 336 | template: {}, 337 | service: { 338 | failover: {}, 339 | }, 340 | dns: {}, 341 | }; 342 | 343 | this.consul.query._params(req, opts); 344 | 345 | should(req).eql({ 346 | body: { 347 | Service: {}, 348 | }, 349 | }); 350 | }); 351 | 352 | it("should handle token", function () { 353 | const req = {}; 354 | const opts = { service: { service: "service1" }, token: "token1" }; 355 | 356 | this.consul.query._params(req, opts); 357 | 358 | should(req).eql({ 359 | body: { 360 | Token: "token1", 361 | Service: { 362 | Service: "service1", 363 | }, 364 | }, 365 | }); 366 | }); 367 | }); 368 | }); 369 | -------------------------------------------------------------------------------- /test/acceptance/agent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const async_ = require("async"); 4 | const should = require("should"); 5 | const uuid = require("uuid"); 6 | 7 | const constants = require("../../lib/constants"); 8 | 9 | const helper = require("./helper"); 10 | 11 | helper.describe("Agent", function () { 12 | before(async function () { 13 | await helper.before(this); 14 | }); 15 | 16 | after(async function () { 17 | await helper.after(this); 18 | }); 19 | 20 | describe("members", function () { 21 | it("should return members agent sees in cluster gossip pool", async function () { 22 | const data = await this.c1.agent.members(); 23 | should(data).be.instanceOf(Array); 24 | 25 | should(data.length).eql(1); 26 | should( 27 | data.map((m) => { 28 | return m.Name; 29 | }), 30 | ).containEql("node1"); 31 | }); 32 | }); 33 | 34 | describe("self", function () { 35 | it("should return information about agent", async function () { 36 | const data = await this.c1.agent.self(); 37 | should(data).be.instanceOf(Object); 38 | should(data).have.properties("Config", "Member"); 39 | 40 | should(data.Config.Server).be.true(); 41 | should(data.Config.Datacenter).eql("dc1"); 42 | should(data.Config.NodeName).eql("node1"); 43 | 44 | should(data.Member.Name).eql("node1"); 45 | should(data.Member.Addr).eql("127.0.0.1"); 46 | }); 47 | 48 | it("should work with opts", async function () { 49 | await this.c1.agent.self({}); 50 | }); 51 | }); 52 | 53 | describe("maintenance", function () { 54 | it("should set node maintenance mode", async function () { 55 | const statusChecks = await this.c1.agent.checks(); 56 | should(statusChecks).not.have.property("_node_maintenance"); 57 | 58 | await this.c1.agent.maintenance(true); 59 | 60 | const enableStatus = await this.c1.agent.checks(); 61 | should(enableStatus).have.property("_node_maintenance"); 62 | should(enableStatus._node_maintenance).have.property( 63 | "Status", 64 | "critical", 65 | ); 66 | 67 | await this.c1.agent.maintenance({ enable: false }); 68 | 69 | const disableStatus = await this.c1.agent.checks(); 70 | should(disableStatus).not.have.property("_node_maintenance"); 71 | }); 72 | 73 | it("should require valid enable", async function () { 74 | try { 75 | await this.c1.agent.maintenance({ enable: "false" }); 76 | should.ok(false); 77 | } catch (err) { 78 | should(err).have.property( 79 | "message", 80 | "consul: agent.maintenance: enable required", 81 | ); 82 | } 83 | }); 84 | }); 85 | 86 | describe("join", function () { 87 | it("should make node2 join cluster", async function () { 88 | const joinAddr = "127.0.0.1"; 89 | const joinerAddr = "127.0.0.2"; 90 | 91 | const members = await this.c1.agent.members(); 92 | const memberAddrs = members.map((m) => { 93 | return m.Addr; 94 | }); 95 | 96 | should(memberAddrs).containEql(joinAddr); 97 | should(memberAddrs).not.containEql(joinerAddr); 98 | 99 | await this.c2.agent.join({ address: joinAddr, token: "agent_master" }); 100 | }); 101 | 102 | it("should require address", async function () { 103 | try { 104 | await this.c1.agent.join({}); 105 | should.ok(false); 106 | } catch (err) { 107 | should(err).have.property( 108 | "message", 109 | "consul: agent.join: address required", 110 | ); 111 | } 112 | }); 113 | }); 114 | 115 | describe("forceLeave", function () { 116 | it("should remove node2 from the cluster", async function () { 117 | const ensureJoined = await this.c1.agent.members(); 118 | 119 | const node2 = ensureJoined.find((m) => m.Name === "node2"); 120 | should.exist(node2); 121 | should(node2.Status).eql(constants.AGENT_STATUS.indexOf("alive")); 122 | 123 | await this.c1.agent.forceLeave("node2"); 124 | 125 | await async_.retry({ times: 100, interval: 100 }, async () => { 126 | const forceLeaveMembers = await this.c1.agent.members(); 127 | const node = forceLeaveMembers.find((m) => m.Name === "node2"); 128 | const leaving = 129 | node && node.Status === constants.AGENT_STATUS.indexOf("leaving"); 130 | if (!leaving) throw new Error("Not leaving"); 131 | }); 132 | }); 133 | 134 | it("should require node", async function () { 135 | try { 136 | await this.c1.agent.forceLeave({}); 137 | should.ok(false); 138 | } catch (err) { 139 | should(err).have.property( 140 | "message", 141 | "consul: agent.forceLeave: node required", 142 | ); 143 | } 144 | }); 145 | }); 146 | 147 | describe("check", function () { 148 | before(function () { 149 | // helper function to check existence of check 150 | this.exists = async (id, exists) => { 151 | const checks = await this.c1.agent.checks(); 152 | 153 | let s = should(checks); 154 | if (!exists) s = s.not; 155 | s.have.property(id); 156 | }; 157 | 158 | this.state = async (id, state) => { 159 | const checks = await this.c1.agent.checks(); 160 | should(checks).have.property(id); 161 | 162 | const check = checks[id]; 163 | should(check.Status).eql(state); 164 | }; 165 | }); 166 | 167 | beforeEach(async function () { 168 | this.name = "check-" + uuid.v4(); 169 | this.deregister = [this.name]; 170 | 171 | const checks = await this.c1.agent.checks(); 172 | 173 | await Promise.all( 174 | Object.keys(checks).map((id) => this.c1.agent.check.deregister(id)), 175 | ); 176 | 177 | await this.c1.agent.check.register({ name: this.name, ttl: "10s" }); 178 | }); 179 | 180 | afterEach(async function () { 181 | await Promise.all( 182 | this.deregister.map((id) => this.c1.agent.check.deregister(id)), 183 | ).catch(() => null); 184 | }); 185 | 186 | describe("list", function () { 187 | it("should return agent checks", async function () { 188 | const data = await this.c1.agent.checks(this.name); 189 | should.exist(data); 190 | should(data).have.property(this.name); 191 | }); 192 | }); 193 | 194 | describe("register", function () { 195 | it("should create check", async function () { 196 | const name = "check-" + uuid.v4(); 197 | 198 | await this.exists(name, false); 199 | await this.deregister.push(name); 200 | await this.c1.agent.check.register({ name: name, ttl: "1s" }); 201 | await this.exists(name, true); 202 | }); 203 | }); 204 | 205 | describe("deregister", function () { 206 | it("should remove check", async function () { 207 | await this.exists(this.name, true); 208 | await this.c1.agent.check.deregister(this.name); 209 | await this.exists(this.name, false); 210 | }); 211 | }); 212 | 213 | describe("pass", function () { 214 | it("should mark check as passing", async function () { 215 | await this.state(this.name, "critical"); 216 | await this.c1.agent.check.pass(this.name); 217 | await this.state(this.name, "passing"); 218 | }); 219 | }); 220 | 221 | describe("warn", function () { 222 | it("should mark check as warning", async function () { 223 | await this.state(this.name, "critical"); 224 | await this.c1.agent.check.warn(this.name); 225 | await this.state(this.name, "warning"); 226 | }); 227 | }); 228 | 229 | describe("fail", function () { 230 | it("should mark check as critical", async function () { 231 | await this.state(this.name, "critical"); 232 | await this.c1.agent.check.fail(this.name); 233 | await this.state(this.name, "critical"); 234 | }); 235 | }); 236 | }); 237 | 238 | describe("service", function () { 239 | before(function () { 240 | // helper function to check existence of service 241 | this.exists = async (id, exists) => { 242 | const services = await this.c1.agent.services(); 243 | 244 | let s = should(services); 245 | if (!exists) s = s.not; 246 | s.have.property(id); 247 | }; 248 | }); 249 | 250 | beforeEach(async function () { 251 | this.name = "service-" + uuid.v4(); 252 | this.deregister = [this.name]; 253 | 254 | // remove existing services 255 | const services = await this.c1.agent.services(); 256 | 257 | const ids = Object.values(services) 258 | .filter((s) => s && s.ID !== "consul") 259 | .map((s) => s.ID); 260 | 261 | await Promise.all(ids.map((id) => this.c1.agent.service.deregister(id))); 262 | 263 | // add service 264 | await this.c1.agent.service.register(this.name); 265 | }); 266 | 267 | afterEach(async function () { 268 | await Promise.all( 269 | this.deregister.map((id) => this.c1.agent.service.deregister(id)), 270 | ).catch(() => null); 271 | }); 272 | 273 | describe("list", function () { 274 | it("should return agent services", async function () { 275 | const data = await this.c1.agent.services(); 276 | should.exist(data); 277 | should(data).have.property(this.name); 278 | }); 279 | }); 280 | 281 | describe("register", function () { 282 | it("should create service", async function () { 283 | const name = "service-" + uuid.v4(); 284 | 285 | await this.exists(name, false); 286 | await this.c1.agent.service.register(name); 287 | await this.exists(name, true); 288 | }); 289 | 290 | it("should create service with http check", async function () { 291 | const name = "service-" + uuid.v4(); 292 | const notes = "simple http check"; 293 | 294 | await this.exists(name, false); 295 | 296 | await this.c1.agent.service.register({ 297 | name: name, 298 | check: { 299 | http: "http://127.0.0.1:8500", 300 | interval: "30s", 301 | notes: notes, 302 | }, 303 | }); 304 | 305 | const checks = await this.c1.agent.check.list(); 306 | should(checks).not.be.empty(); 307 | should(checks["service:" + name]).have.property("Notes", notes); 308 | }); 309 | 310 | it("should create service with script check", async function () { 311 | const name = "service-" + uuid.v4(); 312 | const notes = "simple script check"; 313 | 314 | await this.exists(name, false); 315 | 316 | await this.c1.agent.service.register({ 317 | name: name, 318 | check: { 319 | args: ["sh", "-c", "true"], 320 | interval: "30s", 321 | timeout: "1s", 322 | notes: notes, 323 | }, 324 | }); 325 | 326 | const checks = await this.c1.agent.check.list(); 327 | should(checks).not.be.empty(); 328 | should(checks["service:" + name]).have.property("Notes", notes); 329 | }); 330 | }); 331 | 332 | describe("deregister", function () { 333 | it("should remove service", async function () { 334 | await this.exists(this.name, true); 335 | await this.c1.agent.service.deregister(this.name); 336 | await this.exists(this.name, false); 337 | }); 338 | }); 339 | 340 | describe("maintenance", function () { 341 | it("should set service maintenance mode", async function () { 342 | const checkId = "_service_maintenance:" + this.name; 343 | 344 | const checks = await this.c1.agent.checks(); 345 | should(checks).not.have.property(checkId); 346 | 347 | await this.c1.agent.service.maintenance({ 348 | id: this.name, 349 | enable: true, 350 | }); 351 | 352 | const enableStatus = await this.c1.agent.checks(); 353 | should(enableStatus).have.property(checkId); 354 | should(enableStatus[checkId]).have.property("Status", "critical"); 355 | 356 | await this.c1.agent.service.maintenance({ 357 | id: this.name, 358 | enable: false, 359 | }); 360 | 361 | const disableStatus = this.c1.agent.checks(); 362 | should(disableStatus).not.have.property(checkId); 363 | }); 364 | }); 365 | }); 366 | }); 367 | -------------------------------------------------------------------------------- /test/agent.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const should = require("should"); 4 | 5 | const helper = require("./helper"); 6 | 7 | describe("Agent", function () { 8 | helper.setup(this); 9 | 10 | describe("checks", function () { 11 | it("should work", async function () { 12 | this.nock.get("/v1/agent/checks").reply(200, [{ ok: true }]); 13 | 14 | const data = await this.consul.agent.checks({}); 15 | should(data).eql([{ ok: true }]); 16 | }); 17 | 18 | it("should work with no arguments", async function () { 19 | this.nock.get("/v1/agent/checks").reply(200, [{ ok: true }]); 20 | 21 | const data = await this.consul.agent.checks(); 22 | should(data).eql([{ ok: true }]); 23 | }); 24 | }); 25 | 26 | describe("check", function () { 27 | describe("list", function () { 28 | it("should work", async function () { 29 | this.nock.get("/v1/agent/checks").reply(200, [{ ok: true }]); 30 | 31 | const data = await this.consul.agent.check.list({}); 32 | should(data).eql([{ ok: true }]); 33 | }); 34 | 35 | it("should work with no arguments", async function () { 36 | this.nock.get("/v1/agent/checks").reply(200, [{ ok: true }]); 37 | 38 | const data = await this.consul.agent.check.list(); 39 | should(data).eql([{ ok: true }]); 40 | }); 41 | }); 42 | 43 | describe("register", function () { 44 | it("should work (http)", async function () { 45 | this.nock 46 | .put("/v1/agent/check/register", { 47 | ID: "123", 48 | Name: "test", 49 | ServiceID: "service", 50 | HTTP: "http://example.org/", 51 | TLSSkipVerify: true, 52 | Interval: "5s", 53 | Notes: "http check", 54 | Status: "critical", 55 | }) 56 | .reply(200); 57 | 58 | await this.consul.agent.check.register({ 59 | id: "123", 60 | name: "test", 61 | service_id: "service", 62 | http: "http://example.org/", 63 | tls_skip_verify: true, 64 | interval: "5s", 65 | notes: "http check", 66 | status: "critical", 67 | }); 68 | }); 69 | 70 | it("should work (script)", async function () { 71 | this.nock 72 | .put("/v1/agent/check/register", { 73 | Name: "test", 74 | Script: "true", 75 | Interval: "5s", 76 | }) 77 | .reply(200); 78 | 79 | await this.consul.agent.check.register({ 80 | name: "test", 81 | script: "true", 82 | interval: "5s", 83 | }); 84 | }); 85 | 86 | it("should work (ttl)", async function () { 87 | this.nock 88 | .put("/v1/agent/check/register", { 89 | ID: "123", 90 | Name: "test", 91 | ServiceID: "service", 92 | TTL: "10s", 93 | Notes: "ttl check", 94 | }) 95 | .reply(200); 96 | 97 | await this.consul.agent.check.register({ 98 | id: "123", 99 | name: "test", 100 | serviceid: "service", 101 | ttl: "10s", 102 | notes: "ttl check", 103 | }); 104 | }); 105 | 106 | it("should require check", async function () { 107 | try { 108 | await this.consul.agent.check.register({ 109 | name: "test", 110 | serviceid: "service", 111 | }); 112 | should.ok(false); 113 | } catch (err) { 114 | should(err).property( 115 | "message", 116 | "consul: agent.check.register: args/grpc/h2ping/http/tcp/udp and interval, " + 117 | "ttl, or aliasnode/aliasservice", 118 | ); 119 | } 120 | }); 121 | 122 | it("should require name", async function () { 123 | try { 124 | await this.consul.agent.check.register({ 125 | http: "http://localhost:5000/health", 126 | interval: "10s", 127 | }); 128 | should.ok(false); 129 | } catch (err) { 130 | should(err).property( 131 | "message", 132 | "consul: agent.check.register: name required", 133 | ); 134 | } 135 | }); 136 | }); 137 | 138 | describe("deregister", function () { 139 | it("should work", async function () { 140 | this.nock.put("/v1/agent/check/deregister/123").reply(200); 141 | 142 | await this.consul.agent.check.deregister({ id: "123" }); 143 | }); 144 | 145 | it("should work with just id", async function () { 146 | this.nock.put("/v1/agent/check/deregister/123").reply(200); 147 | 148 | await this.consul.agent.check.deregister("123"); 149 | }); 150 | 151 | it("should require id", async function () { 152 | try { 153 | await this.consul.agent.check.deregister({}); 154 | should.ok(false); 155 | } catch (err) { 156 | should(err).property( 157 | "message", 158 | "consul: agent.check.deregister: id required", 159 | ); 160 | } 161 | }); 162 | }); 163 | 164 | describe("pass", function () { 165 | it("should work", async function () { 166 | this.nock.put("/v1/agent/check/pass/123?note=ok").reply(200); 167 | 168 | await this.consul.agent.check.pass({ 169 | id: "123", 170 | note: "ok", 171 | }); 172 | }); 173 | 174 | it("should work with just id", async function () { 175 | this.nock.put("/v1/agent/check/pass/123").reply(200); 176 | 177 | await this.consul.agent.check.pass("123"); 178 | }); 179 | 180 | it("should require id", async function () { 181 | try { 182 | await this.consul.agent.check.pass({}); 183 | should.ok(false); 184 | } catch (err) { 185 | should(err).property( 186 | "message", 187 | "consul: agent.check.pass: id required", 188 | ); 189 | } 190 | }); 191 | }); 192 | 193 | describe("warn", function () { 194 | it("should work", async function () { 195 | this.nock.put("/v1/agent/check/warn/123?note=ify").reply(200); 196 | 197 | await this.consul.agent.check.warn({ 198 | id: "123", 199 | note: "ify", 200 | }); 201 | }); 202 | 203 | it("should work with just id", async function () { 204 | this.nock.put("/v1/agent/check/warn/123").reply(200); 205 | 206 | await this.consul.agent.check.warn("123"); 207 | }); 208 | 209 | it("should require id", async function () { 210 | try { 211 | await this.consul.agent.check.warn({}); 212 | should.ok(false); 213 | } catch (err) { 214 | should(err).property( 215 | "message", 216 | "consul: agent.check.warn: id required", 217 | ); 218 | } 219 | }); 220 | }); 221 | 222 | describe("fail", function () { 223 | it("should work", async function () { 224 | this.nock.put("/v1/agent/check/fail/123?note=error").reply(200); 225 | 226 | await this.consul.agent.check.fail({ 227 | id: "123", 228 | note: "error", 229 | }); 230 | }); 231 | 232 | it("should work with just id", async function () { 233 | this.nock.put("/v1/agent/check/fail/123").reply(200); 234 | 235 | await this.consul.agent.check.fail("123"); 236 | }); 237 | 238 | it("should require id", async function () { 239 | try { 240 | await this.consul.agent.check.fail({}); 241 | should.ok(false); 242 | } catch (err) { 243 | should(err).property( 244 | "message", 245 | "consul: agent.check.fail: id required", 246 | ); 247 | } 248 | }); 249 | }); 250 | }); 251 | 252 | describe("services", function () { 253 | it("should work", async function () { 254 | this.nock.get("/v1/agent/services").reply(200, [{ ok: true }]); 255 | 256 | const data = await this.consul.agent.services({}); 257 | should(data).eql([{ ok: true }]); 258 | }); 259 | 260 | it("should work with no arguments", async function () { 261 | this.nock.get("/v1/agent/services").reply(200, [{ ok: true }]); 262 | 263 | const data = await this.consul.agent.services(); 264 | should(data).eql([{ ok: true }]); 265 | }); 266 | }); 267 | 268 | describe("service", function () { 269 | describe("list", function () { 270 | it("should work", async function () { 271 | this.nock.get("/v1/agent/services").reply(200, [{ ok: true }]); 272 | 273 | const data = await this.consul.agent.service.list({}); 274 | should(data).eql([{ ok: true }]); 275 | }); 276 | 277 | it("should work with no arguments", async function () { 278 | this.nock.get("/v1/agent/services").reply(200, [{ ok: true }]); 279 | 280 | const data = await this.consul.agent.service.list(); 281 | should(data).eql([{ ok: true }]); 282 | }); 283 | }); 284 | 285 | describe("register", function () { 286 | it("should work with only name", async function () { 287 | this.nock 288 | .put("/v1/agent/service/register", { Name: "service" }) 289 | .reply(200); 290 | 291 | await this.consul.agent.service.register("service"); 292 | }); 293 | 294 | it("should require valid check", async function () { 295 | try { 296 | await this.consul.agent.service.register({ 297 | name: "service", 298 | check: {}, 299 | }); 300 | } catch (err) { 301 | should(err).property( 302 | "message", 303 | "consul: agent.service.register: args/grpc/h2ping/http/tcp/udp and interval, " + 304 | "ttl, or aliasnode/aliasservice", 305 | ); 306 | } 307 | }); 308 | 309 | it("should require name", async function () { 310 | try { 311 | await this.consul.agent.service.register({}); 312 | } catch (err) { 313 | should(err).property( 314 | "message", 315 | "consul: agent.service.register: name required", 316 | ); 317 | } 318 | }); 319 | }); 320 | 321 | describe("deregister", function () { 322 | it("should work", async function () { 323 | this.nock.put("/v1/agent/service/deregister/123").reply(200); 324 | 325 | await this.consul.agent.service.deregister({ id: "123" }); 326 | }); 327 | 328 | it("should work with just id", async function () { 329 | this.nock.put("/v1/agent/service/deregister/123").reply(200); 330 | 331 | await this.consul.agent.service.deregister("123"); 332 | }); 333 | 334 | it("should require id", async function () { 335 | try { 336 | await this.consul.agent.service.deregister({}); 337 | } catch (err) { 338 | should(err).property( 339 | "message", 340 | "consul: agent.service.deregister: id required", 341 | ); 342 | } 343 | }); 344 | }); 345 | 346 | describe("maintaince", function () { 347 | it("should work", async function () { 348 | this.nock 349 | .put("/v1/agent/service/maintenance/123?enable=true") 350 | .reply(200); 351 | 352 | await this.consul.agent.service.maintenance({ id: 123, enable: true }); 353 | }); 354 | 355 | it("should work with reason", async function () { 356 | this.nock 357 | .put("/v1/agent/service/maintenance/123?enable=false&reason=test") 358 | .reply(200); 359 | 360 | await this.consul.agent.service.maintenance({ 361 | id: 123, 362 | enable: false, 363 | reason: "test", 364 | }); 365 | }); 366 | 367 | it("should require id", async function () { 368 | try { 369 | await this.consul.agent.service.maintenance({}); 370 | should.ok(false); 371 | } catch (err) { 372 | should(err).have.property( 373 | "message", 374 | "consul: agent.service.maintenance: id required", 375 | ); 376 | should(err).have.property("isValidation", true); 377 | } 378 | }); 379 | 380 | it("should require enable", async function () { 381 | try { 382 | await this.consul.agent.service.maintenance({ id: 123 }); 383 | should.ok(false); 384 | } catch (err) { 385 | should(err).have.property( 386 | "message", 387 | "consul: agent.service.maintenance: enable required", 388 | ); 389 | should(err).have.property("isValidation", true); 390 | } 391 | }); 392 | }); 393 | }); 394 | 395 | describe("members", function () { 396 | it("should work", async function () { 397 | this.nock.get("/v1/agent/members").reply(200, [{ ok: true }]); 398 | 399 | const data = await this.consul.agent.members({}); 400 | should(data).eql([{ ok: true }]); 401 | }); 402 | 403 | it("should work with no arguments", async function () { 404 | this.nock.get("/v1/agent/members").reply(200, [{ ok: true }]); 405 | 406 | const data = await this.consul.agent.members(); 407 | should(data).eql([{ ok: true }]); 408 | }); 409 | }); 410 | 411 | describe("reload", function () { 412 | it("should work", async function () { 413 | this.nock.put("/v1/agent/reload").reply(200); 414 | 415 | await this.consul.agent.reload({}); 416 | }); 417 | 418 | it("should work with no arguments", async function () { 419 | this.nock.put("/v1/agent/reload").reply(200); 420 | 421 | await this.consul.agent.reload(); 422 | }); 423 | }); 424 | 425 | describe("self", function () { 426 | it("should work", async function () { 427 | this.nock.get("/v1/agent/self").reply(200, { ok: true }); 428 | 429 | const data = await this.consul.agent.self({}); 430 | should(data).eql({ ok: true }); 431 | }); 432 | 433 | it("should work with no arguments", async function () { 434 | this.nock.get("/v1/agent/self").reply(200, { ok: true }); 435 | 436 | const data = await this.consul.agent.self(); 437 | should(data).eql({ ok: true }); 438 | }); 439 | }); 440 | 441 | describe("maintenance", function () { 442 | it("should work", async function () { 443 | this.nock.put("/v1/agent/maintenance?enable=true&reason=test").reply(200); 444 | 445 | await this.consul.agent.maintenance({ enable: true, reason: "test" }); 446 | }); 447 | 448 | it("should work with just enable", async function () { 449 | this.nock.put("/v1/agent/maintenance?enable=false").reply(200); 450 | 451 | await this.consul.agent.maintenance(false); 452 | }); 453 | 454 | it("should require enable", async function () { 455 | try { 456 | await this.consul.agent.maintenance({}); 457 | } catch (err) { 458 | should(err).have.property( 459 | "message", 460 | "consul: agent.maintenance: enable required", 461 | ); 462 | should(err).have.property("isValidation", true); 463 | } 464 | }); 465 | }); 466 | 467 | describe("join", function () { 468 | it("should work", async function () { 469 | this.nock.put("/v1/agent/join/127.0.0.2").reply(200); 470 | 471 | await this.consul.agent.join({ address: "127.0.0.2" }); 472 | }); 473 | 474 | it("should work with just address", async function () { 475 | this.nock.put("/v1/agent/join/127.0.0.2").reply(200); 476 | 477 | await this.consul.agent.join("127.0.0.2"); 478 | }); 479 | 480 | it("should require address", async function () { 481 | try { 482 | await this.consul.agent.join({}); 483 | should.ok(false); 484 | } catch (err) { 485 | should(err).have.property( 486 | "message", 487 | "consul: agent.join: address required", 488 | ); 489 | should(err).have.property("isValidation", true); 490 | } 491 | }); 492 | }); 493 | 494 | describe("forceLeave", function () { 495 | it("should work", async function () { 496 | this.nock.put("/v1/agent/force-leave/node").reply(200); 497 | 498 | await this.consul.agent.forceLeave({ node: "node" }); 499 | }); 500 | 501 | it("should work with just address", async function () { 502 | this.nock.put("/v1/agent/force-leave/node").reply(200); 503 | 504 | await this.consul.agent.forceLeave("node"); 505 | }); 506 | 507 | it("should require node", async function () { 508 | try { 509 | await this.consul.agent.forceLeave({}); 510 | should.ok(false); 511 | } catch (err) { 512 | should(err).have.property( 513 | "message", 514 | "consul: agent.forceLeave: node required", 515 | ); 516 | should(err).have.property("isValidation", true); 517 | } 518 | }); 519 | }); 520 | }); 521 | --------------------------------------------------------------------------------