Skip to content

Commit 9882afc

Browse files
authored
Add the @kbn/apm-config-loader package (#77855) (#78585)
* first shot of the apm configuration loader * revert changes to kibana config * remove test files for now * remove `?.` usages * use lazy config init to avoid crashing integration test runner * loader improvements * add config value override via cli args * add tests for utils package * add prod/dev config handling + loader tests * add tests for config * address josh's comments * nit on doc
1 parent 8dbdb90 commit 9882afc

31 files changed

+1456
-63
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
"@hapi/good-squeeze": "5.2.1",
125125
"@hapi/wreck": "^15.0.2",
126126
"@kbn/analytics": "1.0.0",
127+
"@kbn/apm-config-loader": "1.0.0",
127128
"@kbn/babel-preset": "1.0.0",
128129
"@kbn/config": "1.0.0",
129130
"@kbn/config-schema": "1.0.0",
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# @kbn/apm-config-loader
2+
3+
Configuration loader for the APM instrumentation script.
4+
5+
This module is only meant to be used by the APM instrumentation script (`src/apm.js`)
6+
to load the required configuration options from the `kibana.yaml` configuration file with
7+
default values.
8+
9+
### Why not just use @kbn-config?
10+
11+
`@kbn/config` is the recommended way to load and read the kibana configuration file,
12+
however in the specific case of APM, we want to only need the minimal dependencies
13+
before loading `elastic-apm-node` to avoid losing instrumentation on the already loaded modules.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pid:
2+
enabled: true
3+
file: '/var/run/kibana.pid'
4+
obj: { val: 3 }
5+
arr: [1]
6+
empty_obj: {}
7+
empty_arr: []
8+
obj: { val: 3 }
9+
arr: [1, 2]
10+
empty_obj: {}
11+
empty_arr: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pid.enabled: true
2+
pid.file: '/var/run/kibana.pid'
3+
pid.obj: { val: 3 }
4+
pid.arr: [1, 2]
5+
pid.empty_obj: {}
6+
pid.empty_arr: []
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
foo: 1
2+
bar: "pre-${KBN_ENV_VAR1}-mid-${KBN_ENV_VAR2}-post"
3+
4+
elasticsearch:
5+
requestHeadersWhitelist: ["${KBN_ENV_VAR1}", "${KBN_ENV_VAR2}"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
foo: 1
2+
bar: true
3+
xyz: ['1', '2']
4+
empty_arr: []
5+
abc:
6+
def: test
7+
qwe: 1
8+
zyx: { val: 1 }
9+
pom.bom: 3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
foo: 2
2+
baz: bonkers
3+
xyz: ['3', '4']
4+
arr: [1]
5+
empty_arr: []
6+
abc:
7+
ghi: test2
8+
qwe: 2
9+
zyx: {}
10+
pom.mob: 4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@kbn/apm-config-loader",
3+
"main": "./target/index.js",
4+
"types": "./target/index.d.ts",
5+
"version": "1.0.0",
6+
"license": "Apache-2.0",
7+
"private": true,
8+
"scripts": {
9+
"build": "tsc",
10+
"kbn:bootstrap": "yarn build",
11+
"kbn:watch": "yarn build --watch"
12+
},
13+
"dependencies": {
14+
"@elastic/safer-lodash-set": "0.0.0",
15+
"@kbn/utils": "1.0.0",
16+
"js-yaml": "3.13.1",
17+
"lodash": "^4.17.20"
18+
},
19+
"devDependencies": {
20+
"typescript": "4.0.2",
21+
"tsd": "^0.7.4"
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import { join } from 'path';
21+
const childProcessModule = jest.requireActual('child_process');
22+
const fsModule = jest.requireActual('fs');
23+
24+
export const mockedRootDir = '/root';
25+
26+
export const packageMock = {
27+
raw: {} as any,
28+
};
29+
jest.doMock(join(mockedRootDir, 'package.json'), () => packageMock.raw, { virtual: true });
30+
31+
export const devConfigMock = {
32+
raw: {} as any,
33+
};
34+
jest.doMock(join(mockedRootDir, 'config', 'apm.dev.js'), () => devConfigMock.raw, {
35+
virtual: true,
36+
});
37+
38+
export const gitRevExecMock = jest.fn();
39+
jest.doMock('child_process', () => ({
40+
...childProcessModule,
41+
execSync: (command: string, options: any) => {
42+
if (command.startsWith('git rev-parse')) {
43+
return gitRevExecMock(command, options);
44+
}
45+
return childProcessModule.execSync(command, options);
46+
},
47+
}));
48+
49+
export const readUuidFileMock = jest.fn();
50+
jest.doMock('fs', () => ({
51+
...fsModule,
52+
readFileSync: (path: string, options: any) => {
53+
if (path.endsWith('uuid')) {
54+
return readUuidFileMock(path, options);
55+
}
56+
return fsModule.readFileSync(path, options);
57+
},
58+
}));
59+
60+
export const resetAllMocks = () => {
61+
packageMock.raw = {};
62+
devConfigMock.raw = {};
63+
gitRevExecMock.mockReset();
64+
readUuidFileMock.mockReset();
65+
jest.resetModules();
66+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
import {
21+
packageMock,
22+
mockedRootDir,
23+
gitRevExecMock,
24+
devConfigMock,
25+
readUuidFileMock,
26+
resetAllMocks,
27+
} from './config.test.mocks';
28+
29+
import { ApmConfiguration } from './config';
30+
31+
describe('ApmConfiguration', () => {
32+
beforeEach(() => {
33+
packageMock.raw = {
34+
version: '8.0.0',
35+
build: {
36+
sha: 'sha',
37+
},
38+
};
39+
});
40+
41+
afterEach(() => {
42+
resetAllMocks();
43+
});
44+
45+
it('sets the correct service name', () => {
46+
packageMock.raw = {
47+
version: '9.2.1',
48+
};
49+
const config = new ApmConfiguration(mockedRootDir, {}, false);
50+
expect(config.getConfig('myservice').serviceName).toBe('myservice-9_2_1');
51+
});
52+
53+
it('sets the git revision from `git rev-parse` command in non distribution mode', () => {
54+
gitRevExecMock.mockReturnValue('some-git-rev');
55+
const config = new ApmConfiguration(mockedRootDir, {}, false);
56+
expect(config.getConfig('serviceName').globalLabels.git_rev).toBe('some-git-rev');
57+
});
58+
59+
it('sets the git revision from `pkg.build.sha` in distribution mode', () => {
60+
gitRevExecMock.mockReturnValue('dev-sha');
61+
packageMock.raw = {
62+
version: '9.2.1',
63+
build: {
64+
sha: 'distribution-sha',
65+
},
66+
};
67+
const config = new ApmConfiguration(mockedRootDir, {}, true);
68+
expect(config.getConfig('serviceName').globalLabels.git_rev).toBe('distribution-sha');
69+
});
70+
71+
it('reads the kibana uuid from the uuid file', () => {
72+
readUuidFileMock.mockReturnValue('instance-uuid');
73+
const config = new ApmConfiguration(mockedRootDir, {}, false);
74+
expect(config.getConfig('serviceName').globalLabels.kibana_uuid).toBe('instance-uuid');
75+
});
76+
77+
it('uses the uuid from the kibana config if present', () => {
78+
readUuidFileMock.mockReturnValue('uuid-from-file');
79+
const kibanaConfig = {
80+
server: {
81+
uuid: 'uuid-from-config',
82+
},
83+
};
84+
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, false);
85+
expect(config.getConfig('serviceName').globalLabels.kibana_uuid).toBe('uuid-from-config');
86+
});
87+
88+
it('uses the correct default config depending on the `isDistributable` parameter', () => {
89+
let config = new ApmConfiguration(mockedRootDir, {}, false);
90+
expect(config.getConfig('serviceName')).toEqual(
91+
expect.objectContaining({
92+
serverUrl: expect.any(String),
93+
secretToken: expect.any(String),
94+
})
95+
);
96+
97+
config = new ApmConfiguration(mockedRootDir, {}, true);
98+
expect(Object.keys(config.getConfig('serviceName'))).not.toContain('serverUrl');
99+
});
100+
101+
it('loads the configuration from the kibana config file', () => {
102+
const kibanaConfig = {
103+
elastic: {
104+
apm: {
105+
active: true,
106+
serverUrl: 'https://url',
107+
secretToken: 'secret',
108+
},
109+
},
110+
};
111+
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, true);
112+
expect(config.getConfig('serviceName')).toEqual(
113+
expect.objectContaining({
114+
active: true,
115+
serverUrl: 'https://url',
116+
secretToken: 'secret',
117+
})
118+
);
119+
});
120+
121+
it('loads the configuration from the dev config is present', () => {
122+
devConfigMock.raw = {
123+
active: true,
124+
serverUrl: 'https://dev-url.co',
125+
};
126+
const config = new ApmConfiguration(mockedRootDir, {}, true);
127+
expect(config.getConfig('serviceName')).toEqual(
128+
expect.objectContaining({
129+
active: true,
130+
serverUrl: 'https://dev-url.co',
131+
})
132+
);
133+
});
134+
135+
it('respect the precedence of the dev config', () => {
136+
const kibanaConfig = {
137+
elastic: {
138+
apm: {
139+
active: true,
140+
serverUrl: 'https://url',
141+
secretToken: 'secret',
142+
},
143+
},
144+
};
145+
devConfigMock.raw = {
146+
active: true,
147+
serverUrl: 'https://dev-url.co',
148+
};
149+
const config = new ApmConfiguration(mockedRootDir, kibanaConfig, true);
150+
expect(config.getConfig('serviceName')).toEqual(
151+
expect.objectContaining({
152+
active: true,
153+
serverUrl: 'https://dev-url.co',
154+
secretToken: 'secret',
155+
})
156+
);
157+
});
158+
});

0 commit comments

Comments
 (0)