mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[streams] content packs endpoints (#213910)
Creates basic routes to download and upload content packs associated to a stream. Only dashboard assets will be exported and linked to a stream. The endpoints are currently a proxy to the savedObjects importer/exporter interfaces: - download exports the dashboard linked to a stream - upload imports a content pack file and link the dashboards to the targeted stream. Dashboards are imported as-is with no index pattern replacement performed, this will be implemented separately ### Testing - download `curl -XPOST -H "x-elastic-internal-origin: 'kibana'" -H "kbn-xsrf: true" http://elastic:changeme@localhost:5601/pat/api/streams/logs/content/export --output content.json` - upload `curl -XPOST -H "kbn-xsrf: true" http://elastic:changeme@localhost:5601/pat/api/streams/logs.foo/content/import -F 'content=@content.json'` --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e1f094d1f5
commit
e84f6de3f6
10 changed files with 514 additions and 3 deletions
|
@ -48880,6 +48880,108 @@
|
|||
"x-state": "Technical Preview"
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/content/export": {
|
||||
"post": {
|
||||
"description": "Exports the content associated to a stream.",
|
||||
"operationId": "post-streams-name-content-export",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"null"
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Export stream content",
|
||||
"tags": [
|
||||
"streams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/content/import": {
|
||||
"post": {
|
||||
"description": "Links content objects to a stream.",
|
||||
"operationId": "post-streams-name-content-import",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"content": {}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Import content into a stream",
|
||||
"tags": [
|
||||
"streams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/dashboards": {
|
||||
"get": {
|
||||
"description": "Fetches all dashboards linked to a stream that are visible to the current user in the current space.",
|
||||
|
|
|
@ -48471,6 +48471,108 @@
|
|||
"x-state": "Technical Preview"
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/content/export": {
|
||||
"post": {
|
||||
"description": "Exports the content associated to a stream.",
|
||||
"operationId": "post-streams-name-content-export",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": false,
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
"null"
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
{
|
||||
"not": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Export stream content",
|
||||
"tags": [
|
||||
"streams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/content/import": {
|
||||
"post": {
|
||||
"description": "Links content objects to a stream.",
|
||||
"operationId": "post-streams-name-content-import",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "A required header to protect against CSRF attacks",
|
||||
"in": "header",
|
||||
"name": "kbn-xsrf",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"example": "true",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "name",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"content": {}
|
||||
},
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {},
|
||||
"summary": "Import content into a stream",
|
||||
"tags": [
|
||||
"streams"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/streams/{name}/dashboards": {
|
||||
"get": {
|
||||
"description": "Fetches all dashboards linked to a stream that are visible to the current user in the current space.",
|
||||
|
|
|
@ -43447,6 +43447,72 @@ paths:
|
|||
- streams
|
||||
x-state: Technical Preview
|
||||
x-beta: true
|
||||
/api/streams/{name}/content/export:
|
||||
post:
|
||||
description: Exports the content associated to a stream.
|
||||
operationId: post-streams-name-content-export
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties: {}
|
||||
- enum:
|
||||
- 'null'
|
||||
nullable: true
|
||||
- not: {}
|
||||
responses: {}
|
||||
summary: Export stream content
|
||||
tags:
|
||||
- streams
|
||||
x-beta: true
|
||||
/api/streams/{name}/content/import:
|
||||
post:
|
||||
description: Links content objects to a stream.
|
||||
operationId: post-streams-name-content-import
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
content: {}
|
||||
required:
|
||||
- content
|
||||
responses: {}
|
||||
summary: Import content into a stream
|
||||
tags:
|
||||
- streams
|
||||
x-beta: true
|
||||
/api/streams/{name}/dashboards:
|
||||
get:
|
||||
description: Fetches all dashboards linked to a stream that are visible to the current user in the current space.
|
||||
|
|
|
@ -46515,6 +46515,70 @@ paths:
|
|||
tags:
|
||||
- streams
|
||||
x-state: Technical Preview
|
||||
/api/streams/{name}/content/export:
|
||||
post:
|
||||
description: Exports the content associated to a stream.
|
||||
operationId: post-streams-name-content-export
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
anyOf:
|
||||
- additionalProperties: false
|
||||
type: object
|
||||
properties: {}
|
||||
- enum:
|
||||
- 'null'
|
||||
nullable: true
|
||||
- not: {}
|
||||
responses: {}
|
||||
summary: Export stream content
|
||||
tags:
|
||||
- streams
|
||||
/api/streams/{name}/content/import:
|
||||
post:
|
||||
description: Links content objects to a stream.
|
||||
operationId: post-streams-name-content-import
|
||||
parameters:
|
||||
- description: A required header to protect against CSRF attacks
|
||||
in: header
|
||||
name: kbn-xsrf
|
||||
required: true
|
||||
schema:
|
||||
example: 'true'
|
||||
type: string
|
||||
- in: path
|
||||
name: name
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
additionalProperties: false
|
||||
type: object
|
||||
properties:
|
||||
content: {}
|
||||
required:
|
||||
- content
|
||||
responses: {}
|
||||
summary: Import content into a stream
|
||||
tags:
|
||||
- streams
|
||||
/api/streams/{name}/dashboards:
|
||||
get:
|
||||
description: Fetches all dashboards linked to a stream that are visible to the current user in the current space.
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { z } from '@kbn/zod';
|
||||
|
||||
interface ContentPack {
|
||||
content: string;
|
||||
}
|
||||
|
||||
const contentPackSchema: z.Schema<ContentPack> = z.object({
|
||||
content: z.string(),
|
||||
});
|
||||
|
||||
export { contentPackSchema, type ContentPack };
|
|
@ -6,9 +6,9 @@
|
|||
*/
|
||||
|
||||
export * from './ingest';
|
||||
|
||||
export * from './api';
|
||||
export * from './core';
|
||||
export * from './helpers';
|
||||
export * from './group';
|
||||
export * from './record_types';
|
||||
export * from './content';
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* 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 { Readable } from 'stream';
|
||||
import { z } from '@kbn/zod';
|
||||
import {
|
||||
createConcatStream,
|
||||
createListStream,
|
||||
createMapStream,
|
||||
createPromiseFromStreams,
|
||||
} from '@kbn/utils';
|
||||
import { createSavedObjectsStreamFromNdJson } from '@kbn/core-saved-objects-server-internal/src/routes/utils';
|
||||
import { ContentPack, contentPackSchema } from '@kbn/streams-schema';
|
||||
import { createServerRoute } from '../create_server_route';
|
||||
import { StatusError } from '../../lib/streams/errors/status_error';
|
||||
|
||||
const exportContentRoute = createServerRoute({
|
||||
endpoint: 'POST /api/streams/{name}/content/export 2023-10-31',
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: 'Export stream content',
|
||||
description: 'Exports the content associated to a stream.',
|
||||
},
|
||||
params: z.object({
|
||||
path: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
}),
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason:
|
||||
'This API delegates security to the currently logged in user and their Elasticsearch permissions.',
|
||||
},
|
||||
},
|
||||
async handler({ params, request, response, getScopedClients, context }) {
|
||||
const { assetClient, soClient, streamsClient } = await getScopedClients({ request });
|
||||
|
||||
await streamsClient.ensureStream(params.path.name);
|
||||
|
||||
const dashboards = await assetClient
|
||||
.getAssets({ entityId: params.path.name, entityType: 'stream' })
|
||||
.then((assets) => assets.filter(({ assetType }) => assetType === 'dashboard'));
|
||||
if (dashboards.length === 0) {
|
||||
throw new StatusError(`No dashboards are linked to [${params.path.name}] stream`, 400);
|
||||
}
|
||||
|
||||
const exporter = (await context.core).savedObjects.getExporter(soClient);
|
||||
const exportStream = await exporter.exportByObjects({
|
||||
request,
|
||||
objects: dashboards.map((dashboard) => ({ id: dashboard.assetId, type: 'dashboard' })),
|
||||
includeReferencesDeep: true,
|
||||
});
|
||||
|
||||
const savedObjects: string[] = await createPromiseFromStreams([
|
||||
exportStream,
|
||||
createMapStream((savedObject) => {
|
||||
return JSON.stringify(savedObject);
|
||||
}),
|
||||
createConcatStream([]),
|
||||
]);
|
||||
|
||||
return response.ok({
|
||||
body: { content: savedObjects.join('\n') },
|
||||
headers: {
|
||||
'Content-Disposition': `attachment; filename="content.json"`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const importContentRoute = createServerRoute({
|
||||
endpoint: 'POST /api/streams/{name}/content/import 2023-10-31',
|
||||
options: {
|
||||
access: 'public',
|
||||
summary: 'Import content into a stream',
|
||||
description: 'Links content objects to a stream.',
|
||||
body: {
|
||||
accepts: 'multipart/form-data',
|
||||
output: 'stream',
|
||||
},
|
||||
},
|
||||
params: z.object({
|
||||
path: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
body: z.object({
|
||||
content: z.instanceof(Readable),
|
||||
}),
|
||||
}),
|
||||
security: {
|
||||
authz: {
|
||||
enabled: false,
|
||||
reason:
|
||||
'This API delegates security to the currently logged in user and their Elasticsearch permissions.',
|
||||
},
|
||||
},
|
||||
async handler({ params, request, getScopedClients, context }) {
|
||||
const { assetClient, soClient, streamsClient } = await getScopedClients({ request });
|
||||
|
||||
await streamsClient.ensureStream(params.path.name);
|
||||
|
||||
const body: ContentPack = await new Promise((resolve, reject) => {
|
||||
let data = '';
|
||||
params.body.content.on('data', (chunk) => (data += chunk));
|
||||
params.body.content.on('end', () => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
resolve(contentPackSchema.parse(parsed));
|
||||
} catch (err) {
|
||||
reject(new StatusError('Invalid content pack format', 400));
|
||||
}
|
||||
});
|
||||
params.body.content.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
const updatedSavedObjectsStream = await createPromiseFromStreams([
|
||||
await createSavedObjectsStreamFromNdJson(Readable.from(body.content)),
|
||||
createConcatStream([]),
|
||||
]);
|
||||
|
||||
const importer = (await context.core).savedObjects.getImporter(soClient);
|
||||
const { successResults, errors } = await importer.import({
|
||||
readStream: createListStream(updatedSavedObjectsStream),
|
||||
createNewCopies: true,
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
const createdAssets = (successResults ?? [])
|
||||
.filter((savedObject) => savedObject.type === 'dashboard')
|
||||
.map((dashboard) => ({
|
||||
assetType: 'dashboard' as const,
|
||||
assetId: dashboard.destinationId ?? dashboard.id,
|
||||
}));
|
||||
|
||||
if (createdAssets.length > 0) {
|
||||
await assetClient.bulk(
|
||||
{ entityId: params.path.name, entityType: 'stream' },
|
||||
createdAssets.map((asset) => ({
|
||||
index: { asset },
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return { errors, created: createdAssets };
|
||||
},
|
||||
});
|
||||
|
||||
export const contentRoutes = {
|
||||
...exportContentRoute,
|
||||
...importContentRoute,
|
||||
};
|
|
@ -121,8 +121,6 @@ const linkDashboardRoute = createServerRoute({
|
|||
}),
|
||||
handler: async ({ params, request, getScopedClients }): Promise<LinkDashboardResponse> => {
|
||||
const { assetClient, streamsClient } = await getScopedClients({ request });
|
||||
|
||||
await streamsClient.ensureStream(params.path.name);
|
||||
const {
|
||||
path: { dashboardId, name: streamName },
|
||||
} = params;
|
||||
|
|
|
@ -15,6 +15,7 @@ import { internalProcessingRoutes } from './internal/streams/processing/route';
|
|||
import { ingestRoutes } from './streams/ingest/route';
|
||||
import { internalLifecycleRoutes } from './internal/streams/lifecycle/route';
|
||||
import { groupRoutes } from './streams/group/route';
|
||||
import { contentRoutes } from './content/route';
|
||||
import { internalDashboardRoutes } from './internal/dashboards/route';
|
||||
import { internalCrudRoutes } from './internal/streams/crud/route';
|
||||
import { internalManagementRoutes } from './internal/streams/management/route';
|
||||
|
@ -35,6 +36,7 @@ export const streamsRouteRepository = {
|
|||
...managementRoutes,
|
||||
...ingestRoutes,
|
||||
...groupRoutes,
|
||||
...contentRoutes,
|
||||
};
|
||||
|
||||
export type StreamsRouteRepository = typeof streamsRouteRepository;
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
"@kbn/traced-es-client",
|
||||
"@kbn/es-query",
|
||||
"@kbn/core-elasticsearch-client-server-internal",
|
||||
"@kbn/utils",
|
||||
"@kbn/core-saved-objects-server-internal",
|
||||
"@kbn/core-analytics-server"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue