mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[APM] Refactor Cypress e2e tests (#109024)
* Improve script to setup users and roles * fix readme * CI fixes * add index permissions to roles * disable welcome screen * Run es archive once before tests * Fix ts issues * Update x-pack/plugins/apm/readme.md Co-authored-by: Nathan L Smith <nathan.smith@elastic.co> Co-authored-by: Nathan L Smith <nathan.smith@elastic.co>
This commit is contained in:
parent
d522cae193
commit
a93a7efa80
33 changed files with 453 additions and 413 deletions
|
@ -33,6 +33,7 @@ async function config({ readConfigFile }: FtrConfigProviderContext) {
|
|||
...xpackFunctionalTestsConfig.get('kbnTestServer'),
|
||||
serverArgs: [
|
||||
...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'),
|
||||
'--home.disableWelcomeScreen=true',
|
||||
'--csp.strict=false',
|
||||
// define custom kibana server args here
|
||||
`--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`,
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import url from 'url';
|
||||
import archives_metadata from '../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -28,15 +27,10 @@ const apisToIntercept = [
|
|||
];
|
||||
|
||||
describe('Home page', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
|
||||
it('Redirects to service page with rangeFrom and rangeTo added to the URL', () => {
|
||||
cy.visit('/app/apm');
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
import url from 'url';
|
||||
import archives_metadata from '../../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -59,15 +58,10 @@ const apisToIntercept = [
|
|||
];
|
||||
|
||||
describe('Service overview - header filters', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
|
||||
describe('Filtering by transaction type', () => {
|
||||
it('changes url when selecting different value', () => {
|
||||
cy.visit(serviceOverviewHref);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import url from 'url';
|
||||
import archives_metadata from '../../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -43,25 +42,21 @@ describe('Instances table', () => {
|
|||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
describe('when data is not loaded', () => {
|
||||
it('shows empty message', () => {
|
||||
cy.visit(serviceOverviewHref);
|
||||
cy.contains('opbeans-java');
|
||||
cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains(
|
||||
'No items found'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// describe('when data is not loaded', () => {
|
||||
// it('shows empty message', () => {
|
||||
// cy.visit(serviceOverviewHref);
|
||||
// cy.contains('opbeans-java');
|
||||
// cy.get('[data-test-subj="serviceInstancesTableContainer"]').contains(
|
||||
// 'No items found'
|
||||
// );
|
||||
// });
|
||||
// });
|
||||
|
||||
describe('when data is loaded', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
const serviceNodeName =
|
||||
'31651f3c624b81c55dd4633df0b5b9f9ab06b151121b0404ae796632cd1f87ad';
|
||||
|
||||
it('has data in the table', () => {
|
||||
cy.visit(serviceOverviewHref);
|
||||
cy.contains('opbeans-java');
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import url from 'url';
|
||||
import archives_metadata from '../../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -18,15 +17,10 @@ const baseUrl = url.format({
|
|||
});
|
||||
|
||||
describe('Service Overview', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
|
||||
it('persists transaction type selected when clicking on Transactions tab', () => {
|
||||
cy.visit(baseUrl);
|
||||
cy.get('[data-test-subj="headerFilterTransactionType"]').should(
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
import url from 'url';
|
||||
import moment from 'moment';
|
||||
import archives_metadata from '../../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -47,12 +46,6 @@ const apisToIntercept = [
|
|||
];
|
||||
|
||||
describe('Service overview: Time Comparison', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import url from 'url';
|
||||
import archives_metadata from '../../../fixtures/es_archiver/archives_metadata';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
|
||||
|
||||
const { start, end } = archives_metadata['apm_8.0.0'];
|
||||
|
||||
|
@ -17,15 +16,10 @@ const serviceOverviewHref = url.format({
|
|||
});
|
||||
|
||||
describe('Transactions Overview', () => {
|
||||
before(() => {
|
||||
esArchiverLoad('apm_8.0.0');
|
||||
});
|
||||
after(() => {
|
||||
esArchiverUnload('apm_8.0.0');
|
||||
});
|
||||
beforeEach(() => {
|
||||
cy.loginAsReadOnlyUser();
|
||||
});
|
||||
|
||||
it('persists transaction type selected when navigating to Overview tab', () => {
|
||||
cy.visit(serviceOverviewHref);
|
||||
cy.get('[data-test-subj="headerFilterTransactionType"]').should(
|
||||
|
|
|
@ -5,4 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// @see https://stackoverflow.com/a/50387233/434980
|
||||
// ResizeObserver error can be safely ignored
|
||||
if (err.message.includes('ResizeObserver loop limit exceeded')) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
import './commands';
|
||||
|
|
|
@ -6,31 +6,32 @@
|
|||
*/
|
||||
|
||||
import Path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const ES_ARCHIVE_DIR = './cypress/fixtures/es_archiver';
|
||||
|
||||
// Otherwise cy.exec would inject NODE_TLS_REJECT_UNAUTHORIZED=0 and node would abort if used over https
|
||||
// Otherwise execSync would inject NODE_TLS_REJECT_UNAUTHORIZED=0 and node would abort if used over https
|
||||
const NODE_TLS_REJECT_UNAUTHORIZED = '1';
|
||||
|
||||
export const esArchiverLoad = (folder: string) => {
|
||||
const path = Path.join(ES_ARCHIVE_DIR, folder);
|
||||
cy.exec(
|
||||
execSync(
|
||||
`node ../../../../scripts/es_archiver load "${path}" --config ../../../test/functional/config.js`,
|
||||
{ env: { NODE_TLS_REJECT_UNAUTHORIZED } }
|
||||
{ env: { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED } }
|
||||
);
|
||||
};
|
||||
|
||||
export const esArchiverUnload = (folder: string) => {
|
||||
const path = Path.join(ES_ARCHIVE_DIR, folder);
|
||||
cy.exec(
|
||||
execSync(
|
||||
`node ../../../../scripts/es_archiver unload "${path}" --config ../../../test/functional/config.js`,
|
||||
{ env: { NODE_TLS_REJECT_UNAUTHORIZED } }
|
||||
{ env: { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED } }
|
||||
);
|
||||
};
|
||||
|
||||
export const esArchiverResetKibana = () => {
|
||||
cy.exec(
|
||||
execSync(
|
||||
`node ../../../../scripts/es_archiver empty-kibana-index --config ../../../test/functional/config.js`,
|
||||
{ env: { NODE_TLS_REJECT_UNAUTHORIZED }, failOnNonZeroExit: false }
|
||||
{ env: { ...process.env, NODE_TLS_REJECT_UNAUTHORIZED } }
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,9 +9,9 @@ import { FtrConfigProviderContext } from '@kbn/test';
|
|||
import { cypressOpenTests } from './cypress_start';
|
||||
|
||||
async function openE2ETests({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const cypressConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
const kibanaConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
return {
|
||||
...cypressConfig.getAll(),
|
||||
...kibanaConfig.getAll(),
|
||||
testRunner: cypressOpenTests,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,13 +8,13 @@ import { argv } from 'yargs';
|
|||
import { FtrConfigProviderContext } from '@kbn/test';
|
||||
import { cypressRunTests } from './cypress_start';
|
||||
|
||||
const spec = argv.grep as string;
|
||||
const specArg = argv.spec as string | undefined;
|
||||
|
||||
async function runE2ETests({ readConfigFile }: FtrConfigProviderContext) {
|
||||
const cypressConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
const kibanaConfig = await readConfigFile(require.resolve('./config.ts'));
|
||||
return {
|
||||
...cypressConfig.getAll(),
|
||||
testRunner: cypressRunTests(spec),
|
||||
...kibanaConfig.getAll(),
|
||||
testRunner: cypressRunTests(specArg),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ import Url from 'url';
|
|||
import cypress from 'cypress';
|
||||
import { FtrProviderContext } from './ftr_provider_context';
|
||||
import archives_metadata from './cypress/fixtures/es_archiver/archives_metadata';
|
||||
import { createKibanaUserRole } from '../scripts/kibana-security/create_kibana_user_role';
|
||||
import { createApmUsersAndRoles } from '../scripts/create-apm-users-and-roles/create_apm_users_and_roles';
|
||||
import { esArchiverLoad, esArchiverUnload } from './cypress/tasks/es_archiver';
|
||||
|
||||
export function cypressRunTests(spec?: string) {
|
||||
return async ({ getService }: FtrProviderContext) => {
|
||||
|
@ -47,7 +48,7 @@ async function cypressStart(
|
|||
});
|
||||
|
||||
// Creates APM users
|
||||
await createKibanaUserRole({
|
||||
await createApmUsersAndRoles({
|
||||
elasticsearch: {
|
||||
username: config.get('servers.elasticsearch.username'),
|
||||
password: config.get('servers.elasticsearch.password'),
|
||||
|
@ -58,8 +59,10 @@ async function cypressStart(
|
|||
},
|
||||
});
|
||||
|
||||
return cypressExecution({
|
||||
...(spec !== 'undefined' ? { spec } : {}),
|
||||
await esArchiverLoad('apm_8.0.0');
|
||||
|
||||
const res = await cypressExecution({
|
||||
...(spec !== undefined ? { spec } : {}),
|
||||
config: { baseUrl: kibanaUrl },
|
||||
env: {
|
||||
START_DATE: start,
|
||||
|
@ -67,4 +70,8 @@ async function cypressStart(
|
|||
KIBANA_URL: kibanaUrl,
|
||||
},
|
||||
});
|
||||
|
||||
await esArchiverUnload('apm_8.0.0');
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -255,7 +255,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = `
|
|||
<span
|
||||
className="euiTableCellContent__text"
|
||||
>
|
||||
No errors were found
|
||||
No errors found
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -195,7 +195,7 @@ function ErrorGroupList({ items, serviceName }: Props) {
|
|||
return (
|
||||
<ManagedTable
|
||||
noItemsMessage={i18n.translate('xpack.apm.errorsTable.noErrorsLabel', {
|
||||
defaultMessage: 'No errors were found',
|
||||
defaultMessage: 'No errors found',
|
||||
})}
|
||||
items={items}
|
||||
columns={columns}
|
||||
|
|
|
@ -209,6 +209,17 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {
|
|||
}
|
||||
>
|
||||
<EuiBasicTable
|
||||
noItemsMessage={
|
||||
status === FETCH_STATUS.LOADING
|
||||
? i18n.translate(
|
||||
'xpack.apm.serviceOverview.errorsTable.loading',
|
||||
{ defaultMessage: 'Loading...' }
|
||||
)
|
||||
: i18n.translate(
|
||||
'xpack.apm.serviceOverview.errorsTable.noResults',
|
||||
{ defaultMessage: 'No errors found' }
|
||||
)
|
||||
}
|
||||
columns={columns}
|
||||
items={items}
|
||||
pagination={{
|
||||
|
|
|
@ -146,6 +146,15 @@ export function ServiceOverviewInstancesTable({
|
|||
isEmptyAndLoading={mainStatsItemCount === 0 && isLoading}
|
||||
>
|
||||
<EuiBasicTable
|
||||
noItemsMessage={
|
||||
isLoading
|
||||
? i18n.translate('xpack.apm.serviceOverview.loadingText', {
|
||||
defaultMessage: 'No instances found',
|
||||
})
|
||||
: i18n.translate('xpack.apm.serviceOverview.noResultsText', {
|
||||
defaultMessage: 'No instances found',
|
||||
})
|
||||
}
|
||||
data-test-subj="instancesTable"
|
||||
loading={isLoading}
|
||||
items={mainStatsItems}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { ReactNode, useCallback, useMemo } from 'react';
|
||||
|
@ -129,7 +130,13 @@ function UnoptimizedManagedTable<T>(props: Props<T>) {
|
|||
return (
|
||||
<EuiBasicTable
|
||||
loading={isLoading}
|
||||
noItemsMessage={noItemsMessage}
|
||||
noItemsMessage={
|
||||
isLoading
|
||||
? i18n.translate('xpack.apm.managedTable.loading', {
|
||||
defaultMessage: 'Loading...',
|
||||
})
|
||||
: noItemsMessage
|
||||
}
|
||||
items={renderedItems}
|
||||
columns={(columns as unknown) as Array<EuiBasicTableColumn<T>>} // EuiBasicTableColumn is stricter than ITableColumn
|
||||
sorting={sort}
|
||||
|
|
|
@ -234,12 +234,9 @@ export function TransactionsTable({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableTitle',
|
||||
{
|
||||
defaultMessage: 'Transactions',
|
||||
}
|
||||
)}
|
||||
{i18n.translate('xpack.apm.transactionsTable.title', {
|
||||
defaultMessage: 'Transactions',
|
||||
})}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
@ -250,12 +247,9 @@ export function TransactionsTable({
|
|||
latencyAggregationType={latencyAggregationType}
|
||||
transactionType={transactionType}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.serviceOverview.transactionsTableLinkText',
|
||||
{
|
||||
defaultMessage: 'View transactions',
|
||||
}
|
||||
)}
|
||||
{i18n.translate('xpack.apm.transactionsTable.linkText', {
|
||||
defaultMessage: 'View transactions',
|
||||
})}
|
||||
</TransactionOverviewLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
|
@ -265,7 +259,7 @@ export function TransactionsTable({
|
|||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.title',
|
||||
'xpack.apm.transactionsTable.cardinalityWarning.title',
|
||||
{
|
||||
defaultMessage:
|
||||
'This view shows a subset of reported transactions.',
|
||||
|
@ -276,7 +270,7 @@ export function TransactionsTable({
|
|||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.apm.transactionCardinalityWarning.body"
|
||||
id="xpack.apm.transactionsTable.cardinalityWarning.body"
|
||||
defaultMessage="The number of unique transaction names exceeds the configured value of {bucketSize}. Try reconfiguring your agents to group similar transactions or increase the value of {codeBlock}"
|
||||
values={{
|
||||
bucketSize,
|
||||
|
@ -291,7 +285,7 @@ export function TransactionsTable({
|
|||
path="/troubleshooting.html#troubleshooting-too-many-transactions"
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.apm.transactionCardinalityWarning.docsLink',
|
||||
'xpack.apm.transactionsTable.cardinalityWarning.docsLink',
|
||||
{ defaultMessage: 'Learn more in the docs' }
|
||||
)}
|
||||
</ElasticDocsLink>
|
||||
|
@ -307,6 +301,15 @@ export function TransactionsTable({
|
|||
isEmptyAndLoading={transactionGroupsTotalItems === 0 && isLoading}
|
||||
>
|
||||
<EuiBasicTable
|
||||
noItemsMessage={
|
||||
isLoading
|
||||
? i18n.translate('xpack.apm.transactionsTable.loading', {
|
||||
defaultMessage: 'Loading...',
|
||||
})
|
||||
: i18n.translate('xpack.apm.transactionsTable.noResults', {
|
||||
defaultMessage: 'No transaction groups found',
|
||||
})
|
||||
}
|
||||
loading={isLoading}
|
||||
items={transactionGroups}
|
||||
columns={columns}
|
||||
|
|
|
@ -138,22 +138,17 @@ node scripts/eslint.js x-pack/legacy/plugins/apm
|
|||
|
||||
## Setup default APM users
|
||||
|
||||
APM behaves differently depending on which the role and permissions a logged in user has.
|
||||
For testing purposes APM uses 3 custom users:
|
||||
|
||||
**apm_read_user**: Apps: read. Indices: read (`apm-*`)
|
||||
|
||||
**apm_write_user**: Apps: read/write. Indices: read (`apm-*`)
|
||||
|
||||
**kibana_write_user** Apps: read/write. Indices: None
|
||||
|
||||
To create the users with the correct roles run the following script:
|
||||
APM behaves differently depending on which the role and permissions a logged in user has. To create the users run:
|
||||
|
||||
```sh
|
||||
node x-pack/plugins/apm/scripts/setup-kibana-security.js --role-suffix <github-username-or-something-unique>
|
||||
node x-pack/plugins/apm/scripts/create-apm-users-and-roles.js --username elastic --password changeme --kibana-url http://localhost:5601 --role-suffix <github-username-or-something-unique>
|
||||
```
|
||||
|
||||
The users will be created with the password specified in kibana.dev.yml for `elasticsearch.password`
|
||||
This will create:
|
||||
|
||||
**apm_read_user**: Read only user
|
||||
|
||||
**apm_power_user**: Read+write user.
|
||||
|
||||
## Debugging Elasticsearch queries
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
* The two roles will be assigned to the already existing users: `apm_read_user`, `apm_write_user`, `kibana_write_user`
|
||||
*
|
||||
* This makes it possible to use the existing cloud users locally
|
||||
* Usage: node setup-kibana-security.js --role-suffix <YOUR-GITHUB-USERNAME-OR-SOMETHING-UNIQUE>
|
||||
* Usage: node create-apm-users-and-roles.js --role-suffix <YOUR-GITHUB-USERNAME-OR-SOMETHING-UNIQUE>
|
||||
******************************/
|
||||
|
||||
// compile typescript on the fly
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
require('@kbn/optimizer').registerNodeAutoTranspilation();
|
||||
|
||||
require('./kibana-security/setup-custom-kibana-user-role.ts');
|
||||
require('./create-apm-users-and-roles/create_apm_users_and_roles_cli.ts');
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AbortError, callKibana } from './helpers/call_kibana';
|
||||
import { createRole } from './helpers/create_role';
|
||||
import { powerUserRole } from './roles/power_user_role';
|
||||
import { readOnlyUserRole } from './roles/read_only_user_role';
|
||||
import { createOrUpdateUser } from './helpers/create_or_update_user';
|
||||
|
||||
export interface Elasticsearch {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface Kibana {
|
||||
roleSuffix: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export async function createApmUsersAndRoles({
|
||||
kibana,
|
||||
elasticsearch,
|
||||
}: {
|
||||
kibana: Kibana;
|
||||
elasticsearch: Elasticsearch;
|
||||
}) {
|
||||
const isSecurityEnabled = await getIsSecurityEnabled({
|
||||
elasticsearch,
|
||||
kibana,
|
||||
});
|
||||
if (!isSecurityEnabled) {
|
||||
throw new AbortError('Security must be enabled!');
|
||||
}
|
||||
|
||||
const KIBANA_READ_ROLE = `kibana_read_${kibana.roleSuffix}`;
|
||||
const KIBANA_POWER_ROLE = `kibana_power_${kibana.roleSuffix}`;
|
||||
|
||||
// roles definition
|
||||
const roles = [
|
||||
{ roleName: KIBANA_READ_ROLE, role: readOnlyUserRole },
|
||||
{ roleName: KIBANA_POWER_ROLE, role: powerUserRole },
|
||||
];
|
||||
|
||||
// create roles
|
||||
await Promise.all(
|
||||
roles.map(async (role) => createRole({ elasticsearch, kibana, ...role }))
|
||||
);
|
||||
|
||||
// user definitions
|
||||
const users = [
|
||||
{ username: 'apm_read_user', roles: [KIBANA_READ_ROLE] },
|
||||
{ username: 'apm_power_user', roles: [KIBANA_POWER_ROLE] },
|
||||
];
|
||||
|
||||
// create users
|
||||
await Promise.all(
|
||||
users.map(async (user) =>
|
||||
createOrUpdateUser({ elasticsearch, kibana, user })
|
||||
)
|
||||
);
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
async function getIsSecurityEnabled({
|
||||
elasticsearch,
|
||||
kibana,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibana: Kibana;
|
||||
}) {
|
||||
try {
|
||||
await callKibana({
|
||||
elasticsearch,
|
||||
kibana,
|
||||
options: {
|
||||
url: `/internal/security/me`,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { argv } from 'yargs';
|
||||
import { AbortError, isAxiosError } from './helpers/call_kibana';
|
||||
import { createApmUsersAndRoles } from './create_apm_users_and_roles';
|
||||
import { getKibanaVersion } from './helpers/get_version';
|
||||
|
||||
async function init() {
|
||||
const esUserName = (argv.username as string) || 'elastic';
|
||||
const esPassword = argv.password as string | undefined;
|
||||
const kibanaBaseUrl = argv.kibanaUrl as string | undefined;
|
||||
const kibanaRoleSuffix = argv.roleSuffix as string | undefined;
|
||||
|
||||
if (!esPassword) {
|
||||
console.error(
|
||||
'Please specify credentials for elasticsearch: `--username elastic --password abcd` '
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
if (!kibanaBaseUrl) {
|
||||
console.error(
|
||||
'Please specify the url for Kibana: `--kibana-url http://localhost:5601` '
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
if (
|
||||
!kibanaBaseUrl.startsWith('https://') &&
|
||||
!kibanaBaseUrl.startsWith('http://')
|
||||
) {
|
||||
console.error(
|
||||
'Kibana url must be prefixed with http(s):// `--kibana-url http://localhost:5601`'
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
if (!kibanaRoleSuffix) {
|
||||
console.error(
|
||||
'Please specify a unique suffix that will be added to your roles with `--role-suffix <suffix>` '
|
||||
);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
const kibana = { roleSuffix: kibanaRoleSuffix, hostname: kibanaBaseUrl };
|
||||
const elasticsearch = { username: esUserName, password: esPassword };
|
||||
|
||||
console.log({ kibana, elasticsearch });
|
||||
|
||||
const version = await getKibanaVersion({ elasticsearch, kibana });
|
||||
console.log(`Connected to Kibana ${version}`);
|
||||
|
||||
const users = await createApmUsersAndRoles({ elasticsearch, kibana });
|
||||
const credentials = users
|
||||
.map((u) => ` - ${u.username} / ${esPassword}`)
|
||||
.join('\n');
|
||||
|
||||
console.log(
|
||||
`\nYou can now login to ${kibana.hostname} with:\n${credentials}`
|
||||
);
|
||||
}
|
||||
|
||||
init().catch((e) => {
|
||||
if (e instanceof AbortError) {
|
||||
console.error(e.message);
|
||||
} else if (isAxiosError(e)) {
|
||||
console.error(
|
||||
`${e.config.method?.toUpperCase() || 'GET'} ${e.config.url} (Code: ${
|
||||
e.response?.status
|
||||
})`
|
||||
);
|
||||
|
||||
if (e.response) {
|
||||
console.error(
|
||||
JSON.stringify(
|
||||
{ request: e.config, response: e.response.data },
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
|
@ -6,46 +6,51 @@
|
|||
*/
|
||||
import axios, { AxiosRequestConfig, AxiosError } from 'axios';
|
||||
import { once } from 'lodash';
|
||||
import { Elasticsearch } from './create_kibana_user_role';
|
||||
import { Elasticsearch, Kibana } from '../create_apm_users_and_roles';
|
||||
|
||||
export async function callKibana<T>({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
options: AxiosRequestConfig;
|
||||
}): Promise<T> {
|
||||
const kibanaBasePath = await getKibanaBasePath({ kibanaHostname });
|
||||
const baseUrl = await getBaseUrl(kibana.hostname);
|
||||
const { username, password } = elasticsearch;
|
||||
|
||||
const { data } = await axios.request({
|
||||
...options,
|
||||
baseURL: kibanaHostname + kibanaBasePath,
|
||||
baseURL: baseUrl,
|
||||
auth: { username, password },
|
||||
headers: { 'kbn-xsrf': 'true', ...options.headers },
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
const getKibanaBasePath = once(
|
||||
async ({ kibanaHostname }: { kibanaHostname: string }) => {
|
||||
try {
|
||||
await axios.request({ url: kibanaHostname, maxRedirects: 0 });
|
||||
} catch (e) {
|
||||
if (isAxiosError(e)) {
|
||||
const location = e.response?.headers?.location;
|
||||
const isBasePath = RegExp(/^\/\w{3}$/).test(location);
|
||||
return isBasePath ? location : '';
|
||||
}
|
||||
|
||||
throw e;
|
||||
const getBaseUrl = once(async (kibanaHostname: string) => {
|
||||
try {
|
||||
await axios.request({ url: kibanaHostname, maxRedirects: 0 });
|
||||
} catch (e) {
|
||||
if (isAxiosError(e)) {
|
||||
const location = e.response?.headers?.location;
|
||||
const hasBasePath = RegExp(/^\/\w{3}$/).test(location);
|
||||
const basePath = hasBasePath ? location : '';
|
||||
return `${kibanaHostname}${basePath}`;
|
||||
}
|
||||
return '';
|
||||
|
||||
throw e;
|
||||
}
|
||||
);
|
||||
return kibanaHostname;
|
||||
});
|
||||
|
||||
export function isAxiosError(e: AxiosError | Error): e is AxiosError {
|
||||
return 'isAxiosError' in e;
|
||||
}
|
||||
|
||||
export class AbortError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -8,61 +8,8 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { difference, union } from 'lodash';
|
||||
import { callKibana, isAxiosError } from '../call_kibana';
|
||||
import { Elasticsearch, Kibana } from '../create_kibana_user_role';
|
||||
import { createRole } from './create_role';
|
||||
import { powerUserRole } from './power_user_role';
|
||||
import { readOnlyUserRole } from './read_only_user_role';
|
||||
|
||||
export async function createAPMUsers({
|
||||
kibana: { roleSuffix, hostname },
|
||||
elasticsearch,
|
||||
}: {
|
||||
kibana: Kibana;
|
||||
elasticsearch: Elasticsearch;
|
||||
}) {
|
||||
const KIBANA_READ_ROLE = `kibana_read_${roleSuffix}`;
|
||||
const KIBANA_POWER_ROLE = `kibana_power_${roleSuffix}`;
|
||||
const APM_USER_ROLE = 'apm_user';
|
||||
|
||||
// roles definition
|
||||
const roles = [
|
||||
{
|
||||
roleName: KIBANA_READ_ROLE,
|
||||
role: readOnlyUserRole,
|
||||
},
|
||||
{
|
||||
roleName: KIBANA_POWER_ROLE,
|
||||
role: powerUserRole,
|
||||
},
|
||||
];
|
||||
|
||||
// create roles
|
||||
await Promise.all(
|
||||
roles.map(async (role) =>
|
||||
createRole({ elasticsearch, kibanaHostname: hostname, ...role })
|
||||
)
|
||||
);
|
||||
|
||||
// users definition
|
||||
const users = [
|
||||
{
|
||||
username: 'apm_read_user',
|
||||
roles: [APM_USER_ROLE, KIBANA_READ_ROLE],
|
||||
},
|
||||
{
|
||||
username: 'apm_power_user',
|
||||
roles: [APM_USER_ROLE, KIBANA_POWER_ROLE],
|
||||
},
|
||||
];
|
||||
|
||||
// create users
|
||||
await Promise.all(
|
||||
users.map(async (user) =>
|
||||
createOrUpdateUser({ elasticsearch, kibanaHostname: hostname, user })
|
||||
)
|
||||
);
|
||||
}
|
||||
import { Elasticsearch, Kibana } from '../create_apm_users_and_roles';
|
||||
import { callKibana, isAxiosError } from './call_kibana';
|
||||
|
||||
interface User {
|
||||
username: string;
|
||||
|
@ -72,27 +19,27 @@ interface User {
|
|||
enabled?: boolean;
|
||||
}
|
||||
|
||||
async function createOrUpdateUser({
|
||||
export async function createOrUpdateUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
user,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
user: User;
|
||||
}) {
|
||||
const existingUser = await getUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
username: user.username,
|
||||
});
|
||||
if (!existingUser) {
|
||||
return createUser({ elasticsearch, kibanaHostname, newUser: user });
|
||||
return createUser({ elasticsearch, kibana, newUser: user });
|
||||
}
|
||||
|
||||
return updateUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
existingUser,
|
||||
newUser: user,
|
||||
});
|
||||
|
@ -100,16 +47,16 @@ async function createOrUpdateUser({
|
|||
|
||||
async function createUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
newUser,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
newUser: User;
|
||||
}) {
|
||||
const user = await callKibana<User>({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options: {
|
||||
method: 'POST',
|
||||
url: `/internal/security/users/${newUser.username}`,
|
||||
|
@ -127,12 +74,12 @@ async function createUser({
|
|||
|
||||
async function updateUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
existingUser,
|
||||
newUser,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
existingUser: User;
|
||||
newUser: User;
|
||||
}) {
|
||||
|
@ -149,7 +96,7 @@ async function updateUser({
|
|||
// assign role to user
|
||||
await callKibana({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options: {
|
||||
method: 'POST',
|
||||
url: `/internal/security/users/${username}`,
|
||||
|
@ -162,17 +109,17 @@ async function updateUser({
|
|||
|
||||
async function getUser({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
username,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
username: string;
|
||||
}) {
|
||||
try {
|
||||
return await callKibana<User>({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options: {
|
||||
url: `/internal/security/users/${username}`,
|
||||
},
|
|
@ -7,8 +7,8 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { Role } from '../../../../security/common/model';
|
||||
import { callKibana, isAxiosError } from '../call_kibana';
|
||||
import { Elasticsearch } from '../create_kibana_user_role';
|
||||
import { callKibana, isAxiosError } from './call_kibana';
|
||||
import { Elasticsearch, Kibana } from '../create_apm_users_and_roles';
|
||||
|
||||
type Privilege = [] | ['read'] | ['all'];
|
||||
export interface KibanaPrivileges {
|
||||
|
@ -20,18 +20,18 @@ export type RoleType = Omit<Role, 'name' | 'metadata'>;
|
|||
|
||||
export async function createRole({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
roleName,
|
||||
role,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
roleName: string;
|
||||
role: RoleType;
|
||||
}) {
|
||||
const roleFound = await getRole({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
roleName,
|
||||
});
|
||||
if (roleFound) {
|
||||
|
@ -41,7 +41,7 @@ export async function createRole({
|
|||
|
||||
await callKibana({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options: {
|
||||
method: 'PUT',
|
||||
url: `/api/security/role/${roleName}`,
|
||||
|
@ -52,24 +52,22 @@ export async function createRole({
|
|||
},
|
||||
});
|
||||
|
||||
console.log(
|
||||
`Created role "${roleName}" with privilege "${JSON.stringify(role.kibana)}"`
|
||||
);
|
||||
console.log(`Created role "${roleName}"`);
|
||||
}
|
||||
|
||||
async function getRole({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
roleName,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
kibana: Kibana;
|
||||
roleName: string;
|
||||
}): Promise<Role | null> {
|
||||
try {
|
||||
return await callKibana({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
kibana,
|
||||
options: {
|
||||
method: 'GET',
|
||||
url: `/api/security/role/${roleName}`,
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Elasticsearch, Kibana } from '../create_apm_users_and_roles';
|
||||
import { AbortError } from './call_kibana';
|
||||
import { callKibana, isAxiosError } from './call_kibana';
|
||||
|
||||
export async function getKibanaVersion({
|
||||
elasticsearch,
|
||||
kibana,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibana: Kibana;
|
||||
}) {
|
||||
try {
|
||||
const res: { version: { number: number } } = await callKibana({
|
||||
elasticsearch,
|
||||
kibana,
|
||||
options: {
|
||||
method: 'GET',
|
||||
url: `/api/status`,
|
||||
},
|
||||
});
|
||||
return res.version.number;
|
||||
} catch (e) {
|
||||
if (isAxiosError(e)) {
|
||||
switch (e.response?.status) {
|
||||
case 401:
|
||||
throw new AbortError(
|
||||
`Could not access Kibana with the provided credentials. Username: "${e.config.auth?.username}". Password: "${e.config.auth?.password}"`
|
||||
);
|
||||
|
||||
case 404:
|
||||
throw new AbortError(
|
||||
`Could not get version on ${e.config.url} (Code: 404)`
|
||||
);
|
||||
|
||||
default:
|
||||
throw new AbortError(
|
||||
`Cannot access Kibana on ${e.config.baseURL}. Please specify Kibana with: "--kibana-url <url>"`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
|
@ -5,10 +5,39 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RoleType } from './create_role';
|
||||
import { RoleType } from '../helpers/create_role';
|
||||
|
||||
export const powerUserRole: RoleType = {
|
||||
elasticsearch: { cluster: [], indices: [], run_as: [] },
|
||||
elasticsearch: {
|
||||
run_as: [],
|
||||
cluster: [],
|
||||
indices: [
|
||||
// apm
|
||||
{
|
||||
names: ['apm-*', 'logs-apm*', 'metrics-apm*', 'traces-apm*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
{
|
||||
names: ['observability-annotations'],
|
||||
privileges: ['read', 'write', 'view_index_metadata'],
|
||||
},
|
||||
// logs
|
||||
{
|
||||
names: ['logs-*', 'filebeat-*', 'kibana_sample_data_logs*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
// metrics
|
||||
{
|
||||
names: ['metrics-*', 'metricbeat-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
// uptime
|
||||
{
|
||||
names: ['heartbeat-*', 'synthetics-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: [],
|
|
@ -5,10 +5,41 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RoleType } from './create_role';
|
||||
import { RoleType } from '../helpers/create_role';
|
||||
|
||||
export const readOnlyUserRole: RoleType = {
|
||||
elasticsearch: { cluster: [], indices: [], run_as: [] },
|
||||
elasticsearch: {
|
||||
run_as: [],
|
||||
cluster: [],
|
||||
indices: [
|
||||
// apm
|
||||
{
|
||||
names: [
|
||||
'apm-*',
|
||||
'logs-apm*',
|
||||
'metrics-apm*',
|
||||
'traces-apm*',
|
||||
'observability-annotations',
|
||||
],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
// logs
|
||||
{
|
||||
names: ['logs-*', 'filebeat-*', 'kibana_sample_data_logs*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
// metrics
|
||||
{
|
||||
names: ['metrics-*', 'metricbeat-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
// uptime
|
||||
{
|
||||
names: ['heartbeat-*', 'synthetics-*'],
|
||||
privileges: ['read', 'view_index_metadata'],
|
||||
},
|
||||
],
|
||||
},
|
||||
kibana: [
|
||||
{
|
||||
base: [],
|
|
@ -9,11 +9,11 @@ const { argv } = require('yargs');
|
|||
const childProcess = require('child_process');
|
||||
const path = require('path');
|
||||
|
||||
const { spec } = argv;
|
||||
const { grep } = argv;
|
||||
|
||||
const e2eDir = path.join(__dirname, '../../ftr_e2e');
|
||||
|
||||
childProcess.execSync(
|
||||
`node ../../../../scripts/functional_tests --config ./cypress_run.ts --grep ${spec}`,
|
||||
`node ../../../../scripts/functional_tests --config ./cypress_run.ts --grep ${grep}`,
|
||||
{ cwd: e2eDir, stdio: 'inherit' }
|
||||
);
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { callKibana, isAxiosError } from './call_kibana';
|
||||
import { createAPMUsers } from './create_apm_users';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
export interface Elasticsearch {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface Kibana {
|
||||
roleSuffix: string;
|
||||
hostname: string;
|
||||
}
|
||||
|
||||
export async function createKibanaUserRole({
|
||||
kibana,
|
||||
elasticsearch,
|
||||
}: {
|
||||
kibana: Kibana;
|
||||
elasticsearch: Elasticsearch;
|
||||
}) {
|
||||
const version = await getKibanaVersion({
|
||||
elasticsearch,
|
||||
kibanaHostname: kibana.hostname,
|
||||
});
|
||||
console.log(`Connected to Kibana ${version}`);
|
||||
|
||||
const isSecurityEnabled = await getIsSecurityEnabled({
|
||||
elasticsearch,
|
||||
kibanaHostname: kibana.hostname,
|
||||
});
|
||||
if (!isSecurityEnabled) {
|
||||
throw new AbortError('Security must be enabled!');
|
||||
}
|
||||
|
||||
await createAPMUsers({ kibana, elasticsearch });
|
||||
}
|
||||
|
||||
async function getIsSecurityEnabled({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
}) {
|
||||
try {
|
||||
await callKibana({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
options: {
|
||||
url: `/internal/security/me`,
|
||||
},
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getKibanaVersion({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
}: {
|
||||
elasticsearch: Elasticsearch;
|
||||
kibanaHostname: string;
|
||||
}) {
|
||||
try {
|
||||
const res: { version: { number: number } } = await callKibana({
|
||||
elasticsearch,
|
||||
kibanaHostname,
|
||||
options: {
|
||||
method: 'GET',
|
||||
url: `/api/status`,
|
||||
},
|
||||
});
|
||||
return res.version.number;
|
||||
} catch (e) {
|
||||
if (isAxiosError(e)) {
|
||||
switch (e.response?.status) {
|
||||
case 401:
|
||||
throw new AbortError(
|
||||
`Could not access Kibana with the provided credentials. Username: "${e.config.auth?.username}". Password: "${e.config.auth?.password}"`
|
||||
);
|
||||
|
||||
case 404:
|
||||
throw new AbortError(
|
||||
`Could not get version on ${e.config.url} (Code: 404)`
|
||||
);
|
||||
|
||||
default:
|
||||
throw new AbortError(
|
||||
`Cannot access Kibana on ${e.config.baseURL}. Please specify Kibana with: "--kibana-url <url>"`
|
||||
);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
export class AbortError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { argv } from 'yargs';
|
||||
import { isAxiosError } from './call_kibana';
|
||||
import { createKibanaUserRole, AbortError } from './create_kibana_user_role';
|
||||
|
||||
const esUserName = (argv.username as string) || 'elastic';
|
||||
const esPassword = argv.password as string | undefined;
|
||||
const kibanaBaseUrl = argv.kibanaUrl as string | undefined;
|
||||
const kibanaRoleSuffix = argv.roleSuffix as string | undefined;
|
||||
|
||||
if (!esPassword) {
|
||||
throw new Error(
|
||||
'Please specify credentials for elasticsearch: `--username elastic --password abcd` '
|
||||
);
|
||||
}
|
||||
|
||||
if (!kibanaBaseUrl) {
|
||||
throw new Error(
|
||||
'Please specify the url for Kibana: `--kibana-url http://localhost:5601` '
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!kibanaBaseUrl.startsWith('https://') &&
|
||||
!kibanaBaseUrl.startsWith('http://')
|
||||
) {
|
||||
throw new Error(
|
||||
'Kibana url must be prefixed with http(s):// `--kibana-url http://localhost:5601`'
|
||||
);
|
||||
}
|
||||
|
||||
if (!kibanaRoleSuffix) {
|
||||
throw new Error(
|
||||
'Please specify a unique suffix that will be added to your roles with `--role-suffix <suffix>` '
|
||||
);
|
||||
}
|
||||
|
||||
console.log({
|
||||
kibanaRoleSuffix,
|
||||
esUserName,
|
||||
esPassword,
|
||||
kibanaBaseUrl,
|
||||
});
|
||||
|
||||
createKibanaUserRole({
|
||||
kibana: {
|
||||
roleSuffix: kibanaRoleSuffix,
|
||||
hostname: kibanaBaseUrl,
|
||||
},
|
||||
elasticsearch: {
|
||||
username: esUserName,
|
||||
password: esPassword,
|
||||
},
|
||||
}).catch((e) => {
|
||||
if (e instanceof AbortError) {
|
||||
console.error(e.message);
|
||||
} else if (isAxiosError(e)) {
|
||||
console.error(
|
||||
`${e.config.method?.toUpperCase() || 'GET'} ${e.config.url} (Code: ${
|
||||
e.response?.status
|
||||
})`
|
||||
);
|
||||
|
||||
if (e.response) {
|
||||
console.error(
|
||||
JSON.stringify(
|
||||
{ request: e.config, response: e.response.data },
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
console.error(e);
|
||||
}
|
||||
});
|
|
@ -5751,8 +5751,6 @@
|
|||
"xpack.apm.serviceOverview.transactionsTableColumnImpact": "インパクト",
|
||||
"xpack.apm.serviceOverview.transactionsTableColumnName": "名前",
|
||||
"xpack.apm.serviceOverview.transactionsTableColumnThroughput": "スループット",
|
||||
"xpack.apm.serviceOverview.transactionsTableLinkText": "トランザクションを表示",
|
||||
"xpack.apm.serviceOverview.transactionsTableTitle": "トランザクション",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.allocObjects": "Alloc. objects",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.allocSpace": "Alloc. space",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.cpuTime": "On-CPU",
|
||||
|
@ -5941,9 +5939,6 @@
|
|||
"xpack.apm.transactionActionMenu.viewInUptime": "ステータス",
|
||||
"xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "サンプルドキュメントを表示",
|
||||
"xpack.apm.transactionBreakdown.chartTitle": "スパンタイプ別時間",
|
||||
"xpack.apm.transactionCardinalityWarning.body": "一意のトランザクション名の数が構成された値{bucketSize}を超えています。エージェントを再構成し、類似したトランザクションをグループ化するか、{codeBlock}の値を増やしてください。",
|
||||
"xpack.apm.transactionCardinalityWarning.docsLink": "詳細はドキュメントをご覧ください",
|
||||
"xpack.apm.transactionCardinalityWarning.title": "このビューには、報告されたトランザクションのサブセットが表示されます。",
|
||||
"xpack.apm.transactionDetails.noTraceParentButtonTooltip": "トレースの親が見つかりませんでした",
|
||||
"xpack.apm.transactionDetails.percentOfTraceLabelExplanation": "{parentType, select, transaction {トランザクション} trace {トレース} }の割合が100%を超えています。これは、この{childType, select, span {スパン} transaction {トランザクション} }がルートトランザクションよりも時間がかかるためです。",
|
||||
"xpack.apm.transactionDetails.requestMethodLabel": "リクエストメソッド",
|
||||
|
|
|
@ -5779,8 +5779,6 @@
|
|||
"xpack.apm.serviceOverview.transactionsTableColumnImpact": "影响",
|
||||
"xpack.apm.serviceOverview.transactionsTableColumnName": "名称",
|
||||
"xpack.apm.serviceOverview.transactionsTableColumnThroughput": "吞吐量",
|
||||
"xpack.apm.serviceOverview.transactionsTableLinkText": "查看事务",
|
||||
"xpack.apm.serviceOverview.transactionsTableTitle": "事务",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.allocObjects": "分配的对象",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.allocSpace": "分配的空间",
|
||||
"xpack.apm.serviceProfiling.valueTypeLabel.cpuTime": "CPU 上",
|
||||
|
@ -5971,9 +5969,6 @@
|
|||
"xpack.apm.transactionActionMenu.viewInUptime": "状态",
|
||||
"xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "查看样例文档",
|
||||
"xpack.apm.transactionBreakdown.chartTitle": "跨度类型花费的时间",
|
||||
"xpack.apm.transactionCardinalityWarning.body": "唯一事务名称的数目超过 {bucketSize} 的已配置值。尝试重新配置您的代理以对类似的事务分组或增大 {codeBlock} 的值",
|
||||
"xpack.apm.transactionCardinalityWarning.docsLink": "在文档中了解详情",
|
||||
"xpack.apm.transactionCardinalityWarning.title": "此视图显示已报告事务的子集。",
|
||||
"xpack.apm.transactionDetails.errorCount": "{errorCount, number} 个 {errorCount, plural, other {错误}}",
|
||||
"xpack.apm.transactionDetails.errorsOverviewLinkTooltip": "{errorCount, plural, one {查看 1 个相关错误} other {查看 # 个相关错误}}",
|
||||
"xpack.apm.transactionDetails.noTraceParentButtonTooltip": "找不到上级追溯",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue