mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[8.x] [Synthtrace] Improve URL discovery when running locally in Serverless mode (#211670) (#212111)
# Backport This will backport the following commits from `main` to `8.x`: - [[Synthtrace] Improve URL discovery when running locally in Serverless mode (#211670)](https://github.com/elastic/kibana/pull/211670) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Abdul Wahab Zahid","email":"awahab07@yahoo.com"},"sourceCommit":{"committedDate":"2025-02-21T15:58:07Z","message":"[Synthtrace] Improve URL discovery when running locally in Serverless mode (#211670)\n\n## Summary\n\nThis PR improves how **Synthtrace** resolves the Kibana URL when only\n`--target` (Elasticsearch) is provided or when neither `--target` nor\n`--kibana` is specified. The CLI now attempts to **automatically\ndiscover** the appropriate URLs based on the provided arguments.\n\nSome adjustments were made to improve this discovery process, especially\nwhen running **locally in Serverless mode**, where Kibana may be using\n`http`, while Elasticsearch (ES) is on `https`. Additionally,\nself-signed certificates do not work with the IP address `127.0.0.1`, so\nthis PR defaults to `localhost` and warns the user if `127.0.0.1` is\ndetected in Serverless mode.\n\n### **Improvements**\n- If either of `--target` or `--kibana` or neither provided, the CLI\nattempts to **discovers the URLs** dynamically now in both Stateful and\nServerless.\n- Defaults to `localhost` instead of `127.0.0.1` to avoid SSL\ncertificate issues.\n- Provides a **clear error message and hint** when Kibana and ES use\ndifferent protocols (http vs https) and either or both are unreachable.\n\n### **Expected Behavior After This PR**\nThese commands should now work **seamlessly** in both **local Stateful**\nand **Serverless** modes:\n\n```sh\n✗ node scripts/synthtrace simple_logs\n```\n\nFor **Serverless mode**, these also work:\n\n```sh\n✗ node scripts/synthtrace simple_logs --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200 --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n### **(Side Note) Serverless Kibana with SSL Disabled**\nHowever, the following command will **fail** with an error message if\nKibana is running without SSL, while Elasticsearch is using `https`:\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200\n```\n\n#### **Error Output:**\n```sh\nLoading scenario from kibana/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts\nError: Could not connect to Kibana. request to https://elastic_serverless:changeme@localhost:5601/ failed, reason: write EPROTO 400882F501000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:\n\nIf your Kibana URL differs, consider using the '--kibana' parameter to customize it.\n```\n\n**Solution:** \nIf you must have to provide `--target` (non defaults), also provide\n`--kibana` or start Kibana with SSL enabled.\n```sh\n✗ yarn start --serverless=oblt --ssl\n```","sha":"cb71dff86e042a088aa13cc11f90b0673438b365","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","ci:project-deploy-observability","Team:obs-ux-infra_services","backport:version","v9.1.0","v8.19.0"],"title":"[Synthtrace] Improve URL discovery when running locally in Serverless mode","number":211670,"url":"https://github.com/elastic/kibana/pull/211670","mergeCommit":{"message":"[Synthtrace] Improve URL discovery when running locally in Serverless mode (#211670)\n\n## Summary\n\nThis PR improves how **Synthtrace** resolves the Kibana URL when only\n`--target` (Elasticsearch) is provided or when neither `--target` nor\n`--kibana` is specified. The CLI now attempts to **automatically\ndiscover** the appropriate URLs based on the provided arguments.\n\nSome adjustments were made to improve this discovery process, especially\nwhen running **locally in Serverless mode**, where Kibana may be using\n`http`, while Elasticsearch (ES) is on `https`. Additionally,\nself-signed certificates do not work with the IP address `127.0.0.1`, so\nthis PR defaults to `localhost` and warns the user if `127.0.0.1` is\ndetected in Serverless mode.\n\n### **Improvements**\n- If either of `--target` or `--kibana` or neither provided, the CLI\nattempts to **discovers the URLs** dynamically now in both Stateful and\nServerless.\n- Defaults to `localhost` instead of `127.0.0.1` to avoid SSL\ncertificate issues.\n- Provides a **clear error message and hint** when Kibana and ES use\ndifferent protocols (http vs https) and either or both are unreachable.\n\n### **Expected Behavior After This PR**\nThese commands should now work **seamlessly** in both **local Stateful**\nand **Serverless** modes:\n\n```sh\n✗ node scripts/synthtrace simple_logs\n```\n\nFor **Serverless mode**, these also work:\n\n```sh\n✗ node scripts/synthtrace simple_logs --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200 --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n### **(Side Note) Serverless Kibana with SSL Disabled**\nHowever, the following command will **fail** with an error message if\nKibana is running without SSL, while Elasticsearch is using `https`:\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200\n```\n\n#### **Error Output:**\n```sh\nLoading scenario from kibana/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts\nError: Could not connect to Kibana. request to https://elastic_serverless:changeme@localhost:5601/ failed, reason: write EPROTO 400882F501000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:\n\nIf your Kibana URL differs, consider using the '--kibana' parameter to customize it.\n```\n\n**Solution:** \nIf you must have to provide `--target` (non defaults), also provide\n`--kibana` or start Kibana with SSL enabled.\n```sh\n✗ yarn start --serverless=oblt --ssl\n```","sha":"cb71dff86e042a088aa13cc11f90b0673438b365"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/211670","number":211670,"mergeCommit":{"message":"[Synthtrace] Improve URL discovery when running locally in Serverless mode (#211670)\n\n## Summary\n\nThis PR improves how **Synthtrace** resolves the Kibana URL when only\n`--target` (Elasticsearch) is provided or when neither `--target` nor\n`--kibana` is specified. The CLI now attempts to **automatically\ndiscover** the appropriate URLs based on the provided arguments.\n\nSome adjustments were made to improve this discovery process, especially\nwhen running **locally in Serverless mode**, where Kibana may be using\n`http`, while Elasticsearch (ES) is on `https`. Additionally,\nself-signed certificates do not work with the IP address `127.0.0.1`, so\nthis PR defaults to `localhost` and warns the user if `127.0.0.1` is\ndetected in Serverless mode.\n\n### **Improvements**\n- If either of `--target` or `--kibana` or neither provided, the CLI\nattempts to **discovers the URLs** dynamically now in both Stateful and\nServerless.\n- Defaults to `localhost` instead of `127.0.0.1` to avoid SSL\ncertificate issues.\n- Provides a **clear error message and hint** when Kibana and ES use\ndifferent protocols (http vs https) and either or both are unreachable.\n\n### **Expected Behavior After This PR**\nThese commands should now work **seamlessly** in both **local Stateful**\nand **Serverless** modes:\n\n```sh\n✗ node scripts/synthtrace simple_logs\n```\n\nFor **Serverless mode**, these also work:\n\n```sh\n✗ node scripts/synthtrace simple_logs --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200 --kibana=http://elastic_serverless:changeme@localhost:5601\n```\n\n### **(Side Note) Serverless Kibana with SSL Disabled**\nHowever, the following command will **fail** with an error message if\nKibana is running without SSL, while Elasticsearch is using `https`:\n\n```sh\n✗ node scripts/synthtrace simple_logs --target=https://elastic_serverless:changeme@localhost:9200\n```\n\n#### **Error Output:**\n```sh\nLoading scenario from kibana/packages/kbn-apm-synthtrace/src/scenarios/simple_logs.ts\nError: Could not connect to Kibana. request to https://elastic_serverless:changeme@localhost:5601/ failed, reason: write EPROTO 400882F501000000:error:0A00010B:SSL routines:ssl3_get_record:wrong version number:../deps/openssl/openssl/ssl/record/ssl3_record.c:355:\n\nIf your Kibana URL differs, consider using the '--kibana' parameter to customize it.\n```\n\n**Solution:** \nIf you must have to provide `--target` (non defaults), also provide\n`--kibana` or start Kibana with SSL enabled.\n```sh\n✗ yarn start --serverless=oblt --ssl\n```","sha":"cb71dff86e042a088aa13cc11f90b0673438b365"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Abdul Wahab Zahid <awahab07@yahoo.com>
This commit is contained in:
parent
3bbf2d28c1
commit
fe907479be
16 changed files with 451 additions and 58 deletions
|
@ -99,18 +99,47 @@ Via the CLI, you can run scenarios, either using a fixed time range or continuou
|
|||
|
||||
For live data ingestion:
|
||||
|
||||
```
|
||||
```sh
|
||||
node scripts/synthtrace simple_trace.ts --target=http://admin:changeme@localhost:9200 --live
|
||||
```
|
||||
|
||||
For a fixed time window:
|
||||
|
||||
```
|
||||
```sh
|
||||
node scripts/synthtrace simple_trace.ts --target=http://admin:changeme@localhost:9200 --from=now-24h --to=now
|
||||
```
|
||||
|
||||
The script will try to automatically find bootstrapped APM indices. **If these indices do not exist, the script will exit with an error. It will not bootstrap the indices itself.**
|
||||
|
||||
#### Local Development
|
||||
|
||||
When running the CLI locally, you can simply use the following command to ingest data to a locally running Elasticsearch and Kibana instance:
|
||||
|
||||
```sh
|
||||
node scripts/synthtrace simple_trace.ts
|
||||
```
|
||||
_Assuming both Elasticsearch and Kibana are running on the default localhost ports with default credentials._
|
||||
|
||||
#### A note when Kibana URL differs from Elasticsearch URL
|
||||
|
||||
If the Kibana URL differs from the Elasticsearch URL in protocol or hostname, you should explicitly pass the `--kibana` option to the CLI along with `--target`.
|
||||
|
||||
For example when running ES (with ssl) and Kibana (without ssl) locally in Serverless mode, it's recommended to provide both `--target` and `--kibana` options as the auto-discovered Kibana URL might not be correct in this case.
|
||||
Also use `localhost` instead of `127.0.0.1` as the hostname as `127.0.0.1` will likely not work with self-signed certificates.
|
||||
|
||||
```sh
|
||||
node scripts/synthtrace simple_trace.ts --target=https://elastic_serverless:changeme@localhost:9200 --kibana=http://elastic_serverless:changeme@localhost:5601
|
||||
```
|
||||
|
||||
#### Using CLI for Elastic Cloud URLs
|
||||
|
||||
If you are ingesting data to Elastic Cloud, you can pass the `--target` option with the Elastic Cloud URL. The CLI will infer the Kibana URL from the Elasticsearch URL.
|
||||
Or you can pass only `--kibana` and the CLI will infer the Elasticsearch URL from the Kibana URL. Or pass both if URLs are not in default scheme.
|
||||
|
||||
```sh
|
||||
node scripts/synthtrace simple_trace.ts --target=https://<username>:<password>@your-cloud-cluster.kb.us-west2.gcp.elastic-cloud.com/
|
||||
```
|
||||
|
||||
### Understanding Scenario Files
|
||||
|
||||
Scenario files accept 3 arguments, 2 of them optional and 1 mandatory
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch';
|
||||
import { createLogger, LogLevel } from '../../lib/utils/create_logger';
|
||||
import { RunOptions } from './parse_run_cli_flags';
|
||||
import { getServiceUrls } from './get_service_urls';
|
||||
|
||||
jest.mock('node-fetch');
|
||||
jest.mock('./ssl');
|
||||
jest.mock('./get_service_urls', () => ({
|
||||
...jest.requireActual('./get_service_urls'),
|
||||
discoverAuth: jest.fn(),
|
||||
}));
|
||||
|
||||
const { Response } = jest.requireActual('node-fetch');
|
||||
|
||||
const logger = createLogger(LogLevel.debug);
|
||||
const runOptions = {
|
||||
logLevel: LogLevel.debug,
|
||||
concurrency: 1,
|
||||
'assume-package-version': 'latest',
|
||||
} as RunOptions;
|
||||
|
||||
describe('getServiceUrls', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('localhost Stateful', () => {
|
||||
it('should discover local service urls and auth if none provided', async () => {
|
||||
const expectedValidAuth = 'elastic:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([expectedValidAuth]);
|
||||
await expectServiceUrls(undefined, undefined, {
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover auth for local service urls', async () => {
|
||||
const expectedValidAuth = 'elastic:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([expectedValidAuth]);
|
||||
await expectServiceUrls('http://localhost:9200', 'http://localhost:5601', {
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover target from Kibana URL when target is not provided', async () => {
|
||||
const kibana = 'http://elastic:changeme@localhost:5601';
|
||||
const expectedValidAuth = 'elastic:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([expectedValidAuth]);
|
||||
await expectServiceUrls(undefined, kibana, {
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover Kibana URL from target when Kibana URL is not provided', async () => {
|
||||
const target = 'http://elastic:changeme@localhost:9200';
|
||||
const expectedValidAuth = 'elastic:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([expectedValidAuth]);
|
||||
await expectServiceUrls(target, undefined, {
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw an error if target URL is invalid', async () => {
|
||||
const target = 'http://invalid-kibana-url:9200';
|
||||
|
||||
mockFetchWithAllowedSegments(['5601']); // Only allow Kibana URL
|
||||
await expectServiceUrls(
|
||||
target,
|
||||
undefined,
|
||||
{
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
},
|
||||
`Failed to authenticate user for ${target}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if kibana URL is invalid', async () => {
|
||||
const kibana = 'http://invalid-kibana-url:5601';
|
||||
const target = 'http://elastic:changeme@localhost:9200';
|
||||
|
||||
mockFetchWithAllowedSegments(['9200']); // Only allow Elasticsearch URL
|
||||
await expectServiceUrls(
|
||||
target,
|
||||
kibana,
|
||||
{
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
},
|
||||
`Could not connect to Kibana.`
|
||||
);
|
||||
});
|
||||
|
||||
it('Fails to discover ES if Kibana URL is not reachable', async () => {
|
||||
const authStr = 'elastic:changeme@';
|
||||
const kibana = `http://${authStr}not-reachable:5601`;
|
||||
|
||||
mockFetchWithAllowedSegments(['localhost']);
|
||||
await expectServiceUrls(
|
||||
undefined,
|
||||
kibana,
|
||||
{
|
||||
esUrl: 'http://elastic:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic:changeme@localhost:5601',
|
||||
},
|
||||
`Could not discover Elasticsearch URL based on Kibana URL ${kibana.replace(authStr, '.*')}.` // On CI auth is stripped
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('localhost Serverless', () => {
|
||||
it('should discover local https service urls and auth if none provided', async () => {
|
||||
const expectedValidAuth = 'elastic_serverless:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([
|
||||
`https://${expectedValidAuth}@localhost:9200`,
|
||||
`http://${expectedValidAuth}@localhost:5601`,
|
||||
]); // Only allow https for ES and http for Kibana
|
||||
await expectServiceUrls(undefined, undefined, {
|
||||
esUrl: 'https://elastic_serverless:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic_serverless:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover auth for local https service urls', async () => {
|
||||
const expectedValidAuth = 'elastic_serverless:changeme';
|
||||
|
||||
mockFetchWithAllowedSegments([`https://${expectedValidAuth}`]); // Only allow https urls
|
||||
await expectServiceUrls('https://localhost:9200', 'https://localhost:5601', {
|
||||
esUrl: 'https://elastic_serverless:changeme@localhost:9200',
|
||||
kibanaUrl: 'https://elastic_serverless:changeme@localhost:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws error if target is https but https Kibana is not reachable', async () => {
|
||||
const target = 'https://elastic_serverless:changeme@localhost:9200';
|
||||
|
||||
mockFetchWithAllowedSegments([target, 'http://elastic_serverless:changeme@localhost:5601']); // Only allow http Kibana URL
|
||||
await expectServiceUrls(
|
||||
target,
|
||||
undefined,
|
||||
{
|
||||
esUrl: 'https://elastic_serverless:changeme@localhost:9200',
|
||||
kibanaUrl: 'http://elastic_serverless:changeme@localhost:5601',
|
||||
},
|
||||
`Could not connect to Kibana.`
|
||||
);
|
||||
});
|
||||
|
||||
it('allows a different https Kibana and a different https ES URL', async () => {
|
||||
const target = 'https://elastic_serverless:changeme@host-1:9200';
|
||||
const kibana = 'https://elastic_serverless:changeme@host-2:5601';
|
||||
|
||||
mockFetchWithAllowedSegments([target, kibana]); // Allow both URLs
|
||||
await expectServiceUrls(target, kibana, {
|
||||
esUrl: 'https://elastic_serverless:changeme@host-1:9200',
|
||||
kibanaUrl: 'https://elastic_serverless:changeme@host-2:5601',
|
||||
});
|
||||
});
|
||||
|
||||
it('logs the certificate warning if 127.0.0.1 is used', async () => {
|
||||
const target = 'https://elastic_serverless:changeme@127.0.0.1:9200';
|
||||
const kibana = 'https://elastic_serverless:changeme@localhost:5601';
|
||||
|
||||
const warnSpy = jest.spyOn(logger, 'warn');
|
||||
mockFetchWithAllowedSegments([target, kibana]);
|
||||
await expectServiceUrls(target, kibana, {
|
||||
esUrl: 'https://elastic_serverless:changeme@127.0.0.1:9200',
|
||||
kibanaUrl: 'https://elastic_serverless:changeme@localhost:5601',
|
||||
});
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('WARNING: Self-signed certificate may not work')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Elastic Cloud', () => {
|
||||
it('should discover .kb url if .es target is provided', async () => {
|
||||
const target = 'https://username:1223334444@cluster.kb.us-west2.gcp.elastic-cloud.com';
|
||||
const expectedKibanaUrl = target.replace('.es', '.kb');
|
||||
|
||||
mockFetchWithAllowedSegments([target, expectedKibanaUrl]);
|
||||
await expectServiceUrls(target, undefined, {
|
||||
esUrl: target,
|
||||
kibanaUrl: expectedKibanaUrl,
|
||||
});
|
||||
});
|
||||
|
||||
it('should discover .es url if .kb Kibana is provided', async () => {
|
||||
const kibana = 'https://username:1223334444@cluster.kb.us-west2.gcp.elastic-cloud.com';
|
||||
const expectedEsUrl = kibana.replace('.kb', '.es');
|
||||
|
||||
mockFetchWithAllowedSegments([kibana, expectedEsUrl]);
|
||||
await expectServiceUrls(undefined, kibana, {
|
||||
esUrl: expectedEsUrl,
|
||||
kibanaUrl: kibana,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function mockFetchWithAllowedSegments(allowedUrlSegments: string[]) {
|
||||
(fetch as unknown as jest.Mock).mockImplementation(async (url: string) => {
|
||||
if (allowedUrlSegments.some((segment) => url.includes(segment))) {
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
throw new Error('Url not found');
|
||||
});
|
||||
}
|
||||
|
||||
function expectServiceUrls(
|
||||
target?: string,
|
||||
kibana?: string,
|
||||
expected?: Awaited<ReturnType<typeof getServiceUrls>>,
|
||||
throws?: string
|
||||
) {
|
||||
if (throws) {
|
||||
return expect(getServiceUrls({ ...runOptions, logger, target, kibana })).rejects.toThrow(
|
||||
new RegExp(throws)
|
||||
);
|
||||
}
|
||||
|
||||
return expect(getServiceUrls({ ...runOptions, logger, target, kibana })).resolves.toEqual(
|
||||
expected
|
||||
);
|
||||
}
|
|
@ -13,6 +13,31 @@ import { Logger } from '../../lib/utils/create_logger';
|
|||
import { RunOptions } from './parse_run_cli_flags';
|
||||
import { getFetchAgent } from './ssl';
|
||||
|
||||
async function getFetchStatus(url: string) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
agent: getFetchAgent(url),
|
||||
});
|
||||
return response.status;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function stripAuthIfCi(url: string) {
|
||||
if (process.env.CI?.toLowerCase() === 'true') {
|
||||
return format({
|
||||
...parse(url),
|
||||
auth: undefined,
|
||||
});
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function stripTrailingSlash(url: string) {
|
||||
return url.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
async function discoverAuth(parsedTarget: Url) {
|
||||
const possibleCredentials = [`admin:changeme`, `elastic:changeme`, `elastic_serverless:changeme`];
|
||||
for (const auth of possibleCredentials) {
|
||||
|
@ -20,49 +45,44 @@ async function discoverAuth(parsedTarget: Url) {
|
|||
...parsedTarget,
|
||||
auth,
|
||||
});
|
||||
let status: number;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
agent: getFetchAgent(url),
|
||||
});
|
||||
status = response.status;
|
||||
} catch (err) {
|
||||
status = 0;
|
||||
}
|
||||
|
||||
const status = await getFetchStatus(url);
|
||||
if (status === 200) {
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Failed to authenticate user for ${format(parsedTarget)}`);
|
||||
throw new Error(`Failed to authenticate user for ${stripAuthIfCi(format(parsedTarget))}`);
|
||||
}
|
||||
|
||||
async function getKibanaUrl({ target, logger }: { target: string; logger: Logger }) {
|
||||
async function getKibanaUrl({
|
||||
targetKibanaUrl,
|
||||
logger,
|
||||
}: {
|
||||
targetKibanaUrl: string;
|
||||
logger: Logger;
|
||||
}) {
|
||||
try {
|
||||
const isCI = process.env.CI?.toLowerCase() === 'true';
|
||||
logger.debug(`Checking Kibana URL ${target} for a redirect`);
|
||||
logger.debug(`Checking Kibana URL ${stripAuthIfCi(targetKibanaUrl)} for a redirect`);
|
||||
|
||||
const unredirectedResponse = await fetch(target, {
|
||||
const targetAuth = parse(targetKibanaUrl).auth;
|
||||
|
||||
const unredirectedResponse = await fetch(targetKibanaUrl, {
|
||||
method: 'HEAD',
|
||||
follow: 1,
|
||||
redirect: 'manual',
|
||||
agent: getFetchAgent(target),
|
||||
agent: getFetchAgent(targetKibanaUrl),
|
||||
});
|
||||
|
||||
const discoveredKibanaUrl =
|
||||
unredirectedResponse.headers
|
||||
.get('location')
|
||||
?.replace('/spaces/enter', '')
|
||||
?.replace('spaces/space_selector', '') || target;
|
||||
|
||||
const parsedTarget = parse(target);
|
||||
|
||||
const parsedDiscoveredUrl = parse(discoveredKibanaUrl);
|
||||
?.replace('spaces/space_selector', '') || targetKibanaUrl;
|
||||
|
||||
const discoveredKibanaUrlWithAuth = format({
|
||||
...parsedDiscoveredUrl,
|
||||
auth: parsedTarget.auth,
|
||||
...parse(discoveredKibanaUrl),
|
||||
auth: targetAuth,
|
||||
});
|
||||
|
||||
const redirectedResponse = await fetch(discoveredKibanaUrlWithAuth, {
|
||||
|
@ -72,36 +92,110 @@ async function getKibanaUrl({ target, logger }: { target: string; logger: Logger
|
|||
|
||||
if (redirectedResponse.status !== 200) {
|
||||
throw new Error(
|
||||
`Expected HTTP 200 from ${discoveredKibanaUrlWithAuth}, got ${redirectedResponse.status}`
|
||||
`Expected HTTP 200 from ${stripAuthIfCi(discoveredKibanaUrlWithAuth)}, got ${
|
||||
redirectedResponse.status
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const discoveredKibanaUrlWithoutAuth = format({
|
||||
...parsedDiscoveredUrl,
|
||||
auth: undefined,
|
||||
});
|
||||
|
||||
logger.info(
|
||||
`Discovered kibana running at: ${
|
||||
isCI ? discoveredKibanaUrlWithoutAuth : discoveredKibanaUrlWithAuth
|
||||
}`
|
||||
);
|
||||
logger.info(`Discovered kibana running at: ${stripAuthIfCi(discoveredKibanaUrlWithAuth)}`);
|
||||
|
||||
return discoveredKibanaUrlWithAuth.replace(/\/$/, '');
|
||||
} catch (error) {
|
||||
throw new Error(`Could not connect to Kibana: ` + error.message);
|
||||
throw new Error(
|
||||
`Could not connect to Kibana. ${error.message} \n If your Kibana URL differs, consider using '--kibana' parameter to customize it. \n`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function discoverTargetFromKibanaUrl(kibanaUrl: string) {
|
||||
const suspectedParsedTargetUrl = parse(getTargetUrlFromKibana(kibanaUrl));
|
||||
|
||||
let targetAuth = suspectedParsedTargetUrl.auth;
|
||||
let targetProtocol = suspectedParsedTargetUrl.protocol;
|
||||
const urlWithSwitchedProtocol = parse(
|
||||
format({
|
||||
...suspectedParsedTargetUrl,
|
||||
protocol: suspectedParsedTargetUrl.protocol === 'https:' ? 'http:' : 'https:',
|
||||
})
|
||||
);
|
||||
const errorMessages = `Could not discover Elasticsearch URL based on Kibana URL ${stripAuthIfCi(
|
||||
kibanaUrl
|
||||
)}.`;
|
||||
|
||||
if (!targetAuth) {
|
||||
try {
|
||||
targetAuth = await discoverAuth(suspectedParsedTargetUrl);
|
||||
targetProtocol = suspectedParsedTargetUrl.protocol;
|
||||
} catch (_error) {
|
||||
try {
|
||||
// Retry with switched protocol
|
||||
targetAuth = await discoverAuth(urlWithSwitchedProtocol);
|
||||
targetProtocol = urlWithSwitchedProtocol.protocol;
|
||||
} catch (error) {
|
||||
throw new Error(`${errorMessages} ${error.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const status = await getFetchStatus(format(suspectedParsedTargetUrl));
|
||||
const statusWithSwitchedProtocol = await getFetchStatus(format(urlWithSwitchedProtocol));
|
||||
if (status === 0 && statusWithSwitchedProtocol !== 0) {
|
||||
targetProtocol = urlWithSwitchedProtocol.protocol;
|
||||
}
|
||||
|
||||
if (status === 0 && statusWithSwitchedProtocol === 0) {
|
||||
throw new Error(errorMessages);
|
||||
}
|
||||
}
|
||||
|
||||
return stripTrailingSlash(
|
||||
format({
|
||||
...suspectedParsedTargetUrl,
|
||||
auth: targetAuth,
|
||||
protocol: targetProtocol,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function getTargetUrlFromKibana(kibanaUrl: string) {
|
||||
const kbToEs = kibanaUrl.replace('.kb', '.es');
|
||||
|
||||
// If url contains localhost, replace 5601 with 9200
|
||||
if (kbToEs.includes('localhost') || kbToEs.includes('127.0.0.1')) {
|
||||
return kbToEs.replace(':5601', ':9200');
|
||||
}
|
||||
|
||||
return kbToEs;
|
||||
}
|
||||
|
||||
function getKibanaUrlFromTarget(target: string) {
|
||||
const esToKb = target.replace('.es', '.kb');
|
||||
// If url contains localhost, replace 9200 with 5601
|
||||
if (esToKb.includes('localhost') || esToKb.includes('127.0.0.1')) {
|
||||
return esToKb.replace(':9200', ':5601');
|
||||
}
|
||||
|
||||
return esToKb;
|
||||
}
|
||||
|
||||
function logCertificateWarningsIfNeeded(parsedTarget: Url, parsedKibanaUrl: Url, logger: Logger) {
|
||||
if (
|
||||
(parsedTarget.protocol === 'https:' || parsedKibanaUrl.protocol === 'https:') &&
|
||||
(parsedTarget.hostname === '127.0.0.1' || parsedKibanaUrl.hostname === '127.0.0.1')
|
||||
) {
|
||||
logger.warn(
|
||||
`WARNING: Self-signed certificate may not work with hostname: '127.0.0.1'. Consider using 'localhost' instead.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getServiceUrls({ logger, target, kibana }: RunOptions & { logger: Logger }) {
|
||||
if (!target) {
|
||||
// assume things are running locally
|
||||
kibana = kibana || 'http://127.0.0.1:5601';
|
||||
target = 'http://127.0.0.1:9200';
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
throw new Error('Could not determine an Elasticsearch target');
|
||||
if (!kibana) {
|
||||
kibana = 'http://localhost:5601';
|
||||
logger.info(`No target provided, defaulting Kibana to ${kibana}`);
|
||||
}
|
||||
target = await discoverTargetFromKibanaUrl(kibana);
|
||||
}
|
||||
|
||||
const parsedTarget = parse(target);
|
||||
|
@ -112,12 +206,14 @@ export async function getServiceUrls({ logger, target, kibana }: RunOptions & {
|
|||
auth = await discoverAuth(parsedTarget);
|
||||
}
|
||||
|
||||
const formattedEsUrl = format({
|
||||
...parsedTarget,
|
||||
auth,
|
||||
});
|
||||
const formattedEsUrl = stripTrailingSlash(
|
||||
format({
|
||||
...parsedTarget,
|
||||
auth,
|
||||
})
|
||||
);
|
||||
|
||||
const suspectedKibanaUrl = kibana || target.replace('.es', '.kb');
|
||||
const suspectedKibanaUrl = kibana || getKibanaUrlFromTarget(formattedEsUrl);
|
||||
|
||||
const parsedKibanaUrl = parse(suspectedKibanaUrl);
|
||||
|
||||
|
@ -126,7 +222,9 @@ export async function getServiceUrls({ logger, target, kibana }: RunOptions & {
|
|||
auth,
|
||||
});
|
||||
|
||||
const validatedKibanaUrl = await getKibanaUrl({ target: kibanaUrlWithAuth, logger });
|
||||
const validatedKibanaUrl = await getKibanaUrl({ targetKibanaUrl: kibanaUrlWithAuth, logger });
|
||||
|
||||
logCertificateWarningsIfNeeded(parsedTarget, parsedKibanaUrl, logger);
|
||||
|
||||
return {
|
||||
kibanaUrl: validatedKibanaUrl,
|
||||
|
|
|
@ -39,5 +39,6 @@ export const loggerProxy: Logger = isMainThread
|
|||
},
|
||||
debug: getLogMethod(LogLevel.debug),
|
||||
info: getLogMethod(LogLevel.info),
|
||||
warn: getLogMethod(LogLevel.warn),
|
||||
error: getLogMethod(LogLevel.error),
|
||||
};
|
||||
|
|
|
@ -59,6 +59,10 @@ export function parseRunCliFlags(flags: RunCliFlags) {
|
|||
parsedLogLevel = LogLevel.debug;
|
||||
break;
|
||||
|
||||
case 'warn':
|
||||
parsedLogLevel = LogLevel.warn;
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
parsedLogLevel = LogLevel.error;
|
||||
break;
|
||||
|
|
|
@ -12,8 +12,9 @@ import { CA_CERT_PATH } from '@kbn/dev-utils';
|
|||
import https from 'https';
|
||||
|
||||
export function getFetchAgent(url: string) {
|
||||
const isHTTPS = new URL(url).protocol === 'https:';
|
||||
const isLocalhost = new URL(url).hostname === 'localhost';
|
||||
const urlObj = new URL(url);
|
||||
const isHTTPS = urlObj.protocol === 'https:';
|
||||
const isLocalhost = urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1';
|
||||
return isHTTPS && isLocalhost ? new https.Agent({ rejectUnauthorized: false }) : undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,6 +109,9 @@ export async function startHistoricalDataUpload({
|
|||
case LogLevel.trace:
|
||||
logger.debug.apply({}, message.args);
|
||||
return;
|
||||
case LogLevel.warn:
|
||||
logger.warn.apply({}, message.args);
|
||||
return;
|
||||
case LogLevel.error:
|
||||
logger.error.apply({}, message.args);
|
||||
return;
|
||||
|
|
|
@ -13,7 +13,8 @@ export enum LogLevel {
|
|||
trace = 0,
|
||||
debug = 1,
|
||||
info = 2,
|
||||
error = 3,
|
||||
warn = 3,
|
||||
error = 4,
|
||||
}
|
||||
|
||||
function getTimeString() {
|
||||
|
@ -37,6 +38,12 @@ export function createLogger(logLevel: LogLevel) {
|
|||
console.log(getTimeString(), ...args);
|
||||
}
|
||||
},
|
||||
warn: (...args: any[]) => {
|
||||
if (logLevel <= LogLevel.warn) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(getTimeString(), ...args);
|
||||
}
|
||||
},
|
||||
error: (...args: any[]) => {
|
||||
if (logLevel <= LogLevel.error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -52,5 +59,6 @@ export interface Logger {
|
|||
perf: <T>(name: string, cb: () => T) => T;
|
||||
debug: (...args: any[]) => void;
|
||||
info: (...args: any[]) => void;
|
||||
warn: (...args: any[]) => void;
|
||||
error: (...args: any[]) => void;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient } }) => {
|
||||
const { logger } = runOptions;
|
||||
|
|
|
@ -39,7 +39,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient, apmEsClient } }) => {
|
||||
const { numServices = 3 } = runOptions.scenarioOpts || {};
|
||||
|
|
|
@ -53,7 +53,7 @@ const scenario: Scenario<LogDocument | InfraDocument | ApmFields> = async (runOp
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient, infraEsClient, apmEsClient } }) => {
|
||||
const {
|
||||
|
|
|
@ -71,7 +71,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient } }) => {
|
||||
const { logger } = runOptions;
|
||||
|
|
|
@ -79,7 +79,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient } }) => {
|
||||
const { logger } = runOptions;
|
||||
|
|
|
@ -21,7 +21,7 @@ const scenario: Scenario<LogDocument> = async (runOptions) => {
|
|||
if (isLogsDb) await logsEsClient.createIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
teardown: async ({ logsEsClient }) => {
|
||||
await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
if (isLogsDb) await logsEsClient.deleteIndexTemplate(IndexTemplateName.LogsDb);
|
||||
},
|
||||
generate: ({ range, clients: { logsEsClient } }) => {
|
||||
const { logger } = runOptions;
|
||||
|
|
|
@ -56,6 +56,10 @@ class LoggerAdapter implements Logger {
|
|||
this.log.info(args.join(this.joiner));
|
||||
}
|
||||
|
||||
warn(...args: any[]): void {
|
||||
this.log.warning(args.join(this.joiner));
|
||||
}
|
||||
|
||||
error(arg: string | Error): void {
|
||||
this.log.error(arg);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ export async function setupSynthtrace({
|
|||
const logger: Logger = {
|
||||
debug: (...args) => log.debug(...args),
|
||||
info: (...args) => log.info(...args),
|
||||
warn: (...args) => log.warning(...args),
|
||||
error: (...args) => log.error(args.map((arg) => arg.toString()).join(' ')),
|
||||
perf: (name, cb) => {
|
||||
const now = performance.now();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue