-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the @kbn/apm-config-loader package #77855
Changes from 13 commits
441e920
a06f9aa
15671fd
d0162ab
e57493f
6927b29
9436580
d64436e
7c842ba
3469894
ee3280c
20b6925
a0b47ba
e201543
8fc031d
61cda50
7b320f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# @kbn/apm-config-loader | ||
|
||
Configuration loader for the APM instrumentation script. | ||
|
||
This module is only meant to be used by the APM instrumentation script (`src/apm.js`) | ||
to load the required configuration options from the `kibana.yaml` configuration file with | ||
default values. | ||
|
||
### Why can't just use @kbn-config? | ||
|
||
`@kbn/config` is the recommended way to load and read the kibana configuration file, | ||
however in the specific case of APM, we want to only need the minimal dependencies | ||
before loading `elastic-apm-node` to avoid loosing instrumentation on the already loaded modules. | ||
pgayvallet marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
pid: | ||
enabled: true | ||
file: '/var/run/kibana.pid' | ||
obj: { val: 3 } | ||
arr: [1] | ||
empty_obj: {} | ||
empty_arr: [] | ||
obj: { val: 3 } | ||
arr: [1, 2] | ||
empty_obj: {} | ||
empty_arr: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
pid.enabled: true | ||
pid.file: '/var/run/kibana.pid' | ||
pid.obj: { val: 3 } | ||
pid.arr: [1, 2] | ||
pid.empty_obj: {} | ||
pid.empty_arr: [] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
foo: 1 | ||
bar: "pre-${KBN_ENV_VAR1}-mid-${KBN_ENV_VAR2}-post" | ||
|
||
elasticsearch: | ||
requestHeadersWhitelist: ["${KBN_ENV_VAR1}", "${KBN_ENV_VAR2}"] | ||
pgayvallet marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
foo: 1 | ||
bar: true | ||
xyz: ['1', '2'] | ||
empty_arr: [] | ||
abc: | ||
def: test | ||
qwe: 1 | ||
zyx: { val: 1 } | ||
pom.bom: 3 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
foo: 2 | ||
baz: bonkers | ||
xyz: ['3', '4'] | ||
arr: [1] | ||
empty_arr: [] | ||
abc: | ||
ghi: test2 | ||
qwe: 2 | ||
zyx: {} | ||
pom.mob: 4 |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||
{ | ||||||
"name": "@kbn/apm-config-loader", | ||||||
"main": "./target/index.js", | ||||||
"types": "./target/index.d.ts", | ||||||
"version": "1.0.0", | ||||||
"license": "Apache-2.0", | ||||||
"private": true, | ||||||
"scripts": { | ||||||
"build": "tsc", | ||||||
"kbn:bootstrap": "yarn build", | ||||||
"kbn:watch": "yarn build --watch" | ||||||
}, | ||||||
"dependencies": { | ||||||
"@elastic/safer-lodash-set": "0.0.0", | ||||||
"@kbn/utils": "1.0.0", | ||||||
"js-yaml": "3.13.1", | ||||||
"lodash": "^4.17.20" | ||||||
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To determine the default path of the config file, and of the data folder, I need to import Even with deep imports from kibana/packages/kbn-utils/src/path/index.ts Lines 22 to 23 in 9acf8d2
Not sure if this is blocker or not. I can split There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's ok if we end up importing Joi in this first pass. We already know it's a source of some slowness and I believe we'll still be able to see that at some level in our traces. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Node.js agent currently doesn't instrument joi, but if we were to do that in the future this could present a problem. But I wouldn't worry about this currently - just thought I'd mention it. |
||||||
}, | ||||||
"devDependencies": { | ||||||
"typescript": "4.0.2", | ||||||
"tsd": "^0.7.4" | ||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Licensed to Elasticsearch B.V. under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch B.V. licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
import { join } from 'path'; | ||
const childProcessModule = jest.requireActual('child_process'); | ||
const fsModule = jest.requireActual('fs'); | ||
|
||
export const mockedRootDir = '/root'; | ||
|
||
export const packageMock = { | ||
raw: {} as any, | ||
}; | ||
jest.doMock(join(mockedRootDir, 'package.json'), () => packageMock.raw, { virtual: true }); | ||
|
||
export const devConfigMock = { | ||
raw: {} as any, | ||
}; | ||
jest.doMock(join(mockedRootDir, 'config', 'apm.dev.js'), () => devConfigMock.raw, { | ||
virtual: true, | ||
}); | ||
|
||
export const gitRevExecMock = jest.fn(); | ||
jest.doMock('child_process', () => ({ | ||
...childProcessModule, | ||
execSync: (command: string, options: any) => { | ||
if (command.startsWith('git rev-parse')) { | ||
return gitRevExecMock(command, options); | ||
} | ||
return childProcessModule.execSync(command, options); | ||
}, | ||
})); | ||
|
||
export const readUuidFileMock = jest.fn(); | ||
jest.doMock('fs', () => ({ | ||
...fsModule, | ||
readFileSync: (path: string, options: any) => { | ||
if (path.endsWith('uuid')) { | ||
return readUuidFileMock(path, options); | ||
} | ||
return fsModule.readFileSync(path, options); | ||
}, | ||
})); | ||
|
||
export const resetAllMocks = () => { | ||
packageMock.raw = {}; | ||
devConfigMock.raw = {}; | ||
gitRevExecMock.mockReset(); | ||
readUuidFileMock.mockReset(); | ||
jest.resetModules(); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* | ||
* Licensed to Elasticsearch B.V. under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch B.V. licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
import { | ||
packageMock, | ||
mockedRootDir, | ||
gitRevExecMock, | ||
devConfigMock, | ||
readUuidFileMock, | ||
resetAllMocks, | ||
} from './config.test.mocks'; | ||
|
||
import { ApmConfiguration } from './config'; | ||
|
||
describe('ApmConfiguration', () => { | ||
beforeEach(() => { | ||
packageMock.raw = { | ||
version: '8.0.0', | ||
}; | ||
}); | ||
|
||
afterEach(() => { | ||
resetAllMocks(); | ||
}); | ||
|
||
it('sets the correct service name', () => { | ||
packageMock.raw = { | ||
version: '9.2.1', | ||
}; | ||
const config = new ApmConfiguration(mockedRootDir, {}, false); | ||
expect(config.getConfig('myservice').serviceName).toBe('myservice-9_2_1'); | ||
}); | ||
|
||
it('sets the giv revision in globalLabels', () => { | ||
pgayvallet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
gitRevExecMock.mockReturnValue('some-git-rev'); | ||
const config = new ApmConfiguration(mockedRootDir, {}, false); | ||
expect(config.getConfig('serviceName').globalLabels.git_rev).toBe('some-git-rev'); | ||
}); | ||
|
||
it('reads the kibana uuid from the uuid file', () => { | ||
readUuidFileMock.mockReturnValue('instance-uuid'); | ||
const config = new ApmConfiguration(mockedRootDir, {}, false); | ||
expect(config.getConfig('serviceName').globalLabels.kibana_uuid).toBe('instance-uuid'); | ||
}); | ||
|
||
it('uses the uuid from the kibana config if present', () => { | ||
readUuidFileMock.mockReturnValue('uuid-from-file'); | ||
const kibanaConfig = { | ||
server: { | ||
uuid: 'uuid-from-config', | ||
}, | ||
}; | ||
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, false); | ||
expect(config.getConfig('serviceName').globalLabels.kibana_uuid).toBe('uuid-from-config'); | ||
}); | ||
|
||
it('uses the correct default config depending on the `isDistributable` parameter', () => { | ||
let config = new ApmConfiguration(mockedRootDir, {}, false); | ||
expect(config.getConfig('serviceName')).toEqual( | ||
expect.objectContaining({ | ||
serverUrl: expect.any(String), | ||
secretToken: expect.any(String), | ||
}) | ||
); | ||
|
||
config = new ApmConfiguration(mockedRootDir, {}, true); | ||
expect(Object.keys(config.getConfig('serviceName'))).not.toContain('serverUrl'); | ||
}); | ||
|
||
it('loads the configuration from the kibana config file', () => { | ||
const kibanaConfig = { | ||
elastic: { | ||
apm: { | ||
active: true, | ||
serverUrl: 'https://url', | ||
secretToken: 'secret', | ||
}, | ||
}, | ||
}; | ||
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, true); | ||
expect(config.getConfig('serviceName')).toEqual( | ||
expect.objectContaining({ | ||
active: true, | ||
serverUrl: 'https://url', | ||
secretToken: 'secret', | ||
}) | ||
); | ||
}); | ||
|
||
it('loads the configuration from the dev config is present', () => { | ||
devConfigMock.raw = { | ||
active: true, | ||
serverUrl: 'https://dev-url.co', | ||
}; | ||
const config = new ApmConfiguration(mockedRootDir, {}, true); | ||
expect(config.getConfig('serviceName')).toEqual( | ||
expect.objectContaining({ | ||
active: true, | ||
serverUrl: 'https://dev-url.co', | ||
}) | ||
); | ||
}); | ||
|
||
it('respect the precedence of the dev config', () => { | ||
const kibanaConfig = { | ||
elastic: { | ||
apm: { | ||
active: true, | ||
serverUrl: 'https://url', | ||
secretToken: 'secret', | ||
}, | ||
}, | ||
}; | ||
devConfigMock.raw = { | ||
active: true, | ||
serverUrl: 'https://dev-url.co', | ||
}; | ||
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, true); | ||
expect(config.getConfig('serviceName')).toEqual( | ||
expect.objectContaining({ | ||
active: true, | ||
serverUrl: 'https://dev-url.co', | ||
secretToken: 'secret', | ||
}) | ||
); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.