[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:
Kibana Machine 2025-02-22 04:50:16 +11:00 committed by GitHub
parent 3bbf2d28c1
commit fe907479be
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 451 additions and 58 deletions

View file

@ -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

View file

@ -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
);
}

View file

@ -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,

View file

@ -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),
};

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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 || {};

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);
}

View file

@ -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();