mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[APM] Add support for versioned APIs in diagnostics tool (#167050)
This fixes a problem where versioned APIs were not supported. It also adds a `--local` flag for easily running the diagnostics tool against a local cluster running with default credentials (elastic/changeme)
This commit is contained in:
parent
bfafd369a0
commit
7e32fc8432
2 changed files with 185 additions and 98 deletions
|
@ -7,103 +7,149 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { URL } from 'url';
|
||||
import datemath from '@elastic/datemath';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { AxiosError } from 'axios';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import yargs from 'yargs';
|
||||
import { initDiagnosticsBundle } from './diagnostics_bundle';
|
||||
|
||||
const { argv } = yargs(process.argv.slice(2))
|
||||
.option('esHost', {
|
||||
type: 'string',
|
||||
description: 'Elasticsearch host name',
|
||||
})
|
||||
.option('kbHost', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('username', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('password', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('cloudId', {
|
||||
type: 'string',
|
||||
})
|
||||
.option('apiKey', {
|
||||
type: 'string',
|
||||
})
|
||||
.option('rangeFrom', {
|
||||
type: 'string',
|
||||
description: 'Time-range start',
|
||||
coerce: convertDate,
|
||||
})
|
||||
.option('rangeTo', {
|
||||
type: 'string',
|
||||
description: 'Time range end',
|
||||
coerce: convertDate,
|
||||
})
|
||||
.option('kuery', {
|
||||
type: 'string',
|
||||
description: 'KQL query to filter documents by',
|
||||
})
|
||||
.help();
|
||||
async function init() {
|
||||
const { argv } = yargs(process.argv.slice(2))
|
||||
.option('esHost', {
|
||||
type: 'string',
|
||||
description: 'Elasticsearch host name',
|
||||
})
|
||||
.option('kbHost', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('username', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('password', {
|
||||
type: 'string',
|
||||
description: 'Kibana host name',
|
||||
})
|
||||
.option('local', {
|
||||
type: 'boolean',
|
||||
description: 'Connect to local cluster',
|
||||
default: false,
|
||||
})
|
||||
.option('cloudId', {
|
||||
type: 'string',
|
||||
})
|
||||
.option('apiKey', {
|
||||
type: 'string',
|
||||
})
|
||||
.option('rangeFrom', {
|
||||
type: 'string',
|
||||
description: 'Time-range start',
|
||||
coerce: convertDate,
|
||||
})
|
||||
.option('rangeTo', {
|
||||
type: 'string',
|
||||
description: 'Time range end',
|
||||
coerce: convertDate,
|
||||
})
|
||||
.option('kuery', {
|
||||
type: 'string',
|
||||
description: 'KQL query to filter documents by',
|
||||
})
|
||||
.help();
|
||||
|
||||
const { esHost, kbHost, password, username, kuery, apiKey, cloudId } = argv;
|
||||
const rangeFrom = argv.rangeFrom as unknown as number;
|
||||
const rangeTo = argv.rangeTo as unknown as number;
|
||||
const { kuery, apiKey, cloudId } = argv;
|
||||
let esHost = argv.esHost;
|
||||
let kbHost = argv.kbHost;
|
||||
let password = argv.password;
|
||||
let username = argv.username;
|
||||
|
||||
if ((!esHost || !kbHost) && !cloudId) {
|
||||
console.error('Either esHost and kbHost or cloudId must be provided');
|
||||
process.exit(1);
|
||||
}
|
||||
const rangeFrom = argv.rangeFrom as unknown as number;
|
||||
const rangeTo = argv.rangeTo as unknown as number;
|
||||
|
||||
if ((!username || !password) && !apiKey) {
|
||||
console.error('Either username and password or apiKey must be provided');
|
||||
process.exit(1);
|
||||
}
|
||||
if (argv.local) {
|
||||
esHost = 'http://localhost:9200';
|
||||
kbHost = 'http://127.0.0.1:5601';
|
||||
password = 'changeme';
|
||||
username = 'elastic';
|
||||
}
|
||||
|
||||
if (rangeFrom) {
|
||||
console.log(`rangeFrom = ${new Date(rangeFrom).toISOString()}`);
|
||||
}
|
||||
if ((!esHost || !kbHost) && !cloudId) {
|
||||
console.error(
|
||||
'Please provide either: --esHost and --kbHost or --cloudId\n'
|
||||
);
|
||||
|
||||
if (rangeTo) {
|
||||
console.log(`rangeTo = ${new Date(rangeTo).toISOString()}`);
|
||||
}
|
||||
console.log('Example 1:');
|
||||
console.log(
|
||||
'--kbHost https://foo.kb.us-west2.gcp.elastic-cloud.com --esHost https://foo.es.us-west2.gcp.elastic-cloud.com\n'
|
||||
);
|
||||
|
||||
initDiagnosticsBundle({
|
||||
esHost,
|
||||
kbHost,
|
||||
password,
|
||||
apiKey,
|
||||
cloudId,
|
||||
username,
|
||||
start: rangeFrom,
|
||||
end: rangeTo,
|
||||
kuery,
|
||||
})
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
console.log('Example 2:');
|
||||
console.log('--cloudId foo:very_secret\n');
|
||||
|
||||
console.log('Example 3:');
|
||||
console.log('--local');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ((!username || !password) && !apiKey) {
|
||||
console.error(
|
||||
'Please provide either: --username and --password or --apiKey \n'
|
||||
);
|
||||
|
||||
console.log('Example 1:');
|
||||
console.log('--username elastic --password changeme\n');
|
||||
|
||||
console.log('Example 2:');
|
||||
console.log('--apiKey very_secret\n');
|
||||
|
||||
console.log('Example 3:');
|
||||
console.log('--local');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (rangeFrom) {
|
||||
console.log(`rangeFrom = ${new Date(rangeFrom).toISOString()}`);
|
||||
}
|
||||
|
||||
if (rangeTo) {
|
||||
console.log(`rangeTo = ${new Date(rangeTo).toISOString()}`);
|
||||
}
|
||||
|
||||
initDiagnosticsBundle({
|
||||
esHost,
|
||||
kbHost: await getHostnameWithBasePath(kbHost),
|
||||
password,
|
||||
apiKey,
|
||||
cloudId,
|
||||
username,
|
||||
start: rangeFrom,
|
||||
end: rangeTo,
|
||||
kuery,
|
||||
})
|
||||
.catch((err) => {
|
||||
process.exitCode = 1;
|
||||
if (err instanceof AxiosError && err.response?.data) {
|
||||
console.error(err.response.data);
|
||||
return;
|
||||
}
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
process.exitCode = 1;
|
||||
if (err instanceof AxiosError && err.response?.data) {
|
||||
console.error(err.response.data);
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
if (err instanceof errors.ResponseError && err.meta.body.error.reason) {
|
||||
// @ts-expect-error
|
||||
console.error(err.meta.body.error.reason);
|
||||
return;
|
||||
}
|
||||
if (err instanceof errors.ResponseError && err.meta.body.error.reason) {
|
||||
// @ts-expect-error
|
||||
console.error(err.meta.body.error.reason);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
});
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
function convertDate(dateString: string): number {
|
||||
const parsed = datemath.parse(dateString);
|
||||
|
@ -113,3 +159,38 @@ function convertDate(dateString: string): number {
|
|||
|
||||
throw new Error(`Incorrect argument: ${dateString}`);
|
||||
}
|
||||
|
||||
async function getHostnameWithBasePath(kibanaHostname?: string) {
|
||||
if (!kibanaHostname) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedHostName = parseHostName(kibanaHostname);
|
||||
|
||||
try {
|
||||
await axios.get(parsedHostName, { maxRedirects: 0 });
|
||||
} catch (e) {
|
||||
if (isAxiosError(e) && e.response?.status === 302) {
|
||||
const location = e.response?.headers?.location ?? '';
|
||||
return `${parsedHostName}${location}`;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
return parsedHostName;
|
||||
}
|
||||
|
||||
function parseHostName(hostname: string) {
|
||||
// replace localhost with 127.0.0.1
|
||||
// https://github.com/node-fetch/node-fetch/issues/1624#issuecomment-1235826631
|
||||
hostname = hostname.replace('localhost', '127.0.0.1');
|
||||
|
||||
// extract just the hostname in case user provided a full URL
|
||||
const parsedUrl = new URL(hostname);
|
||||
return parsedUrl.origin;
|
||||
}
|
||||
|
||||
export function isAxiosError(e: AxiosError | Error): e is AxiosError {
|
||||
return 'isAxiosError' in e;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
import fs from 'fs/promises';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import type { APMIndices } from '@kbn/apm-data-access-plugin/server';
|
||||
import { APIReturnType } from '../../public/services/rest/create_call_apm_api';
|
||||
import { getDiagnosticsBundle } from '../../server/routes/diagnostics/get_diagnostics_bundle';
|
||||
|
@ -39,7 +39,7 @@ export async function initDiagnosticsBundle({
|
|||
}) {
|
||||
const auth = username && password ? { username, password } : undefined;
|
||||
const apiKeyHeader = apiKey ? { Authorization: `ApiKey ${apiKey}` } : {};
|
||||
const { kibanaHost } = parseCloudId(cloudId);
|
||||
const parsedCloudId = parseCloudId(cloudId);
|
||||
|
||||
const esClient = new Client({
|
||||
...(esHost ? { node: esHost } : {}),
|
||||
|
@ -48,12 +48,17 @@ export async function initDiagnosticsBundle({
|
|||
headers: { ...apiKeyHeader },
|
||||
});
|
||||
|
||||
const kibanaClient = axios.create({
|
||||
baseURL: kbHost ?? kibanaHost,
|
||||
const kibanaClientOpts = {
|
||||
baseURL: kbHost ?? parsedCloudId.kibanaHost,
|
||||
auth,
|
||||
headers: { 'kbn-xsrf': 'true', ...apiKeyHeader },
|
||||
});
|
||||
const apmIndices = await getApmIndices(kibanaClient);
|
||||
headers: {
|
||||
'kbn-xsrf': 'true',
|
||||
'elastic-api-version': '2023-10-31',
|
||||
...apiKeyHeader,
|
||||
},
|
||||
};
|
||||
|
||||
const apmIndices = await getApmIndices(kibanaClientOpts);
|
||||
|
||||
const bundle = await getDiagnosticsBundle({
|
||||
esClient,
|
||||
|
@ -62,8 +67,8 @@ export async function initDiagnosticsBundle({
|
|||
end,
|
||||
kuery,
|
||||
});
|
||||
const fleetPackageInfo = await getFleetPackageInfo(kibanaClient);
|
||||
const kibanaVersion = await getKibanaVersion(kibanaClient);
|
||||
const fleetPackageInfo = await getFleetPackageInfo(kibanaClientOpts);
|
||||
const kibanaVersion = await getKibanaVersion(kibanaClientOpts);
|
||||
|
||||
await saveReportToFile({ ...bundle, fleetPackageInfo, kibanaVersion });
|
||||
}
|
||||
|
@ -79,7 +84,7 @@ async function saveReportToFile(combinedReport: DiagnosticsBundle) {
|
|||
console.log(`Diagnostics report written to "${filename}"`);
|
||||
}
|
||||
|
||||
async function getApmIndices(kibanaClient: AxiosInstance) {
|
||||
async function getApmIndices(kbnClientOpts: AxiosRequestConfig) {
|
||||
interface Response {
|
||||
apmIndexSettings: Array<{
|
||||
configurationName: string;
|
||||
|
@ -88,8 +93,9 @@ async function getApmIndices(kibanaClient: AxiosInstance) {
|
|||
}>;
|
||||
}
|
||||
|
||||
const res = await kibanaClient.get<Response>(
|
||||
'/internal/apm/settings/apm-index-settings'
|
||||
const res = await axios.get<Response>(
|
||||
'/internal/apm/settings/apm-index-settings',
|
||||
kbnClientOpts
|
||||
);
|
||||
|
||||
return Object.fromEntries(
|
||||
|
@ -102,16 +108,16 @@ async function getApmIndices(kibanaClient: AxiosInstance) {
|
|||
) as APMIndices;
|
||||
}
|
||||
|
||||
async function getFleetPackageInfo(kibanaClient: AxiosInstance) {
|
||||
const res = await kibanaClient.get('/api/fleet/epm/packages/apm');
|
||||
async function getFleetPackageInfo(kbnClientOpts: AxiosRequestConfig) {
|
||||
const res = await axios.get('/api/fleet/epm/packages/apm', kbnClientOpts);
|
||||
return {
|
||||
version: res.data.response.version,
|
||||
isInstalled: res.data.response.status,
|
||||
};
|
||||
}
|
||||
|
||||
async function getKibanaVersion(kibanaClient: AxiosInstance) {
|
||||
const res = await kibanaClient.get('/api/status');
|
||||
async function getKibanaVersion(kbnClientOpts: AxiosRequestConfig) {
|
||||
const res = await axios.get('/api/status', kbnClientOpts);
|
||||
return res.data.version.number;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue