[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:
Dario Gieselaar 2019-11-13 15:42:46 +01:00 committed by GitHub
parent 7bb968c554
commit f317c25852
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 135 additions and 36 deletions

View file

@ -87,7 +87,7 @@ export const apm: LegacyPluginInitializer = kibana => {
catalogue: ['apm'],
privileges: {
all: {
api: ['apm'],
api: ['apm', 'apm_write'],
catalogue: ['apm'],
savedObject: {
all: [],

View file

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

View file

@ -31,6 +31,9 @@ describe('timeseriesFetcher', () => {
client: {
search: clientSpy
} as any,
internalClient: {
search: clientSpy
} as any,
config: {
get: () => 'myIndex' as any,
has: () => true

View file

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

View file

@ -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 () => {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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', () => {

View file

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

View file

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

View file

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