[APM] Use callWithInternalUser for agent configuration endpoin… (#50211)
* [APM] Use callWithInternalUser for agent configuration endpoints Closes #50050. * Review feedback * Use internalClient for agent conf queries only
This commit is contained in:
parent
7bb968c554
commit
f317c25852
|
@ -87,7 +87,7 @@ export const apm: LegacyPluginInitializer = kibana => {
|
|||
catalogue: ['apm'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['apm'],
|
||||
api: ['apm', 'apm_write'],
|
||||
catalogue: ['apm'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
|
|
|
@ -97,6 +97,7 @@ interface MockSetup {
|
|||
start: number;
|
||||
end: number;
|
||||
client: any;
|
||||
internalClient: any;
|
||||
config: {
|
||||
get: any;
|
||||
has: any;
|
||||
|
@ -122,12 +123,21 @@ export async function inspectSearchParams(
|
|||
}
|
||||
});
|
||||
|
||||
const internalClientSpy = jest.fn().mockReturnValueOnce({
|
||||
hits: {
|
||||
total: 0
|
||||
}
|
||||
});
|
||||
|
||||
const mockSetup = {
|
||||
start: 1528113600000,
|
||||
end: 1528977600000,
|
||||
client: {
|
||||
search: clientSpy
|
||||
} as any,
|
||||
internalClient: {
|
||||
search: internalClientSpy
|
||||
} as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
@ -153,8 +163,15 @@ export async function inspectSearchParams(
|
|||
// we're only extracting the search params
|
||||
}
|
||||
|
||||
let params;
|
||||
if (clientSpy.mock.calls.length) {
|
||||
params = clientSpy.mock.calls[0][0];
|
||||
} else {
|
||||
params = internalClientSpy.mock.calls[0][0];
|
||||
}
|
||||
|
||||
return {
|
||||
params: clientSpy.mock.calls[0][0],
|
||||
params,
|
||||
teardown: () => clientSpy.mockClear()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ describe('timeseriesFetcher', () => {
|
|||
client: {
|
||||
search: clientSpy
|
||||
} as any,
|
||||
internalClient: {
|
||||
search: clientSpy
|
||||
} as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
|
|
@ -92,10 +92,23 @@ interface APMOptions {
|
|||
includeLegacyData: boolean;
|
||||
}
|
||||
|
||||
export function getESClient(req: Legacy.Request) {
|
||||
interface ClientCreateOptions {
|
||||
clientAsInternalUser?: boolean;
|
||||
}
|
||||
|
||||
export type ESClient = ReturnType<typeof getESClient>;
|
||||
|
||||
export function getESClient(
|
||||
req: Legacy.Request,
|
||||
{ clientAsInternalUser = false }: ClientCreateOptions = {}
|
||||
) {
|
||||
const cluster = req.server.plugins.elasticsearch.getCluster('data');
|
||||
const query = req.query as Record<string, unknown>;
|
||||
|
||||
const callMethod = clientAsInternalUser
|
||||
? cluster.callWithInternalUser.bind(cluster)
|
||||
: cluster.callWithRequest.bind(cluster, req);
|
||||
|
||||
return {
|
||||
search: async <
|
||||
TDocument = unknown,
|
||||
|
@ -121,20 +134,18 @@ export function getESClient(req: Legacy.Request) {
|
|||
console.log(JSON.stringify(nextParams.body, null, 4));
|
||||
}
|
||||
|
||||
return (cluster.callWithRequest(
|
||||
req,
|
||||
'search',
|
||||
nextParams
|
||||
) as unknown) as Promise<ESSearchResponse<TDocument, TSearchRequest>>;
|
||||
return (callMethod('search', nextParams) as unknown) as Promise<
|
||||
ESSearchResponse<TDocument, TSearchRequest>
|
||||
>;
|
||||
},
|
||||
index: <Body>(params: APMIndexDocumentParams<Body>) => {
|
||||
return cluster.callWithRequest(req, 'index', params);
|
||||
return callMethod('index', params);
|
||||
},
|
||||
delete: (params: IndicesDeleteParams) => {
|
||||
return cluster.callWithRequest(req, 'delete', params);
|
||||
return callMethod('delete', params);
|
||||
},
|
||||
indicesCreate: (params: IndicesCreateParams) => {
|
||||
return cluster.callWithRequest(req, 'indices.create', params);
|
||||
return callMethod('indices.create', params);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ jest.mock('../settings/apm_indices/get_apm_indices', () => ({
|
|||
|
||||
function getMockRequest() {
|
||||
const callWithRequestSpy = jest.fn();
|
||||
const callWithInternalUserSpy = jest.fn();
|
||||
const mockRequest = ({
|
||||
params: {},
|
||||
query: {},
|
||||
|
@ -28,14 +29,17 @@ function getMockRequest() {
|
|||
config: () => ({ get: () => 'apm-*' }),
|
||||
plugins: {
|
||||
elasticsearch: {
|
||||
getCluster: () => ({ callWithRequest: callWithRequestSpy })
|
||||
getCluster: () => ({
|
||||
callWithRequest: callWithRequestSpy,
|
||||
callWithInternalUser: callWithInternalUserSpy
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
getUiSettingsService: () => ({ get: async () => false })
|
||||
} as any) as Legacy.Request;
|
||||
|
||||
return { callWithRequestSpy, mockRequest };
|
||||
return { callWithRequestSpy, callWithInternalUserSpy, mockRequest };
|
||||
}
|
||||
|
||||
describe('setupRequest', () => {
|
||||
|
@ -57,6 +61,27 @@ describe('setupRequest', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should call callWithInternalUser with default args', async () => {
|
||||
const { mockRequest, callWithInternalUserSpy } = getMockRequest();
|
||||
const { internalClient } = await setupRequest(mockRequest);
|
||||
await internalClient.search({
|
||||
index: 'apm-*',
|
||||
body: { foo: 'bar' }
|
||||
} as any);
|
||||
expect(callWithInternalUserSpy).toHaveBeenCalledWith('search', {
|
||||
index: 'apm-*',
|
||||
body: {
|
||||
foo: 'bar',
|
||||
query: {
|
||||
bool: {
|
||||
filter: [{ range: { 'observer.version_major': { gte: 7 } } }]
|
||||
}
|
||||
}
|
||||
},
|
||||
ignore_throttled: true
|
||||
});
|
||||
});
|
||||
|
||||
describe('observer.version_major filter', () => {
|
||||
describe('if index is apm-*', () => {
|
||||
it('should merge `observer.version_major` filter with existing boolean filters', async () => {
|
||||
|
|
|
@ -41,7 +41,8 @@ export async function setupRequest(req: Legacy.Request) {
|
|||
start: moment.utc(query.start).valueOf(),
|
||||
end: moment.utc(query.end).valueOf(),
|
||||
uiFiltersES,
|
||||
client: getESClient(req),
|
||||
client: getESClient(req, { clientAsInternalUser: false }),
|
||||
internalClient: getESClient(req, { clientAsInternalUser: true }),
|
||||
config,
|
||||
indices
|
||||
};
|
||||
|
|
|
@ -21,7 +21,7 @@ export async function createOrUpdateConfiguration({
|
|||
>;
|
||||
setup: Setup;
|
||||
}) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
const params: APMIndexDocumentParams<AgentConfiguration> = {
|
||||
refresh: true,
|
||||
|
@ -44,5 +44,5 @@ export async function createOrUpdateConfiguration({
|
|||
params.id = configurationId;
|
||||
}
|
||||
|
||||
return client.index(params);
|
||||
return internalClient.index(params);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ export async function deleteConfiguration({
|
|||
configurationId: string;
|
||||
setup: Setup;
|
||||
}) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
const params = {
|
||||
refresh: 'wait_for',
|
||||
|
@ -21,5 +21,5 @@ export async function deleteConfiguration({
|
|||
id: configurationId
|
||||
};
|
||||
|
||||
return client.delete(params);
|
||||
return internalClient.delete(params);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ export async function getExistingEnvironmentsForService({
|
|||
serviceName: string | undefined;
|
||||
setup: Setup;
|
||||
}) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
const bool = serviceName
|
||||
? { filter: [{ term: { [SERVICE_NAME]: serviceName } }] }
|
||||
|
@ -42,7 +42,7 @@ export async function getExistingEnvironmentsForService({
|
|||
}
|
||||
};
|
||||
|
||||
const resp = await client.search(params);
|
||||
const resp = await internalClient.search(params);
|
||||
const buckets = idx(resp.aggregations, _ => _.environments.buckets) || [];
|
||||
return buckets.map(bucket => bucket.key as string);
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@ export type AgentConfigurationListAPIResponse = PromiseReturnType<
|
|||
typeof listConfigurations
|
||||
>;
|
||||
export async function listConfigurations({ setup }: { setup: Setup }) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
const params = {
|
||||
index: indices['apm_oss.apmAgentConfigurationIndex']
|
||||
};
|
||||
|
||||
const resp = await client.search<AgentConfiguration>(params);
|
||||
const resp = await internalClient.search<AgentConfiguration>(params);
|
||||
return resp.hits.hits.map(item => ({
|
||||
id: item._id,
|
||||
...item._source
|
||||
|
|
|
@ -16,7 +16,7 @@ export async function markAppliedByAgent({
|
|||
body: AgentConfiguration;
|
||||
setup: Setup;
|
||||
}) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
const params = {
|
||||
index: indices['apm_oss.apmAgentConfigurationIndex'],
|
||||
|
@ -27,5 +27,5 @@ export async function markAppliedByAgent({
|
|||
}
|
||||
};
|
||||
|
||||
return client.index<AgentConfiguration>(params);
|
||||
return internalClient.index<AgentConfiguration>(params);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ describe('search configurations', () => {
|
|||
setup: ({
|
||||
config: { get: () => '' },
|
||||
client: { search: async () => searchMocks },
|
||||
internalClient: { search: async () => searchMocks },
|
||||
indices: {
|
||||
apm_oss: {
|
||||
sourcemapIndices: 'myIndex',
|
||||
|
@ -41,6 +42,7 @@ describe('search configurations', () => {
|
|||
setup: ({
|
||||
config: { get: () => '' },
|
||||
client: { search: async () => searchMocks },
|
||||
internalClient: { search: async () => searchMocks },
|
||||
indices: {
|
||||
apm_oss: {
|
||||
sourcemapIndices: 'myIndex',
|
||||
|
|
|
@ -20,7 +20,7 @@ export async function searchConfigurations({
|
|||
environment?: string;
|
||||
setup: Setup;
|
||||
}) {
|
||||
const { client, indices } = setup;
|
||||
const { internalClient, indices } = setup;
|
||||
|
||||
// sorting order
|
||||
// 1. exact match: service.name AND service.environment (eg. opbeans-node / production)
|
||||
|
@ -49,7 +49,9 @@ export async function searchConfigurations({
|
|||
}
|
||||
};
|
||||
|
||||
const resp = await client.search<AgentConfiguration, typeof params>(params);
|
||||
const resp = await internalClient.search<AgentConfiguration, typeof params>(
|
||||
params
|
||||
);
|
||||
const { hits } = resp.hits;
|
||||
|
||||
const exactMatch = hits.find(
|
||||
|
|
|
@ -13,6 +13,9 @@ function getSetup() {
|
|||
client: {
|
||||
search: jest.fn()
|
||||
} as any,
|
||||
internalClient: {
|
||||
search: jest.fn()
|
||||
} as any,
|
||||
config: {
|
||||
get: jest.fn<any, string[]>((key: string) => {
|
||||
switch (key) {
|
||||
|
|
|
@ -30,6 +30,7 @@ describe('getTransactionBreakdown', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
@ -54,6 +55,7 @@ describe('getTransactionBreakdown', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
@ -95,6 +97,7 @@ describe('getTransactionBreakdown', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
@ -135,6 +138,7 @@ describe('getTransactionBreakdown', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
@ -159,6 +163,7 @@ describe('getTransactionBreakdown', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
|
|
@ -26,6 +26,7 @@ describe('getAnomalySeries', () => {
|
|||
start: 0,
|
||||
end: 500000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('timeseriesFetcher', () => {
|
|||
start: 1528113600000,
|
||||
end: 1528977600000,
|
||||
client: { search: clientSpy } as any,
|
||||
internalClient: { search: clientSpy } as any,
|
||||
config: {
|
||||
get: () => 'myIndex' as any,
|
||||
has: () => true
|
||||
|
|
|
@ -38,9 +38,17 @@ describe('createApi', () => {
|
|||
},
|
||||
handler: async () => null
|
||||
}))
|
||||
.add(() => ({
|
||||
path: '/baz',
|
||||
method: 'PUT',
|
||||
options: {
|
||||
tags: ['access:apm', 'access:apm_write']
|
||||
},
|
||||
handler: async () => null
|
||||
}))
|
||||
.init(coreMock, legacySetupMock);
|
||||
|
||||
expect(legacySetupMock.server.route).toHaveBeenCalledTimes(2);
|
||||
expect(legacySetupMock.server.route).toHaveBeenCalledTimes(3);
|
||||
|
||||
const firstRoute = legacySetupMock.server.route.mock.calls[0][0];
|
||||
|
||||
|
@ -63,6 +71,17 @@ describe('createApi', () => {
|
|||
path: '/bar',
|
||||
handler: expect.any(Function)
|
||||
});
|
||||
|
||||
const thirdRoute = legacySetupMock.server.route.mock.calls[2][0];
|
||||
|
||||
expect(thirdRoute).toEqual({
|
||||
method: 'PUT',
|
||||
options: {
|
||||
tags: ['access:apm', 'access:apm_write']
|
||||
},
|
||||
path: '/baz',
|
||||
handler: expect.any(Function)
|
||||
});
|
||||
});
|
||||
|
||||
describe('when validating', () => {
|
||||
|
|
|
@ -33,12 +33,11 @@ export function createApi() {
|
|||
init(core: CoreSetup, __LEGACY: LegacySetup) {
|
||||
const { server } = __LEGACY;
|
||||
factoryFns.forEach(fn => {
|
||||
const { params = {}, ...route } = fn(core, __LEGACY) as Route<
|
||||
string,
|
||||
HttpMethod,
|
||||
Params,
|
||||
any
|
||||
>;
|
||||
const {
|
||||
params = {},
|
||||
options = { tags: ['access:apm'] },
|
||||
...route
|
||||
} = fn(core, __LEGACY) as Route<string, HttpMethod, Params, any>;
|
||||
|
||||
const bodyRt = params.body;
|
||||
const fallbackBodyRt = bodyRt || t.null;
|
||||
|
@ -55,9 +54,7 @@ export function createApi() {
|
|||
server.route(
|
||||
merge(
|
||||
{
|
||||
options: {
|
||||
tags: ['access:apm']
|
||||
},
|
||||
options,
|
||||
method: 'GET'
|
||||
},
|
||||
route,
|
||||
|
|
|
@ -31,6 +31,9 @@ export const agentConfigurationRoute = createRoute(core => ({
|
|||
export const deleteAgentConfigurationRoute = createRoute(() => ({
|
||||
method: 'DELETE',
|
||||
path: '/api/apm/settings/agent-configuration/{configurationId}',
|
||||
options: {
|
||||
tags: ['access:apm', 'access:apm_write']
|
||||
},
|
||||
params: {
|
||||
path: t.type({
|
||||
configurationId: t.string
|
||||
|
@ -108,6 +111,9 @@ export const createAgentConfigurationRoute = createRoute(() => ({
|
|||
params: {
|
||||
body: agentPayloadRt
|
||||
},
|
||||
options: {
|
||||
tags: ['access:apm', 'access:apm_write']
|
||||
},
|
||||
handler: async (req, { body }) => {
|
||||
const setup = await setupRequest(req);
|
||||
return await createOrUpdateConfiguration({ configuration: body, setup });
|
||||
|
@ -117,6 +123,9 @@ export const createAgentConfigurationRoute = createRoute(() => ({
|
|||
export const updateAgentConfigurationRoute = createRoute(() => ({
|
||||
method: 'PUT',
|
||||
path: '/api/apm/settings/agent-configuration/{configurationId}',
|
||||
options: {
|
||||
tags: ['access:apm', 'access:apm_write']
|
||||
},
|
||||
params: {
|
||||
path: t.type({
|
||||
configurationId: t.string
|
||||
|
|
|
@ -34,6 +34,9 @@ export interface Route<
|
|||
path: TPath;
|
||||
method?: TMethod;
|
||||
params?: TParams;
|
||||
options?: {
|
||||
tags: Array<'access:apm' | 'access:apm_write'>;
|
||||
};
|
||||
handler: (
|
||||
req: Request,
|
||||
params: DecodeParams<TParams>,
|
||||
|
|
Loading…
Reference in a new issue