-
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.
[Fleet] Setup fleet server indices in Kibana without packages (#90658) (
- Loading branch information
Showing
16 changed files
with
729 additions
and
74 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
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
156 changes: 156 additions & 0 deletions
156
x-pack/plugins/fleet/server/services/fleet_server/elastic_index.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,156 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { elasticsearchServiceMock } from 'src/core/server/mocks'; | ||
import hash from 'object-hash'; | ||
import { setupFleetServerIndexes } from './elastic_index'; | ||
import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; | ||
import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; | ||
import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; | ||
import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; | ||
import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; | ||
import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; | ||
|
||
const FLEET_INDEXES_MIGRATION_HASH = { | ||
'.fleet-actions': hash(EsFleetActionsIndex), | ||
'.fleet-agents': hash(ESFleetAgentIndex), | ||
'.fleet-enrollment-apy-keys': hash(ESFleetEnrollmentApiKeysIndex), | ||
'.fleet-policies': hash(ESFleetPoliciesIndex), | ||
'.fleet-policies-leader': hash(ESFleetPoliciesLeaderIndex), | ||
'.fleet-servers': hash(ESFleetServersIndex), | ||
}; | ||
|
||
describe('setupFleetServerIndexes ', () => { | ||
it('should create all the indices and aliases if nothings exists', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
await setupFleetServerIndexes(esMock); | ||
|
||
const indexesCreated = esMock.indices.create.mock.calls.map((call) => call[0].index).sort(); | ||
expect(indexesCreated).toEqual([ | ||
'.fleet-actions_1', | ||
'.fleet-agents_1', | ||
'.fleet-enrollment-api-keys_1', | ||
'.fleet-policies-leader_1', | ||
'.fleet-policies_1', | ||
'.fleet-servers_1', | ||
]); | ||
const aliasesCreated = esMock.indices.updateAliases.mock.calls | ||
.map((call) => (call[0].body as any)?.actions[0].add.alias) | ||
.sort(); | ||
|
||
expect(aliasesCreated).toEqual([ | ||
'.fleet-actions', | ||
'.fleet-agents', | ||
'.fleet-enrollment-api-keys', | ||
'.fleet-policies', | ||
'.fleet-policies-leader', | ||
'.fleet-servers', | ||
]); | ||
}); | ||
|
||
it('should not create any indices and create aliases if indices exists but not the aliases', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
// @ts-expect-error | ||
migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
const aliasesCreated = esMock.indices.updateAliases.mock.calls | ||
.map((call) => (call[0].body as any)?.actions[0].add.alias) | ||
.sort(); | ||
|
||
expect(aliasesCreated).toEqual([ | ||
'.fleet-actions', | ||
'.fleet-agents', | ||
'.fleet-enrollment-api-keys', | ||
'.fleet-policies', | ||
'.fleet-policies-leader', | ||
'.fleet-servers', | ||
]); | ||
}); | ||
|
||
it('should put new indices mapping if the mapping has been updated ', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
migrationHash: 'NOT_VALID_HASH', | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
const indexesMappingUpdated = esMock.indices.putMapping.mock.calls | ||
.map((call) => call[0].index) | ||
.sort(); | ||
|
||
expect(indexesMappingUpdated).toEqual([ | ||
'.fleet-actions_1', | ||
'.fleet-agents_1', | ||
'.fleet-enrollment-api-keys_1', | ||
'.fleet-policies-leader_1', | ||
'.fleet-policies_1', | ||
'.fleet-servers_1', | ||
]); | ||
}); | ||
|
||
it('should not create any indices or aliases if indices and aliases already exists', async () => { | ||
const esMock = elasticsearchServiceMock.createInternalClient(); | ||
|
||
// @ts-expect-error | ||
esMock.indices.exists.mockResolvedValue({ body: true }); | ||
// @ts-expect-error | ||
esMock.indices.getMapping.mockImplementation((params: { index: string }) => { | ||
return { | ||
body: { | ||
[params.index]: { | ||
mappings: { | ||
_meta: { | ||
// @ts-expect-error | ||
migrationHash: FLEET_INDEXES_MIGRATION_HASH[params.index.replace(/_1$/, '')], | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}); | ||
// @ts-expect-error | ||
esMock.indices.existsAlias.mockResolvedValue({ body: true }); | ||
|
||
await setupFleetServerIndexes(esMock); | ||
|
||
expect(esMock.indices.create).not.toBeCalled(); | ||
expect(esMock.indices.updateAliases).not.toBeCalled(); | ||
}); | ||
}); |
117 changes: 117 additions & 0 deletions
117
x-pack/plugins/fleet/server/services/fleet_server/elastic_index.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,117 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
import { ElasticsearchClient } from 'kibana/server'; | ||
import hash from 'object-hash'; | ||
|
||
import { FLEET_SERVER_INDICES, FLEET_SERVER_INDICES_VERSION } from '../../../common'; | ||
import { appContextService } from '../app_context'; | ||
import ESFleetAgentIndex from './elasticsearch/fleet_agents.json'; | ||
import ESFleetPoliciesIndex from './elasticsearch/fleet_policies.json'; | ||
import ESFleetPoliciesLeaderIndex from './elasticsearch/fleet_policies_leader.json'; | ||
import ESFleetServersIndex from './elasticsearch/fleet_servers.json'; | ||
import ESFleetEnrollmentApiKeysIndex from './elasticsearch/fleet_enrollment_api_keys.json'; | ||
import EsFleetActionsIndex from './elasticsearch/fleet_actions.json'; | ||
|
||
const FLEET_INDEXES: Array<[typeof FLEET_SERVER_INDICES[number], any]> = [ | ||
['.fleet-actions', EsFleetActionsIndex], | ||
['.fleet-agents', ESFleetAgentIndex], | ||
['.fleet-enrollment-api-keys', ESFleetEnrollmentApiKeysIndex], | ||
['.fleet-policies', ESFleetPoliciesIndex], | ||
['.fleet-policies-leader', ESFleetPoliciesLeaderIndex], | ||
['.fleet-servers', ESFleetServersIndex], | ||
]; | ||
|
||
export async function setupFleetServerIndexes( | ||
esClient = appContextService.getInternalUserESClient() | ||
) { | ||
await Promise.all( | ||
FLEET_INDEXES.map(async ([indexAlias, indexData]) => { | ||
const index = `${indexAlias}_${FLEET_SERVER_INDICES_VERSION}`; | ||
await createOrUpdateIndex(esClient, index, indexData); | ||
await createAliasIfDoNotExists(esClient, indexAlias, index); | ||
}) | ||
); | ||
} | ||
|
||
export async function createAliasIfDoNotExists( | ||
esClient: ElasticsearchClient, | ||
alias: string, | ||
index: string | ||
) { | ||
const { body: exists } = await esClient.indices.existsAlias({ | ||
name: alias, | ||
}); | ||
|
||
if (exists === true) { | ||
return; | ||
} | ||
await esClient.indices.updateAliases({ | ||
body: { | ||
actions: [ | ||
{ | ||
add: { index, alias }, | ||
}, | ||
], | ||
}, | ||
}); | ||
} | ||
|
||
async function createOrUpdateIndex( | ||
esClient: ElasticsearchClient, | ||
indexName: string, | ||
indexData: any | ||
) { | ||
const resExists = await esClient.indices.exists({ | ||
index: indexName, | ||
}); | ||
|
||
// Support non destructive migration only (adding new field) | ||
if (resExists.body === true) { | ||
return updateIndex(esClient, indexName, indexData); | ||
} | ||
|
||
return createIndex(esClient, indexName, indexData); | ||
} | ||
|
||
async function updateIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { | ||
const res = await esClient.indices.getMapping({ | ||
index: indexName, | ||
}); | ||
|
||
const migrationHash = hash(indexData); | ||
if (res.body[indexName].mappings?._meta?.migrationHash !== migrationHash) { | ||
await esClient.indices.putMapping({ | ||
index: indexName, | ||
body: Object.assign({ | ||
...indexData.mappings, | ||
_meta: { ...(indexData.mappings._meta || {}), migrationHash }, | ||
}), | ||
}); | ||
} | ||
} | ||
|
||
async function createIndex(esClient: ElasticsearchClient, indexName: string, indexData: any) { | ||
try { | ||
const migrationHash = hash(indexData); | ||
await esClient.indices.create({ | ||
index: indexName, | ||
body: { | ||
...indexData, | ||
mappings: Object.assign({ | ||
...indexData.mappings, | ||
_meta: { ...(indexData.mappings._meta || {}), migrationHash }, | ||
}), | ||
}, | ||
}); | ||
} catch (err) { | ||
// Swallow already exists errors as concurent Kibana can try to create that indice | ||
if (err?.body?.error?.type !== 'resource_already_exists_exception') { | ||
throw err; | ||
} | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
x-pack/plugins/fleet/server/services/fleet_server/elasticsearch/fleet_actions.json
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,30 @@ | ||
{ | ||
"settings": {}, | ||
"mappings": { | ||
"dynamic": false, | ||
"properties": { | ||
"action_id": { | ||
"type": "keyword" | ||
}, | ||
"agents": { | ||
"type": "keyword" | ||
}, | ||
"data": { | ||
"enabled": false, | ||
"type": "object" | ||
}, | ||
"expiration": { | ||
"type": "date" | ||
}, | ||
"input_type": { | ||
"type": "keyword" | ||
}, | ||
"@timestamp": { | ||
"type": "date" | ||
}, | ||
"type": { | ||
"type": "keyword" | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.