-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
# Backport This will backport the following commits from `main` to `8.8`: - [Fix config stacking order (#158827)](#158827) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Alex Szabo","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-06-05T13:15:07Z","message":"Fix config stacking order (#158827)\n\n## Summary\r\nFixes: #155154 (introduced in #149878), builds on #155436 .\r\n\r\n- Adds tests to ensure the configuration merging order, check those for\r\nreference.\r\n- Updates the README to explain the intention\r\n \r\nFor the tests, I needed to output something to the logs. I hope it's not\r\na big issue to log it. If needed, I might hide that behind a verbose- or\r\nfeature flag.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <[email protected]>","sha":"c57589ec57e5e8265a66cd9c8c2102005736f6d8","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Team:Operations","release_note:fix","backport:prev-minor","v8.9.0"],"number":158827,"url":"https://github.com/elastic/kibana/pull/158827","mergeCommit":{"message":"Fix config stacking order (#158827)\n\n## Summary\r\nFixes: #155154 (introduced in #149878), builds on #155436 .\r\n\r\n- Adds tests to ensure the configuration merging order, check those for\r\nreference.\r\n- Updates the README to explain the intention\r\n \r\nFor the tests, I needed to output something to the logs. I hope it's not\r\na big issue to log it. If needed, I might hide that behind a verbose- or\r\nfeature flag.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <[email protected]>","sha":"c57589ec57e5e8265a66cd9c8c2102005736f6d8"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/158827","number":158827,"mergeCommit":{"message":"Fix config stacking order (#158827)\n\n## Summary\r\nFixes: #155154 (introduced in #149878), builds on #155436 .\r\n\r\n- Adds tests to ensure the configuration merging order, check those for\r\nreference.\r\n- Updates the README to explain the intention\r\n \r\nFor the tests, I needed to output something to the logs. I hope it's not\r\na big issue to log it. If needed, I might hide that behind a verbose- or\r\nfeature flag.\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <[email protected]>","sha":"c57589ec57e5e8265a66cd9c8c2102005736f6d8"}}]}] BACKPORT--> --------- Co-authored-by: Alex Szabo <[email protected]>
- Loading branch information
1 parent
9b343b4
commit 419cd20
Showing
5 changed files
with
261 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
243 changes: 243 additions & 0 deletions
243
src/cli/serve/integration_tests/config_ordering.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
/* | ||
* 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 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 or the Server | ||
* Side Public License, v 1. | ||
*/ | ||
|
||
import * as Fs from 'fs'; | ||
import * as Path from 'path'; | ||
import * as Os from 'os'; | ||
import * as Child from 'child_process'; | ||
import Del from 'del'; | ||
import * as Rx from 'rxjs'; | ||
import { filter, map, take, timeout } from 'rxjs/operators'; | ||
|
||
const tempDir = Path.join(Os.tmpdir(), 'kbn-config-test'); | ||
|
||
const kibanaPath = follow('../../../../scripts/kibana.js'); | ||
|
||
const TIMEOUT_MS = 20000; | ||
|
||
const envForTempDir = { | ||
env: { KBN_PATH_CONF: tempDir }, | ||
}; | ||
|
||
const TestFiles = { | ||
fileList: [] as string[], | ||
|
||
createEmptyConfigFiles(fileNames: string[], root: string = tempDir): string[] { | ||
const configFiles = []; | ||
for (const fileName of fileNames) { | ||
const filePath = Path.resolve(root, fileName); | ||
|
||
if (!Fs.existsSync(filePath)) { | ||
Fs.writeFileSync(filePath, 'dummy'); | ||
|
||
TestFiles.fileList.push(filePath); | ||
} | ||
|
||
configFiles.push(filePath); | ||
} | ||
|
||
return configFiles; | ||
}, | ||
cleanUpEmptyConfigFiles() { | ||
for (const filePath of TestFiles.fileList) { | ||
Del.sync(filePath); | ||
} | ||
TestFiles.fileList.length = 0; | ||
}, | ||
}; | ||
|
||
describe('Server configuration ordering', () => { | ||
let kibanaProcess: Child.ChildProcessWithoutNullStreams; | ||
|
||
beforeEach(() => { | ||
Fs.mkdirSync(tempDir, { recursive: true }); | ||
}); | ||
|
||
afterEach(async () => { | ||
if (kibanaProcess !== undefined) { | ||
const exitPromise = new Promise((resolve) => kibanaProcess?.once('exit', resolve)); | ||
kibanaProcess.kill('SIGKILL'); | ||
await exitPromise; | ||
} | ||
|
||
Del.sync(tempDir, { force: true }); | ||
TestFiles.cleanUpEmptyConfigFiles(); | ||
}); | ||
|
||
it('loads default config set without any options', async function () { | ||
TestFiles.createEmptyConfigFiles(['kibana.yml']); | ||
|
||
kibanaProcess = Child.spawn(process.execPath, [kibanaPath, '--verbose'], envForTempDir); | ||
const configList = await extractConfigurationOrder(kibanaProcess); | ||
|
||
expect(configList).toEqual(['kibana.yml']); | ||
}); | ||
|
||
it('loads serverless configs when --serverless is set', async () => { | ||
TestFiles.createEmptyConfigFiles([ | ||
'serverless.yml', | ||
'serverless.oblt.yml', | ||
'kibana.yml', | ||
'serverless.recent.yml', | ||
]); | ||
|
||
kibanaProcess = Child.spawn( | ||
process.execPath, | ||
[kibanaPath, '--verbose', '--serverless', 'oblt'], | ||
envForTempDir | ||
); | ||
const configList = await extractConfigurationOrder(kibanaProcess); | ||
|
||
expect(configList).toEqual([ | ||
'serverless.yml', | ||
'serverless.oblt.yml', | ||
'kibana.yml', | ||
'serverless.recent.yml', | ||
]); | ||
}); | ||
|
||
it('prefers --config options over default', async () => { | ||
const [configPath] = TestFiles.createEmptyConfigFiles([ | ||
'potato.yml', | ||
'serverless.yml', | ||
'serverless.oblt.yml', | ||
'kibana.yml', | ||
'serverless.recent.yml', | ||
]); | ||
|
||
kibanaProcess = Child.spawn( | ||
process.execPath, | ||
[kibanaPath, '--verbose', '--serverless', 'oblt', '--config', configPath], | ||
envForTempDir | ||
); | ||
const configList = await extractConfigurationOrder(kibanaProcess); | ||
|
||
expect(configList).toEqual([ | ||
'serverless.yml', | ||
'serverless.oblt.yml', | ||
'potato.yml', | ||
'serverless.recent.yml', | ||
]); | ||
}); | ||
|
||
it('defaults to "es" if --serverless and --dev are there', async () => { | ||
TestFiles.createEmptyConfigFiles([ | ||
'serverless.yml', | ||
'serverless.es.yml', | ||
'kibana.yml', | ||
'kibana.dev.yml', | ||
'serverless.dev.yml', | ||
]); | ||
|
||
kibanaProcess = Child.spawn( | ||
process.execPath, | ||
[kibanaPath, '--verbose', '--serverless', '--dev'], | ||
envForTempDir | ||
); | ||
const configList = await extractConfigurationOrder(kibanaProcess); | ||
|
||
expect(configList).toEqual([ | ||
'serverless.yml', | ||
'serverless.es.yml', | ||
'kibana.yml', | ||
'serverless.recent.yml', | ||
'kibana.dev.yml', | ||
'serverless.dev.yml', | ||
]); | ||
}); | ||
|
||
it('adds dev configs to the stack', async () => { | ||
TestFiles.createEmptyConfigFiles([ | ||
'serverless.yml', | ||
'serverless.security.yml', | ||
'serverless.recent.yml', | ||
'kibana.yml', | ||
'kibana.dev.yml', | ||
'serverless.dev.yml', | ||
]); | ||
|
||
kibanaProcess = Child.spawn( | ||
process.execPath, | ||
[kibanaPath, '--verbose', '--serverless', 'security', '--dev'], | ||
envForTempDir | ||
); | ||
|
||
const configList = await extractConfigurationOrder(kibanaProcess); | ||
|
||
expect(configList).toEqual([ | ||
'serverless.yml', | ||
'serverless.security.yml', | ||
'kibana.yml', | ||
'serverless.recent.yml', | ||
'kibana.dev.yml', | ||
'serverless.dev.yml', | ||
]); | ||
}); | ||
}); | ||
|
||
async function extractConfigurationOrder( | ||
proc: Child.ChildProcessWithoutNullStreams | ||
): Promise<string[] | undefined> { | ||
const configMessage = await waitForMessage(proc, /[Cc]onfig.*order:/, TIMEOUT_MS); | ||
|
||
const configList = configMessage | ||
.match(/order: (.*)$/) | ||
?.at(1) | ||
?.split(', ') | ||
?.map((path) => Path.basename(path)); | ||
|
||
return configList; | ||
} | ||
|
||
async function waitForMessage( | ||
proc: Child.ChildProcessWithoutNullStreams, | ||
expression: string | RegExp, | ||
timeoutMs: number | ||
): Promise<string> { | ||
const message$ = Rx.fromEvent(proc.stdout!, 'data').pipe( | ||
map((messages) => String(messages).split('\n').filter(Boolean)) | ||
); | ||
|
||
const trackedExpression$ = message$.pipe( | ||
// We know the sighup handler will be registered before this message logged | ||
filter((messages: string[]) => messages.some((m) => m.match(expression))), | ||
take(1) | ||
); | ||
|
||
const error$ = message$.pipe( | ||
filter((messages: string[]) => messages.some((line) => line.match(/fatal/i))), | ||
take(1), | ||
map((line) => new Error(line.join('\n'))) | ||
); | ||
|
||
const value = await Rx.firstValueFrom( | ||
Rx.race(trackedExpression$, error$).pipe( | ||
timeout({ | ||
first: timeoutMs, | ||
with: () => | ||
Rx.throwError( | ||
() => new Error(`Config options didn't appear in logs for ${timeoutMs / 1000}s...`) | ||
), | ||
}) | ||
) | ||
); | ||
|
||
if (value instanceof Error) { | ||
throw value; | ||
} | ||
|
||
if (Array.isArray(value)) { | ||
return value[0]; | ||
} else { | ||
return value; | ||
} | ||
} | ||
|
||
function follow(file: string) { | ||
return Path.relative(process.cwd(), Path.resolve(__dirname, file)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters