Compare commits

...

12 Commits

Author SHA1 Message Date
4466461828 Bumped v5.6.19 2019-07-05 11:40:33 +02:00
66fd94643d Remove auth data from inspect and toJSON in connection class (#887)
* Remove auth data from inspect and toJSON in connection class

* Updated test
2019-07-04 14:43:48 +02:00
39bbd77bec Updated type definitions (#882)
* Updated type definitions

* Updated test
2019-06-19 09:15:57 +02:00
a4f893d563 Support branch for code generation (#883) 2019-06-18 15:47:17 +02:00
6707a8603e Added cloud configuration example (#880)
* Added cloud configuration example

* Used cloud example from auth docs and added link
2019-06-14 10:13:57 +02:00
a38e89aa04 [DOCS] Fixes typo (#877) 2019-06-12 08:42:21 +02:00
0311ce9e42 Docs: added missing configuration options (#870) 2019-06-03 12:29:23 +02:00
2e90d5b55e Bumped v5.6.18 2019-05-22 12:20:41 +02:00
32836b4f6c Support for non-friendly chars in url username and password (#858)
* Support for non-friendly chars in url username and password
- Added auth option to Connection class
- Updated pool.addConnection

* Updated test
2019-05-20 17:10:49 +02:00
f8034c60bc API generation 2019-05-16 16:56:01 -04:00
c68a5ce9a2 Patch deprecated parameters (#851)
* Updated code generation

* API generation

* API generation

* Updated code generation
2019-05-16 16:55:07 -04:00
554bb1ff05 missing comma (#854) 2019-05-16 09:39:59 -04:00
15 changed files with 191 additions and 55 deletions

View File

@ -8,7 +8,7 @@ The client is designed to be easily configured as you see fit for your needs, fo
const { Client } = require('@elastic/elasticsearch') const { Client } = require('@elastic/elasticsearch')
const client = new Client({ const client = new Client({
node: 'http://localhost:9200' node: 'http://localhost:9200',
maxRetries: 5, maxRetries: 5,
requestTimeout: 60000, requestTimeout: 60000,
sniffOnStart: true sniffOnStart: true
@ -150,6 +150,26 @@ function generateRequestId (params, options) {
|`name` |`name`
|`string` - The name to identify the client instance in the events. + |`string` - The name to identify the client instance in the events. +
_Default:_ `elasticsearch-js` _Default:_ `elasticsearch-js`
|`headers`
|`object` - A set of custom headers to send in every request. +
_Default:_ `{}`
|`cloud`
a|`object` - Custom configuration for connecting to https://cloud.elastic.co[Elastic Cloud]. See https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/auth-reference.html[Authentication] for more details. +
_Default:_ `null` +
_Cloud configuration example:_
[source,js]
----
const client = new Client({
cloud: {
id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==',
username: 'elastic',
password: 'changeme'
}
})
----
|=== |===
=== Advanced configuration === Advanced configuration

View File

@ -59,7 +59,7 @@ async function run (): void {
client client
.search(params) .search(params)
.then((result: ApiResponse) => { .then((result: ApiResponse) => {
console.og(result.body.hits.hits) console.log(result.body.hits.hits)
}) })
.catch((err: Error) => { .catch((err: Error) => {
console.log(err) console.log(err)

22
index.d.ts vendored
View File

@ -20,7 +20,7 @@
/// <reference types="node" /> /// <reference types="node" />
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { SecureContextOptions } from 'tls'; import { ConnectionOptions as TlsConnectionOptions } from 'tls';
import Transport, { import Transport, {
ApiResponse, ApiResponse,
RequestEvent, RequestEvent,
@ -31,6 +31,7 @@ import Transport, {
generateRequestIdFn, generateRequestIdFn,
TransportRequestCallback TransportRequestCallback
} from './lib/Transport'; } from './lib/Transport';
import { URL } from 'url';
import Connection, { AgentOptions, agentFn } from './lib/Connection'; import Connection, { AgentOptions, agentFn } from './lib/Connection';
import ConnectionPool, { ResurrectEvent } from './lib/ConnectionPool'; import ConnectionPool, { ResurrectEvent } from './lib/ConnectionPool';
import Serializer from './lib/Serializer'; import Serializer from './lib/Serializer';
@ -72,8 +73,22 @@ interface ClientExtends {
} }
// /Extend API // /Extend API
interface NodeOptions {
url: URL;
id?: string;
agent?: AgentOptions;
ssl?: TlsConnectionOptions;
headers?: anyObject;
roles?: {
master: boolean;
data: boolean;
ingest: boolean;
ml: boolean;
}
}
interface ClientOptions { interface ClientOptions {
node?: string | string[]; node?: string | string[] | NodeOptions | NodeOptions[];
nodes?: string | string[]; nodes?: string | string[];
Connection?: typeof Connection; Connection?: typeof Connection;
ConnectionPool?: typeof ConnectionPool; ConnectionPool?: typeof ConnectionPool;
@ -89,7 +104,7 @@ interface ClientOptions {
resurrectStrategy?: 'ping' | 'optimistic' | 'none'; resurrectStrategy?: 'ping' | 'optimistic' | 'none';
suggestCompression?: boolean; suggestCompression?: boolean;
compression?: 'gzip'; compression?: 'gzip';
ssl?: SecureContextOptions; ssl?: TlsConnectionOptions;
agent?: AgentOptions | agentFn; agent?: AgentOptions | agentFn;
nodeFilter?: nodeFilterFn; nodeFilter?: nodeFilterFn;
nodeSelector?: nodeSelectorFn | string; nodeSelector?: nodeSelectorFn | string;
@ -327,5 +342,6 @@ export {
ResurrectEvent, ResurrectEvent,
RequestParams, RequestParams,
ClientOptions, ClientOptions,
NodeOptions,
ClientExtendsCallbackOptions ClientExtendsCallbackOptions
}; };

6
lib/Connection.d.ts vendored
View File

@ -22,13 +22,13 @@
import { URL } from 'url'; import { URL } from 'url';
import { inspect, InspectOptions } from 'util'; import { inspect, InspectOptions } from 'util';
import * as http from 'http'; import * as http from 'http';
import { SecureContextOptions } from 'tls'; import { ConnectionOptions as TlsConnectionOptions } from 'tls';
export declare type agentFn = () => any; export declare type agentFn = () => any;
interface ConnectionOptions { interface ConnectionOptions {
url: URL; url: URL;
ssl?: SecureContextOptions; ssl?: TlsConnectionOptions;
id?: string; id?: string;
headers?: any; headers?: any;
agent?: AgentOptions | agentFn; agent?: AgentOptions | agentFn;
@ -59,7 +59,7 @@ export default class Connection {
ML: string; ML: string;
}; };
url: URL; url: URL;
ssl: SecureContextOptions | null; ssl: TlsConnectionOptions | null;
id: string; id: string;
headers: any; headers: any;
deadCount: number; deadCount: number;

View File

@ -35,6 +35,7 @@ class Connection {
this.ssl = opts.ssl || null this.ssl = opts.ssl || null
this.id = opts.id || stripAuth(opts.url.href) this.id = opts.id || stripAuth(opts.url.href)
this.headers = opts.headers || null this.headers = opts.headers || null
this.auth = opts.auth || { username: null, password: null }
this.deadCount = 0 this.deadCount = 0
this.resurrectTimeout = 0 this.resurrectTimeout = 0
@ -180,6 +181,7 @@ class Connection {
buildRequestObject (params) { buildRequestObject (params) {
const url = this.url const url = this.url
const { username, password } = this.auth
const request = { const request = {
protocol: url.protocol, protocol: url.protocol,
hostname: url.hostname[0] === '[' hostname: url.hostname[0] === '['
@ -194,8 +196,8 @@ class Connection {
// https://github.com/elastic/elasticsearch-js/issues/843 // https://github.com/elastic/elasticsearch-js/issues/843
port: url.port !== '' ? url.port : undefined, port: url.port !== '' ? url.port : undefined,
headers: this.headers, headers: this.headers,
auth: !!url.username === true || !!url.password === true auth: username != null && password != null
? `${url.username}:${url.password}` ? `${username}:${password}`
: undefined, : undefined,
agent: this.agent agent: this.agent
} }
@ -224,12 +226,12 @@ class Connection {
} }
// Handles console.log and utils.inspect invocations. // Handles console.log and utils.inspect invocations.
// We want to hide `agent` and `ssl` since they made // We want to hide `auth`, `agent` and `ssl` since they made
// the logs very hard to read. The user can still // the logs very hard to read. The user can still
// access them with `instance.agent` and `instance.ssl`. // access them with `instance.agent` and `instance.ssl`.
[inspect.custom] (depth, options) { [inspect.custom] (depth, options) {
return { return {
url: this.url, url: stripAuth(this.url.toString()),
id: this.id, id: this.id,
headers: this.headers, headers: this.headers,
deadCount: this.deadCount, deadCount: this.deadCount,
@ -242,7 +244,7 @@ class Connection {
toJSON () { toJSON () {
return { return {
url: this.url, url: stripAuth(this.url.toString()),
id: this.id, id: this.id,
headers: this.headers, headers: this.headers,
deadCount: this.deadCount, deadCount: this.deadCount,

View File

@ -222,21 +222,24 @@ class ConnectionPool {
// we can add it to them once the connection instance has been created // we can add it to them once the connection instance has been created
if (opts.url.username !== '' && opts.url.password !== '') { if (opts.url.username !== '' && opts.url.password !== '') {
this._auth = { this._auth = {
username: opts.url.username, username: decodeURIComponent(opts.url.username),
password: opts.url.password password: decodeURIComponent(opts.url.password)
}
opts.auth = this._auth
}
if (this._auth != null) {
if (opts.auth == null || (opts.auth.username == null && opts.auth.password == null)) {
opts.auth = this._auth
opts.url.username = this._auth.username
opts.url.password = this._auth.password
} }
} }
if (opts.ssl == null) opts.ssl = this._ssl if (opts.ssl == null) opts.ssl = this._ssl
if (opts.agent == null) opts.agent = this._agent if (opts.agent == null) opts.agent = this._agent
const connection = new this.Connection(opts) const connection = new this.Connection(opts)
if (connection.url.username === '' &&
connection.url.password === '' &&
this._auth != null) {
connection.url.username = this._auth.username
connection.url.password = this._auth.password
}
debug('Adding a new connection', connection) debug('Adding a new connection', connection)
if (this.connections.has(connection.id)) { if (this.connections.has(connection.id)) {
throw new Error(`Connection with id '${connection.id}' is already present`) throw new Error(`Connection with id '${connection.id}' is already present`)

View File

@ -4,7 +4,7 @@
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"homepage": "http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html", "homepage": "http://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html",
"version": "5.6.17", "version": "5.6.19",
"keywords": [ "keywords": [
"elasticsearch", "elasticsearch",
"elastic", "elastic",

View File

@ -35,12 +35,12 @@ const {
} = require('./utils') } = require('./utils')
start(minimist(process.argv.slice(2), { start(minimist(process.argv.slice(2), {
string: ['tag'] string: ['tag', 'branch']
})) }))
function start (opts) { function start (opts) {
const log = ora('Loading Elasticsearch Repository').start() const log = ora('Loading Elasticsearch Repository').start()
if (semver.valid(opts.tag) === null) { if (opts.branch == null && semver.valid(opts.tag) === null) {
log.fail(`Missing or invalid tag: ${opts.tag}`) log.fail(`Missing or invalid tag: ${opts.tag}`)
return return
} }
@ -55,7 +55,7 @@ function start (opts) {
log.text = 'Cleaning API folder...' log.text = 'Cleaning API folder...'
rimraf.sync(join(apiOutputFolder, '*.js')) rimraf.sync(join(apiOutputFolder, '*.js'))
cloneAndCheckout({ log, tag: opts.tag }, (err, { apiFolder, xPackFolder }) => { cloneAndCheckout({ log, tag: opts.tag, branch: opts.branch }, (err, { apiFolder, xPackFolder }) => {
if (err) { if (err) {
log.fail(err.message) log.fail(err.message)
return return
@ -74,7 +74,7 @@ function start (opts) {
writeFileSync( writeFileSync(
requestParamsOutputFile, requestParamsOutputFile,
generateRequestTypes(allSpec), generateRequestTypes(opts.branch || opts.tag, allSpec),
{ encoding: 'utf8' } { encoding: 'utf8' }
) )
@ -118,7 +118,7 @@ function start (opts) {
const spec = require(join(apiFolder, file)) const spec = require(join(apiFolder, file))
allSpec.push(spec) allSpec.push(spec)
const code = generate(spec, common) const code = generate(opts.branch || opts.tag, spec, common)
const filePath = join(apiOutputFolder, `${file.slice(0, file.lastIndexOf('.'))}.js`) const filePath = join(apiOutputFolder, `${file.slice(0, file.lastIndexOf('.'))}.js`)
writeFileSync(filePath, code, { encoding: 'utf8' }) writeFileSync(filePath, code, { encoding: 'utf8' })

View File

@ -29,7 +29,7 @@ const apiFolder = join(esFolder, 'rest-api-spec', 'src', 'main', 'resources', 'r
const xPackFolder = join(esFolder, 'x-pack', 'plugin', 'src', 'test', 'resources', 'rest-api-spec', 'api') const xPackFolder = join(esFolder, 'x-pack', 'plugin', 'src', 'test', 'resources', 'rest-api-spec', 'api')
function cloneAndCheckout (opts, callback) { function cloneAndCheckout (opts, callback) {
const { log, tag } = opts const { log, tag, branch } = opts
withTag(tag, callback) withTag(tag, callback)
/** /**
@ -57,13 +57,19 @@ function cloneAndCheckout (opts, callback) {
if (fresh) { if (fresh) {
clone(checkout) clone(checkout)
} else if (opts.branch) {
checkout(true)
} else { } else {
checkout() checkout()
} }
function checkout () { function checkout (alsoPull = false) {
log.text = `Checking out tag '${tag}'` if (branch) {
git.checkout(tag, err => { log.text = `Checking out branch '${branch}'`
} else {
log.text = `Checking out tag '${tag}'`
}
git.checkout(branch || tag, err => {
if (err) { if (err) {
if (retry++ > 0) { if (retry++ > 0) {
callback(new Error(`Cannot checkout tag '${tag}'`), { apiFolder, xPackFolder }) callback(new Error(`Cannot checkout tag '${tag}'`), { apiFolder, xPackFolder })
@ -71,6 +77,9 @@ function cloneAndCheckout (opts, callback) {
} }
return pull(checkout) return pull(checkout)
} }
if (alsoPull) {
return pull(checkout)
}
callback(null, { apiFolder, xPackFolder }) callback(null, { apiFolder, xPackFolder })
}) })
} }

View File

@ -20,11 +20,16 @@
'use strict' 'use strict'
const dedent = require('dedent') const dedent = require('dedent')
const semver = require('semver')
const allowedMethods = { const allowedMethods = {
noBody: ['GET', 'HEAD', 'DELETE'], noBody: ['GET', 'HEAD', 'DELETE'],
body: ['POST', 'PUT', 'DELETE'] body: ['POST', 'PUT', 'DELETE']
} }
// if a parameter is depracted in a minor release
// we should be able to support it until the next major
const deprecatedParameters = require('./patch.json')
// list of apis that does not need any kind of validation // list of apis that does not need any kind of validation
// because of how the url is built or the `type` handling in ES7 // because of how the url is built or the `type` handling in ES7
const noPathValidation = [ const noPathValidation = [
@ -59,7 +64,8 @@ const ndjsonApi = [
'xpack.monitoring.bulk' 'xpack.monitoring.bulk'
] ]
function generate (spec, common) { function generate (version, spec, common) {
const release = semver.valid(version) ? semver.major(version) : version
const api = Object.keys(spec)[0] const api = Object.keys(spec)[0]
const name = api const name = api
.replace(/\.([a-z])/g, k => k[1].toUpperCase()) .replace(/\.([a-z])/g, k => k[1].toUpperCase())
@ -80,7 +86,11 @@ function generate (spec, common) {
if (params[key].required) { if (params[key].required) {
required.push(key) required.push(key)
} }
acceptedQuerystring.push(key) acceptedQuerystring.push(key)
if (deprecatedParameters[release] && deprecatedParameters[release][key]) {
acceptedQuerystring.push(deprecatedParameters[release][key])
}
} }
for (const key in spec[api]) { for (const key in spec[api]) {

View File

@ -19,7 +19,11 @@
'use strict' 'use strict'
function generate (api) { const semver = require('semver')
const deprecatedParameters = require('./patch.json')
function generate (version, api) {
const release = semver.valid(version) ? semver.major(version) : version
var types = `/* var types = `/*
* Licensed to Elasticsearch B.V. under one or more contributor * Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with * license agreements. See the NOTICE file distributed with
@ -64,9 +68,20 @@ export interface Generic {
const partsArr = Object.keys(parts) const partsArr = Object.keys(parts)
.map(k => ({ key: k, value: parts[k] })) .map(k => ({ key: k, value: parts[k] }))
const deprecatedParametersToAdd = []
const paramsArr = Object.keys(params) const paramsArr = Object.keys(params)
.filter(k => !Object.keys(parts).includes(k)) .filter(k => !Object.keys(parts).includes(k))
.map(k => ({ key: k, value: params[k] })) .map(k => {
if (deprecatedParameters[release] && deprecatedParameters[release][k]) {
deprecatedParametersToAdd.push({
key: deprecatedParameters[release][k],
value: params[k]
})
}
return { key: k, value: params[k] }
})
deprecatedParametersToAdd.forEach(k => partsArr.push(k))
const genLine = e => { const genLine = e => {
const optional = e.value.required ? '' : '?' const optional = e.value.required ? '' : '?'

14
scripts/utils/patch.json Normal file
View File

@ -0,0 +1,14 @@
{
"6": {
"_source_includes": "_source_include",
"_source_excludes": "_source_exclude"
},
"7": {
"_source_includes": "_source_include",
"_source_excludes": "_source_exclude"
},
"master": {
"_source_includes": "_source_include",
"_source_excludes": "_source_exclude"
}
}

View File

@ -27,13 +27,29 @@ import {
ResurrectEvent, ResurrectEvent,
events, events,
errors, errors,
ClientExtendsCallbackOptions ClientExtendsCallbackOptions,
NodeOptions
} from '../../index' } from '../../index'
import { TransportRequestParams, TransportRequestOptions } from '../../lib/Transport' import { TransportRequestParams, TransportRequestOptions } from '../../lib/Transport'
import { URL } from 'url'
const client = new Client({ node: 'http://localhost:9200' }) const client = new Client({ node: 'http://localhost:9200' })
const nodeOpts: NodeOptions = {
url: new URL('http://localhost:9200'),
id: 'winteriscoming',
headers: { 'foo': 'bar' },
roles: {
master: false,
data: true,
ingest: false,
ml: false
}
}
const client2 = new Client({ node: nodeOpts })
client.on(events.RESPONSE, (err: errors.ElasticsearchClientError | null, request: RequestEvent) => { client.on(events.RESPONSE, (err: errors.ElasticsearchClientError | null, request: RequestEvent) => {
if (err) console.log(err) if (err) console.log(err)
const { body, statusCode } = request const { body, statusCode } = request

View File

@ -61,8 +61,23 @@ test('API', t => {
t.deepEqual(pool._auth, { username: 'foo', password: 'bar' }) t.deepEqual(pool._auth, { username: 'foo', password: 'bar' })
pool.addConnection('http://localhost:9201') pool.addConnection('http://localhost:9201')
t.strictEqual(pool.connections.get('http://localhost:9201/').url.username, 'foo') const conn = pool.connections.get('http://localhost:9201/')
t.strictEqual(pool.connections.get('http://localhost:9201/').url.password, 'bar') t.strictEqual(conn.url.username, 'foo')
t.strictEqual(conn.url.password, 'bar')
t.strictEqual(conn.auth.username, 'foo')
t.strictEqual(conn.auth.password, 'bar')
t.end()
})
t.test('addConnection should handle not-friendly url parameters for user and password', t => {
const pool = new ConnectionPool({ Connection })
const href = 'http://us"er:p@assword@localhost:9200/'
pool.addConnection(href)
const conn = pool.getConnection()
t.strictEqual(conn.url.username, 'us%22er')
t.strictEqual(conn.url.password, 'p%40assword')
t.strictEqual(conn.auth.username, 'us"er')
t.strictEqual(conn.auth.password, 'p@assword')
t.end() t.end()
}) })

View File

@ -526,7 +526,8 @@ test('Url with auth', t => {
buildServer(handler, ({ port }, server) => { buildServer(handler, ({ port }, server) => {
const connection = new Connection({ const connection = new Connection({
url: new URL(`http://foo:bar@localhost:${port}`) url: new URL(`http://foo:bar@localhost:${port}`),
auth: { username: 'foo', password: 'bar' }
}) })
connection.request({ connection.request({
path: '/hello', path: '/hello',
@ -722,11 +723,11 @@ test('setRole', t => {
t.end() t.end()
}) })
test('Util.inspect Connection class should hide agent and ssl', t => { test('Util.inspect Connection class should hide agent, ssl and auth', t => {
t.plan(1) t.plan(1)
const connection = new Connection({ const connection = new Connection({
url: new URL('http://localhost:9200'), url: new URL('http://user:password@localhost:9200'),
id: 'node-id', id: 'node-id',
headers: { foo: 'bar' } headers: { foo: 'bar' }
}) })
@ -740,20 +741,7 @@ test('Util.inspect Connection class should hide agent and ssl', t => {
.replace(/(\r\n|\n|\r)/gm, '') .replace(/(\r\n|\n|\r)/gm, '')
} }
t.strictEqual(cleanStr(inspect(connection)), cleanStr(`{ url: t.strictEqual(cleanStr(inspect(connection)), cleanStr(`{ url: 'http://localhost:9200/',
URL {
href: 'http://localhost:9200/',
origin: 'http://localhost:9200',
protocol: 'http:',
username: '',
password: '',
host: 'localhost:9200',
hostname: 'localhost',
port: '9200',
pathname: '/',
search: '',
searchParams: URLSearchParams {},
hash: '' },
id: 'node-id', id: 'node-id',
headers: { foo: 'bar' }, headers: { foo: 'bar' },
deadCount: 0, deadCount: 0,
@ -764,6 +752,34 @@ test('Util.inspect Connection class should hide agent and ssl', t => {
) )
}) })
test('connection.toJSON should hide agent, ssl and auth', t => {
t.plan(1)
const connection = new Connection({
url: new URL('http://user:password@localhost:9200'),
id: 'node-id',
headers: { foo: 'bar' }
})
t.deepEqual(connection.toJSON(), {
url: 'http://localhost:9200/',
id: 'node-id',
headers: {
foo: 'bar'
},
deadCount: 0,
resurrectTimeout: 0,
_openRequests: 0,
status: 'alive',
roles: {
master: true,
data: true,
ingest: true,
ml: false
}
})
})
// https://github.com/elastic/elasticsearch-js/issues/843 // https://github.com/elastic/elasticsearch-js/issues/843
test('Port handling', t => { test('Port handling', t => {
t.test('http 80', t => { t.test('http 80', t => {