diff --git a/src/core/server/ui_settings/saved_objects/migrations.test.ts b/src/core/server/ui_settings/saved_objects/migrations.test.ts new file mode 100644 index 0000000000000..1620995c932c4 --- /dev/null +++ b/src/core/server/ui_settings/saved_objects/migrations.test.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { migrations } from './migrations'; + +describe('ui_settings 7.9.0 migrations', () => { + const migration = migrations['7.9.0']; + + test('returns doc on empty object', () => { + expect(migration({} as SavedObjectUnsanitizedDoc)).toEqual({ + references: [], + }); + }); + test('properly renames siem attributes to securitySolution', () => { + const doc = { + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + 'siem:defaultAnomalyScore': 59, + 'siem:enableNewsFeed': false, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }; + expect(migration(doc)).toEqual({ + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + 'securitySolution:defaultAnomalyScore': 59, + 'securitySolution:enableNewsFeed': false, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }); + }); +}); diff --git a/src/core/server/ui_settings/saved_objects/migrations.ts b/src/core/server/ui_settings/saved_objects/migrations.ts new file mode 100644 index 0000000000000..750d4e6bc1ea6 --- /dev/null +++ b/src/core/server/ui_settings/saved_objects/migrations.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectUnsanitizedDoc, SavedObjectSanitizedDoc } from 'kibana/server'; + +export const migrations = { + '7.9.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ + ...doc, + ...(doc.attributes && { + attributes: Object.keys(doc.attributes).reduce( + (acc, key) => + key.startsWith('siem:') + ? { + ...acc, + [key.replace('siem', 'securitySolution')]: doc.attributes[key], + } + : { + ...acc, + [key]: doc.attributes[key], + }, + {} + ), + }), + references: doc.references || [], + }), +}; diff --git a/src/core/server/ui_settings/saved_objects/ui_settings.ts b/src/core/server/ui_settings/saved_objects/ui_settings.ts index 0eab40a7b3a5d..26704f46a509c 100644 --- a/src/core/server/ui_settings/saved_objects/ui_settings.ts +++ b/src/core/server/ui_settings/saved_objects/ui_settings.ts @@ -18,6 +18,7 @@ */ import { SavedObjectsType } from '../../saved_objects'; +import { migrations } from './migrations'; export const uiSettingsType: SavedObjectsType = { name: 'config', @@ -46,4 +47,5 @@ export const uiSettingsType: SavedObjectsType = { return `Advanced Settings [${obj.id}]`; }, }, + migrations, }; diff --git a/src/es_archiver/cli.ts b/src/es_archiver/cli.ts index 98888b81d9a31..85e10b31a87ee 100644 --- a/src/es_archiver/cli.ts +++ b/src/es_archiver/cli.ts @@ -67,9 +67,10 @@ cmd .action((name, indices) => execute((archiver, { raw }) => archiver.save(name, indices, { raw }))); cmd + .option('--use-create', 'use create instead of index for loading documents') .command('load ') .description('load the archive in --dir with ') - .action((name) => execute((archiver) => archiver.load(name))); + .action((name) => execute((archiver, { useCreate }) => archiver.load(name, { useCreate }))); cmd .command('unload ') diff --git a/test/api_integration/apis/saved_objects/bulk_get.js b/test/api_integration/apis/saved_objects/bulk_get.js index 23aa175740b67..c802d52913065 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.js +++ b/test/api_integration/apis/saved_objects/bulk_get.js @@ -94,6 +94,7 @@ export default function ({ getService }) { buildNum: 8467, defaultIndex: '91200a00-9efd-11e7-acb3-3dab96693fab', }, + migrationVersion: resp.body.saved_objects[2].migrationVersion, references: [], }, ], diff --git a/x-pack/plugins/ingest_manager/common/constants/routes.ts b/x-pack/plugins/ingest_manager/common/constants/routes.ts index 3309d8497f4c5..1fe29aa54f6f9 100644 --- a/x-pack/plugins/ingest_manager/common/constants/routes.ts +++ b/x-pack/plugins/ingest_manager/common/constants/routes.ts @@ -44,6 +44,7 @@ export const AGENT_CONFIG_API_ROUTES = { INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, CREATE_PATTERN: `${AGENT_CONFIG_API_ROOT}`, UPDATE_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}`, + COPY_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/copy`, DELETE_PATTERN: `${AGENT_CONFIG_API_ROOT}/delete`, FULL_INFO_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/full`, FULL_INFO_DOWNLOAD_PATTERN: `${AGENT_CONFIG_API_ROOT}/{agentConfigId}/download`, diff --git a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json index f0ed3ed9a0364..ea61d97145795 100644 --- a/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json +++ b/x-pack/plugins/ingest_manager/common/openapi/spec_oas3.json @@ -48,13 +48,7 @@ "type": "boolean" } }, - "required": [ - "items", - "total", - "page", - "perPage", - "success" - ] + "required": ["items", "total", "page", "perPage", "success"] }, "examples": { "success": { @@ -66,14 +60,9 @@ "namespace": "default", "description": "Default agent configuration created by Kibana", "status": "active", - "datasources": [ - "8a5679b0-8fbf-11ea-b2ce-01c4a6127154" - ], + "datasources": ["8a5679b0-8fbf-11ea-b2ce-01c4a6127154"], "is_default": true, - "monitoring_enabled": [ - "logs", - "metrics" - ], + "monitoring_enabled": ["logs", "metrics"], "revision": 2, "updated_on": "2020-05-06T17:32:21.905Z", "updated_by": "system", @@ -175,10 +164,7 @@ "type": "boolean" } }, - "required": [ - "item", - "success" - ] + "required": ["item", "success"] }, "examples": { "success": { @@ -213,21 +199,13 @@ "dataset": "system.auth", "vars": { "paths": { - "value": [ - "/var/log/auth.log*", - "/var/log/secure*" - ], + "value": ["/var/log/auth.log*", "/var/log/secure*"], "type": "text" } }, "agent_stream": { - "paths": [ - "/var/log/auth.log*", - "/var/log/secure*" - ], - "exclude_files": [ - ".gz$" - ], + "paths": ["/var/log/auth.log*", "/var/log/secure*"], + "exclude_files": [".gz$"], "multiline": { "pattern": "^\\s", "match": "after" @@ -253,21 +231,13 @@ "dataset": "system.syslog", "vars": { "paths": { - "value": [ - "/var/log/messages*", - "/var/log/syslog*" - ], + "value": ["/var/log/messages*", "/var/log/syslog*"], "type": "text" } }, "agent_stream": { - "paths": [ - "/var/log/messages*", - "/var/log/syslog*" - ], - "exclude_files": [ - ".gz$" - ], + "paths": ["/var/log/messages*", "/var/log/syslog*"], + "exclude_files": [".gz$"], "multiline": { "pattern": "^\\s", "match": "after" @@ -299,16 +269,12 @@ "dataset": "system.core", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "core" - ], + "metricsets": ["core"], "core.metrics": "percentages" } }, @@ -318,16 +284,11 @@ "dataset": "system.cpu", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -343,16 +304,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "cpu" - ], + "metricsets": ["cpu"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -366,9 +323,7 @@ "enabled": true, "dataset": "system.diskio", "agent_stream": { - "metricsets": [ - "diskio" - ] + "metricsets": ["diskio"] } }, { @@ -376,9 +331,7 @@ "enabled": true, "dataset": "system.entropy", "agent_stream": { - "metricsets": [ - "entropy" - ] + "metricsets": ["entropy"] } }, { @@ -396,9 +349,7 @@ } }, "agent_stream": { - "metricsets": [ - "filesystem" - ], + "metricsets": ["filesystem"], "period": "1m", "processors": [ { @@ -424,9 +375,7 @@ } }, "agent_stream": { - "metricsets": [ - "fsstat" - ], + "metricsets": ["fsstat"], "period": "1m", "processors": [ { @@ -443,16 +392,11 @@ "dataset": "system.load", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -468,16 +412,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "load" - ], + "metricsets": ["load"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -492,16 +432,11 @@ "dataset": "system.memory", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -517,16 +452,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "memory" - ], + "metricsets": ["memory"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -541,16 +472,11 @@ "dataset": "system.network", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -566,16 +492,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "network" - ], + "metricsets": ["network"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -589,9 +511,7 @@ "enabled": true, "dataset": "system.network_summary", "agent_stream": { - "metricsets": [ - "network_summary" - ] + "metricsets": ["network_summary"] } }, { @@ -600,16 +520,11 @@ "dataset": "system.process", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -625,16 +540,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "process" - ], + "metricsets": ["process"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -649,16 +560,11 @@ "dataset": "system.process_summary", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -674,16 +580,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "process_summary" - ], + "metricsets": ["process_summary"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -697,9 +599,7 @@ "enabled": true, "dataset": "system.raid", "agent_stream": { - "metricsets": [ - "raid" - ] + "metricsets": ["raid"] } }, { @@ -707,9 +607,7 @@ "enabled": true, "dataset": "system.service", "agent_stream": { - "metricsets": [ - "service" - ] + "metricsets": ["service"] } }, { @@ -717,9 +615,7 @@ "enabled": true, "dataset": "system.socket", "agent_stream": { - "metricsets": [ - "socket" - ] + "metricsets": ["socket"] } }, { @@ -728,16 +624,11 @@ "dataset": "system.socket_summary", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -753,16 +644,12 @@ "type": "integer" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "socket_summary" - ], + "metricsets": ["socket_summary"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -777,16 +664,11 @@ "dataset": "system.uptime", "vars": { "core.metrics": { - "value": [ - "percentages" - ], + "value": ["percentages"], "type": "text" }, "cpu.metrics": { - "value": [ - "percentages", - "normalized_percentages" - ], + "value": ["percentages", "normalized_percentages"], "type": "text" }, "period": { @@ -794,16 +676,12 @@ "type": "text" }, "processes": { - "value": [ - ".*" - ], + "value": [".*"], "type": "text" } }, "agent_stream": { - "metricsets": [ - "uptime" - ], + "metricsets": ["uptime"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -815,9 +693,7 @@ "enabled": true, "dataset": "system.users", "agent_stream": { - "metricsets": [ - "users" - ] + "metricsets": ["users"] } } ] @@ -827,10 +703,7 @@ } ], "is_default": true, - "monitoring_enabled": [ - "logs", - "metrics" - ], + "monitoring_enabled": ["logs", "metrics"], "revision": 2, "updated_on": "2020-05-06T17:32:21.905Z", "updated_by": "system" @@ -865,10 +738,7 @@ "type": "boolean" } }, - "required": [ - "item", - "success" - ] + "required": ["item", "success"] }, "examples": { "example-1": { @@ -916,6 +786,64 @@ ] } }, + "/agent_configs/{agentConfigId}/copy": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "agentConfigId", + "in": "path", + "required": true + } + ], + "post": { + "summary": "Agent config - copy one config", + "operationId": "agent-config-copy", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "item": { + "$ref": "#/components/schemas/AgentConfig" + }, + "success": { + "type": "boolean" + } + }, + "required": ["item", "success"] + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": ["name"] + }, + "examples": {} + } + }, + "description": "" + }, + "description": "Copies one agent config" + } + }, "/agent_configs/delete": { "post": { "summary": "Agent Config - Delete", @@ -937,10 +865,7 @@ "type": "boolean" } }, - "required": [ - "id", - "success" - ] + "required": ["id", "success"] } }, "examples": { @@ -982,9 +907,7 @@ "examples": { "example-1": { "value": { - "agentConfigIds": [ - "df7d2540-5a47-11ea-80da-89b5a66da347" - ] + "agentConfigIds": ["df7d2540-5a47-11ea-80da-89b5a66da347"] } } } @@ -1030,10 +953,7 @@ "type": "boolean" } }, - "required": [ - "items", - "success" - ] + "required": ["items", "success"] }, "examples": { "example-1": { @@ -1056,10 +976,7 @@ "type": "logs", "streams": [ { - "paths": [ - "/var/log/hello1.log", - "/var/log/hello2.log" - ] + "paths": ["/var/log/hello1.log", "/var/log/hello2.log"] } ] } @@ -1098,16 +1015,12 @@ { "enabled": true, "dataset": "nginx.acccess", - "paths": [ - "/var/log/nginx/access.log" - ] + "paths": ["/var/log/nginx/access.log"] }, { "enabled": true, "dataset": "nginx.error", - "paths": [ - "/var/log/nginx/error.log" - ] + "paths": ["/var/log/nginx/error.log"] } ] }, @@ -1141,16 +1054,12 @@ { "enabled": true, "dataset": "nginx.acccess", - "paths": [ - "/var/log/nginx/access.log" - ] + "paths": ["/var/log/nginx/access.log"] }, { "enabled": true, "dataset": "nginx.error", - "paths": [ - "/var/log/nginx/error.log" - ] + "paths": ["/var/log/nginx/error.log"] } ] }, @@ -1184,16 +1093,12 @@ { "enabled": true, "dataset": "nginx.acccess", - "paths": [ - "/var/log/nginx/access.log" - ] + "paths": ["/var/log/nginx/access.log"] }, { "enabled": true, "dataset": "nginx.error", - "paths": [ - "/var/log/nginx/error.log" - ] + "paths": ["/var/log/nginx/error.log"] } ] }, @@ -1224,16 +1129,12 @@ { "streams": [ { - "paths": [ - "/var/log/nginx/access.log" - ], + "paths": ["/var/log/nginx/access.log"], "dataset": "nginx.acccess", "enabled": true }, { - "paths": [ - "/var/log/nginx/error.log" - ], + "paths": ["/var/log/nginx/error.log"], "dataset": "nginx.error", "enabled": true } @@ -1302,16 +1203,12 @@ { "enabled": true, "dataset": "nginx.acccess", - "paths": [ - "/var/log/nginx/access.log" - ] + "paths": ["/var/log/nginx/access.log"] }, { "enabled": true, "dataset": "nginx.error", - "paths": [ - "/var/log/nginx/error.log" - ] + "paths": ["/var/log/nginx/error.log"] } ] }, @@ -1359,10 +1256,7 @@ "type": "boolean" } }, - "required": [ - "item", - "success" - ] + "required": ["item", "success"] } } } @@ -1398,10 +1292,7 @@ "type": "boolean" } }, - "required": [ - "item", - "sucess" - ] + "required": ["item", "sucess"] } } } @@ -1430,9 +1321,7 @@ "type": "boolean" } }, - "required": [ - "isInitialized" - ] + "required": ["isInitialized"] }, "examples": { "success": { @@ -1472,9 +1361,7 @@ "type": "boolean" } }, - "required": [ - "isInitialized" - ] + "required": ["isInitialized"] }, "examples": { "success": { @@ -1500,10 +1387,7 @@ "type": "string" } }, - "required": [ - "admin_username", - "admin_password" - ] + "required": ["admin_username", "admin_password"] } } } @@ -1541,19 +1425,13 @@ "properties": { "status": { "type": "string", - "enum": [ - "installed", - "not_installed" - ] + "enum": ["installed", "not_installed"] }, "savedObject": { "type": "string" } }, - "required": [ - "status", - "savedObject" - ] + "required": ["status", "savedObject"] } ] }, @@ -1567,10 +1445,7 @@ "readme": "/package/coredns-1.0.1/docs/README.md", "description": "CoreDNS logs and metrics integration.\nThe CoreDNS integrations allows to gather logs and metrics from the CoreDNS DNS server to get better insights.\n", "type": "integration", - "categories": [ - "logs", - "metrics" - ], + "categories": ["logs", "metrics"], "requirement": { "kibana": { "versions": ">6.7.0" @@ -1703,16 +1578,12 @@ "ingest_pipeline": "pipeline-entry", "vars": [ { - "default": [ - "/var/log/coredns.log" - ], + "default": ["/var/log/coredns.log"], "name": "paths", "type": "textarea" }, { - "default": [ - "coredns" - ], + "default": ["coredns"], "name": "tags", "type": "text" } @@ -1726,9 +1597,7 @@ "type": "metrics", "vars": [ { - "default": [ - "http://localhost:9153" - ], + "default": ["http://localhost:9153"], "description": "CoreDNS hosts", "name": "hosts", "required": true @@ -1889,20 +1758,14 @@ "type": "string" } }, - "required": [ - "id", - "type" - ] + "required": ["id", "type"] } }, "success": { "type": "boolean" } }, - "required": [ - "response", - "success" - ] + "required": ["response", "success"] } } } @@ -1939,20 +1802,14 @@ "type": "string" } }, - "required": [ - "id", - "type" - ] + "required": ["id", "type"] } }, "success": { "type": "boolean" } }, - "required": [ - "response", - "success" - ] + "required": ["response", "success"] } } } @@ -2668,11 +2525,7 @@ "type": "number" } }, - "required": [ - "id", - "title", - "count" - ] + "required": ["id", "title", "count"] } } } @@ -2690,82 +2543,76 @@ "200": { "description": "OK", "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "type": "object" + "application/json": { + "schema": { + "type": "object", + "properties": { + "list": { + "type": "array", + "items": { + "type": "object" + } + }, + "success": { + "type": "boolean" + }, + "total": { + "type": "number" + }, + "page": { + "type": "number" + }, + "perPage": { + "type": "number" } }, - "success": { - "type": "boolean" - }, - "total": { - "type": "number" - }, - "page": { - "type": "number" - }, - "perPage": { - "type": "number" - } + "required": ["list", "success", "total", "page", "perPage"] }, - "required": [ - "list", - "success", - "total", - "page", - "perPage" - ] - }, - "examples": { - "example-1": { - "value": { - "list": [ - { - "id": "205661d0-5e53-11ea-ad31-4f31c06bd9a4", - "active": true, - "config_id": "ae556400-5e39-11ea-8b49-f9747e466f7b", - "type": "PERMANENT", - "enrolled_at": "2020-03-04T20:02:50.605Z", - "user_provided_metadata": { - "dev_agent_version": "0.0.1", - "region": "us-east" - }, - "local_metadata": { - "host": "localhost", - "ip": "127.0.0.1", - "system": "Darwin 18.7.0", - "memory": 34359738368 - }, - "actions": [ - { - "data": "{\"config\":{\"id\":\"ae556400-5e39-11ea-8b49-f9747e466f7b\",\"outputs\":{\"default\":{\"type\":\"elasticsearch\",\"hosts\":[\"http://localhost:9200\"],\"api_key\":\"\",\"api_token\":\"6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw\"}},\"datasources\":[]}}", - "created_at": "2020-03-04T20:02:56.149Z", - "id": "6a95c00a-d76d-4931-97c3-0bf935272d7d", - "type": "CONFIG_CHANGE" - } - ], - "access_api_key_id": "6Mkkp3ABz7e_XRqrzLNJ", - "default_api_key": "6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw", - "current_error_events": [], - "last_checkin": "2020-03-04T20:03:05.700Z", - "status": "online" - } - ], - "success": true, - "total": 1, - "page": 1, - "perPage": 20 + "examples": { + "example-1": { + "value": { + "list": [ + { + "id": "205661d0-5e53-11ea-ad31-4f31c06bd9a4", + "active": true, + "config_id": "ae556400-5e39-11ea-8b49-f9747e466f7b", + "type": "PERMANENT", + "enrolled_at": "2020-03-04T20:02:50.605Z", + "user_provided_metadata": { + "dev_agent_version": "0.0.1", + "region": "us-east" + }, + "local_metadata": { + "host": "localhost", + "ip": "127.0.0.1", + "system": "Darwin 18.7.0", + "memory": 34359738368 + }, + "actions": [ + { + "data": "{\"config\":{\"id\":\"ae556400-5e39-11ea-8b49-f9747e466f7b\",\"outputs\":{\"default\":{\"type\":\"elasticsearch\",\"hosts\":[\"http://localhost:9200\"],\"api_key\":\"\",\"api_token\":\"6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw\"}},\"datasources\":[]}}", + "created_at": "2020-03-04T20:02:56.149Z", + "id": "6a95c00a-d76d-4931-97c3-0bf935272d7d", + "type": "CONFIG_CHANGE" + } + ], + "access_api_key_id": "6Mkkp3ABz7e_XRqrzLNJ", + "default_api_key": "6ckkp3ABz7e_XRqr3LM8:gQuDfUNSRgmY0iziYqP9Hw", + "current_error_events": [], + "last_checkin": "2020-03-04T20:03:05.700Z", + "status": "online" + } + ], + "success": true, + "total": 1, + "page": 1, + "perPage": 20 + } } } } } } - } }, "operationId": "get-fleet-agents", "security": [ @@ -2804,10 +2651,7 @@ "type": "string" } }, - "required": [ - "item", - "success" - ] + "required": ["item", "success"] } } } @@ -2880,9 +2724,7 @@ "properties": { "action": { "type": "string", - "enum": [ - "checkin" - ] + "enum": ["checkin"] }, "success": { "type": "string" @@ -2909,13 +2751,7 @@ "type": "string" } }, - "required": [ - "agent_id", - "data", - "id", - "created_at", - "type" - ] + "required": ["agent_id", "data", "id", "created_at", "type"] } } } @@ -2935,9 +2771,7 @@ "outputs": { "default": { "type": "elasticsearch", - "hosts": [ - "http://localhost:9200" - ], + "hosts": ["http://localhost:9200"], "api_key": "Z-XkgHIBvwtjzIKtSCTh:AejRqdKpQx6z-6dqSI1LHg" } }, @@ -2957,13 +2791,8 @@ "id": "logs-system.auth", "enabled": true, "dataset": "system.auth", - "paths": [ - "/var/log/auth.log*", - "/var/log/secure*" - ], - "exclude_files": [ - ".gz$" - ], + "paths": ["/var/log/auth.log*", "/var/log/secure*"], + "exclude_files": [".gz$"], "multiline": { "pattern": "^\\s", "match": "after" @@ -2986,13 +2815,8 @@ "id": "logs-system.syslog", "enabled": true, "dataset": "system.syslog", - "paths": [ - "/var/log/messages*", - "/var/log/syslog*" - ], - "exclude_files": [ - ".gz$" - ], + "paths": ["/var/log/messages*", "/var/log/syslog*"], + "exclude_files": [".gz$"], "multiline": { "pattern": "^\\s", "match": "after" @@ -3021,18 +2845,14 @@ "id": "system/metrics-system.core", "enabled": true, "dataset": "system.core", - "metricsets": [ - "core" - ], + "metricsets": ["core"], "core.metrics": "percentages" }, { "id": "system/metrics-system.cpu", "enabled": true, "dataset": "system.cpu", - "metricsets": [ - "cpu" - ], + "metricsets": ["cpu"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3044,25 +2864,19 @@ "id": "system/metrics-system.diskio", "enabled": true, "dataset": "system.diskio", - "metricsets": [ - "diskio" - ] + "metricsets": ["diskio"] }, { "id": "system/metrics-system.entropy", "enabled": true, "dataset": "system.entropy", - "metricsets": [ - "entropy" - ] + "metricsets": ["entropy"] }, { "id": "system/metrics-system.filesystem", "enabled": true, "dataset": "system.filesystem", - "metricsets": [ - "filesystem" - ], + "metricsets": ["filesystem"], "period": "1m", "processors": [ { @@ -3076,9 +2890,7 @@ "id": "system/metrics-system.fsstat", "enabled": true, "dataset": "system.fsstat", - "metricsets": [ - "fsstat" - ], + "metricsets": ["fsstat"], "period": "1m", "processors": [ { @@ -3092,9 +2904,7 @@ "id": "system/metrics-system.load", "enabled": true, "dataset": "system.load", - "metricsets": [ - "load" - ], + "metricsets": ["load"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3106,9 +2916,7 @@ "id": "system/metrics-system.memory", "enabled": true, "dataset": "system.memory", - "metricsets": [ - "memory" - ], + "metricsets": ["memory"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3120,9 +2928,7 @@ "id": "system/metrics-system.network", "enabled": true, "dataset": "system.network", - "metricsets": [ - "network" - ], + "metricsets": ["network"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3134,17 +2940,13 @@ "id": "system/metrics-system.network_summary", "enabled": true, "dataset": "system.network_summary", - "metricsets": [ - "network_summary" - ] + "metricsets": ["network_summary"] }, { "id": "system/metrics-system.process", "enabled": true, "dataset": "system.process", - "metricsets": [ - "process" - ], + "metricsets": ["process"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3156,9 +2958,7 @@ "id": "system/metrics-system.process_summary", "enabled": true, "dataset": "system.process_summary", - "metricsets": [ - "process_summary" - ], + "metricsets": ["process_summary"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3170,33 +2970,25 @@ "id": "system/metrics-system.raid", "enabled": true, "dataset": "system.raid", - "metricsets": [ - "raid" - ] + "metricsets": ["raid"] }, { "id": "system/metrics-system.service", "enabled": true, "dataset": "system.service", - "metricsets": [ - "service" - ] + "metricsets": ["service"] }, { "id": "system/metrics-system.socket", "enabled": true, "dataset": "system.socket", - "metricsets": [ - "socket" - ] + "metricsets": ["socket"] }, { "id": "system/metrics-system.socket_summary", "enabled": true, "dataset": "system.socket_summary", - "metricsets": [ - "socket_summary" - ], + "metricsets": ["socket_summary"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3208,9 +3000,7 @@ "id": "system/metrics-system.uptime", "enabled": true, "dataset": "system.uptime", - "metricsets": [ - "uptime" - ], + "metricsets": ["uptime"], "core.metrics": "percentages", "cpu.metrics": "percentages,normalized_percentages", "period": "10s", @@ -3220,9 +3010,7 @@ "id": "system/metrics-system.users", "enabled": true, "dataset": "system.users", - "metricsets": [ - "users" - ] + "metricsets": ["users"] } ] } @@ -3373,15 +3161,10 @@ }, "action": { "type": "string", - "enum": [ - "acks" - ] + "enum": ["acks"] } }, - "required": [ - "success", - "action" - ] + "required": ["success", "action"] }, "examples": { "success": { @@ -3498,21 +3281,14 @@ "properties": { "type": { "type": "string", - "enum": [ - "PERMANENT", - "EPHEMERAL", - "TEMPORARY" - ] + "enum": ["PERMANENT", "EPHEMERAL", "TEMPORARY"] }, "shared_id": { "type": "string" }, "metadata": { "type": "object", - "required": [ - "local", - "user_provided" - ], + "required": ["local", "user_provided"], "properties": { "local": { "$ref": "#/components/schemas/AgentMetadata" @@ -3523,10 +3299,7 @@ } } }, - "required": [ - "type", - "metadata" - ] + "required": ["type", "metadata"] }, "examples": { "good": { @@ -3723,10 +3496,7 @@ }, "status": { "type": "string", - "enum": [ - "active", - "inactive" - ] + "enum": ["active", "inactive"] }, "datasources": { "oneOf": [ @@ -3757,10 +3527,7 @@ "type": "number" } }, - "required": [ - "id", - "status" - ] + "required": ["id", "status"] } ] }, @@ -3781,10 +3548,7 @@ "items": {} } }, - "required": [ - "id", - "revision" - ] + "required": ["id", "revision"] }, { "$ref": "#/components/schemas/NewDatasource" @@ -3829,16 +3593,12 @@ { "enabled": true, "dataset": "nginx.acccess", - "paths": [ - "/var/log/nginx/access.log" - ] + "paths": ["/var/log/nginx/access.log"] }, { "enabled": true, "dataset": "nginx.error", - "paths": [ - "/var/log/nginx/error.log" - ] + "paths": ["/var/log/nginx/error.log"] } ] }, @@ -3874,11 +3634,7 @@ "type": "string" } }, - "required": [ - "name", - "version", - "title" - ] + "required": ["name", "version", "title"] }, "namespace": { "type": "string" @@ -3914,11 +3670,7 @@ "type": "object" } }, - "required": [ - "type", - "enabled", - "streams" - ] + "required": ["type", "enabled", "streams"] } }, "config_id": { @@ -3931,12 +3683,7 @@ "type": "string" } }, - "required": [ - "output_id", - "inputs", - "config_id", - "name" - ] + "required": ["output_id", "inputs", "config_id", "name"] }, "PackageInfo": { "title": "PackageInfo", @@ -4007,9 +3754,7 @@ "type": "string" } }, - "required": [ - "src" - ] + "required": ["src"] } }, "icons": { @@ -4059,10 +3804,7 @@ "type": "string" } }, - "required": [ - "name", - "default" - ] + "required": ["name", "default"] } }, "type": { @@ -4072,14 +3814,7 @@ "type": "string" } }, - "required": [ - "title", - "name", - "release", - "ingeset_pipeline", - "type", - "package" - ] + "required": ["title", "name", "release", "ingeset_pipeline", "type", "package"] } }, "download": { @@ -4153,13 +3888,7 @@ "AgentStatus": { "type": "string", "title": "AgentStatus", - "enum": [ - "offline", - "error", - "online", - "inactive", - "warning" - ] + "enum": ["offline", "error", "online", "inactive", "warning"] }, "Agent": { "title": "Agent", @@ -4187,10 +3916,7 @@ "type": "string" }, "config_revision": { - "type": [ - "number", - "null" - ] + "type": ["number", "null"] }, "config_newest_revision": { "type": "number" @@ -4223,23 +3949,12 @@ "type": "string" } }, - "required": [ - "type", - "active", - "enrolled_at", - "id", - "current_error_events", - "status" - ] + "required": ["type", "active", "enrolled_at", "id", "current_error_events", "status"] }, "AgentType": { "type": "string", "title": "AgentType", - "enum": [ - "PERMANENT", - "EPHEMERAL", - "TEMPORARY" - ] + "enum": ["PERMANENT", "EPHEMERAL", "TEMPORARY"] }, "AgentMetadata": { "title": "AgentMetadata", @@ -4251,12 +3966,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "STATE", - "ERROR", - "ACTION_RESULT", - "ACTION" - ] + "enum": ["STATE", "ERROR", "ACTION_RESULT", "ACTION"] }, "subtype": { "type": "string", @@ -4295,13 +4005,7 @@ "type": "string" } }, - "required": [ - "type", - "subtype", - "timestamp", - "message", - "agent_id" - ] + "required": ["type", "subtype", "timestamp", "message", "agent_id"] }, "AgentEvent": { "title": "AgentEvent", @@ -4313,9 +4017,7 @@ "type": "string" } }, - "required": [ - "id" - ] + "required": ["id"] }, { "$ref": "#/components/schemas/NewAgentEvent" diff --git a/x-pack/plugins/ingest_manager/common/services/routes.ts b/x-pack/plugins/ingest_manager/common/services/routes.ts index 3fc990ea9d70c..8136abe1a42d4 100644 --- a/x-pack/plugins/ingest_manager/common/services/routes.ts +++ b/x-pack/plugins/ingest_manager/common/services/routes.ts @@ -83,6 +83,10 @@ export const agentConfigRouteService = { return AGENT_CONFIG_API_ROUTES.UPDATE_PATTERN.replace('{agentConfigId}', agentConfigId); }, + getCopyPath: (agentConfigId: string) => { + return AGENT_CONFIG_API_ROUTES.COPY_PATTERN.replace('{agentConfigId}', agentConfigId); + }, + getDeletePath: () => { return AGENT_CONFIG_API_ROUTES.DELETE_PATTERN; }, diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts index 82d7fa51b2082..86020cb5235ae 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/agent_config.ts @@ -49,6 +49,15 @@ export interface UpdateAgentConfigResponse { success: boolean; } +export interface CopyAgentConfigRequest { + body: Pick; +} + +export interface CopyAgentConfigResponse { + item: AgentConfig; + success: boolean; +} + export interface DeleteAgentConfigRequest { body: { agentConfigId: string; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts index 45ca6047b0d96..c81303de3d7c3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/agent_config.ts @@ -19,6 +19,8 @@ import { CreateAgentConfigResponse, UpdateAgentConfigRequest, UpdateAgentConfigResponse, + CopyAgentConfigRequest, + CopyAgentConfigResponse, DeleteAgentConfigRequest, DeleteAgentConfigResponse, } from '../../types'; @@ -76,6 +78,17 @@ export const sendUpdateAgentConfig = ( }); }; +export const sendCopyAgentConfig = ( + agentConfigId: string, + body: CopyAgentConfigRequest['body'] +) => { + return sendRequest({ + path: agentConfigRouteService.getCopyPath(agentConfigId), + method: 'post', + body: JSON.stringify(body), + }); +}; + export const sendDeleteAgentConfig = (body: DeleteAgentConfigRequest['body']) => { return sendRequest({ path: agentConfigRouteService.getDeletePath(), diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx index e126a8b871d45..39fe090e5008c 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/actions_menu.tsx @@ -11,69 +11,92 @@ import { useCapabilities } from '../../../hooks'; import { ContextMenuActions } from '../../../components'; import { AgentEnrollmentFlyout } from '../../fleet/components'; import { ConfigYamlFlyout } from './config_yaml_flyout'; +import { AgentConfigCopyProvider } from './config_copy_provider'; -export const AgentConfigActionMenu = memo<{ config: AgentConfig; fullButton?: boolean }>( - ({ config, fullButton = false }) => { - const hasWriteCapabilities = useCapabilities().write; - const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); - const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); - return ( - <> - {isYamlFlyoutOpen ? ( - - setIsYamlFlyoutOpen(false)} /> - - ) : null} - {isEnrollmentFlyoutOpen && ( - - setIsEnrollmentFlyoutOpen(false)} +export const AgentConfigActionMenu = memo<{ + config: AgentConfig; + onCopySuccess?: (newAgentConfig: AgentConfig) => void; + fullButton?: boolean; +}>(({ config, onCopySuccess, fullButton = false }) => { + const hasWriteCapabilities = useCapabilities().write; + const [isYamlFlyoutOpen, setIsYamlFlyoutOpen] = useState(false); + const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState(false); + + return ( + + {(copyAgentConfigPrompt) => { + return ( + <> + {isYamlFlyoutOpen ? ( + + setIsYamlFlyoutOpen(false)} /> + + ) : null} + {isEnrollmentFlyoutOpen && ( + + setIsEnrollmentFlyoutOpen(false)} + /> + + )} + + ), + } + : undefined + } + items={[ + setIsEnrollmentFlyoutOpen(true)} + key="enrollAgents" + > + + , + setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} + key="viewConfig" + > + + , + { + copyAgentConfigPrompt(config, onCopySuccess); + }} + key="copyConfig" + > + + , + ]} /> - - )} - - ), - } - : undefined - } - items={[ - setIsEnrollmentFlyoutOpen(true)} - key="enrollAgents" - > - - , - setIsYamlFlyoutOpen(!isYamlFlyoutOpen)} - key="viewConfig" - > - - , - ]} - /> - - ); - } -); + + ); + }} + + ); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_copy_provider.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_copy_provider.tsx new file mode 100644 index 0000000000000..9776304797fd4 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/components/config_copy_provider.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useRef, useState } from 'react'; +import { EuiConfirmModal, EuiOverlayMask, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AgentConfig } from '../../../types'; +import { sendCopyAgentConfig, useCore } from '../../../hooks'; + +interface Props { + children: (copyAgentConfig: CopyAgentConfig) => React.ReactElement; +} + +export type CopyAgentConfig = (agentConfig: AgentConfig, onSuccess?: OnSuccessCallback) => void; + +type OnSuccessCallback = (newAgentConfig: AgentConfig) => void; + +export const AgentConfigCopyProvider: React.FunctionComponent = ({ children }) => { + const { notifications } = useCore(); + const [agentConfig, setAgentConfig] = useState(); + const [newAgentConfig, setNewAgentConfig] = useState>(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const onSuccessCallback = useRef(null); + + const copyAgentConfigPrompt: CopyAgentConfig = ( + agentConfigToCopy, + onSuccess = () => undefined + ) => { + if (!agentConfigToCopy) { + throw new Error('No agent config specified to copy'); + } + setIsModalOpen(true); + setAgentConfig(agentConfigToCopy); + setNewAgentConfig({ + name: i18n.translate( + 'xpack.ingestManager.copyAgentConfig.confirmModal.defaultNewConfigName', + { + defaultMessage: '{name} (copy)', + values: { name: agentConfigToCopy.name }, + } + ), + description: agentConfigToCopy.description, + }); + onSuccessCallback.current = onSuccess; + }; + + const closeModal = () => { + setAgentConfig(undefined); + setNewAgentConfig(undefined); + setIsLoading(false); + setIsModalOpen(false); + }; + + const copyAgentConfig = async () => { + setIsLoading(true); + try { + const { data } = await sendCopyAgentConfig(agentConfig!.id, newAgentConfig!); + + if (data?.success) { + notifications.toasts.addSuccess( + i18n.translate('xpack.ingestManager.copyAgentConfig.successNotificationTitle', { + defaultMessage: 'Agent config copied', + }) + ); + if (onSuccessCallback.current) { + onSuccessCallback.current(data.item); + } + } + + if (!data?.success) { + notifications.toasts.addDanger( + i18n.translate('xpack.ingestManager.copyAgentConfig.failureNotificationTitle', { + defaultMessage: "Error copying agent config '{id}'", + values: { id: agentConfig!.id }, + }) + ); + } + } catch (e) { + notifications.toasts.addDanger( + i18n.translate('xpack.ingestManager.copyAgentConfig.fatalErrorNotificationTitle', { + defaultMessage: 'Error copying agent config', + }) + ); + } + closeModal(); + }; + + const renderModal = () => { + if (!isModalOpen || !agentConfig || !newAgentConfig) { + return null; + } + + return ( + + + } + onCancel={closeModal} + onConfirm={copyAgentConfig} + cancelButtonText={ + + } + confirmButtonText={ + + } + confirmButtonDisabled={isLoading || !newAgentConfig.name.trim()} + > +

+ +

+ + } + fullWidth + > + setNewAgentConfig({ ...newAgentConfig, name: e.target.value })} + /> + + + } + fullWidth + > + + setNewAgentConfig({ ...newAgentConfig, description: e.target.value }) + } + /> + +
+
+ ); + }; + + return ( + + {children(copyAgentConfigPrompt)} + {renderModal()} + + ); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx index c74958078ca94..410c0fcb2d140 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/details_page/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { useMemo, useState } from 'react'; -import { Redirect, useRouteMatch, Switch, Route } from 'react-router-dom'; +import { Redirect, useRouteMatch, Switch, Route, useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, FormattedDate } from '@kbn/i18n/react'; import { @@ -40,7 +40,8 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { const { params: { configId, tabId = '' }, } = useRouteMatch<{ configId: string; tabId?: string }>(); - const { getHref } = useLink(); + const history = useHistory(); + const { getHref, getPath } = useLink(); const agentConfigRequest = useGetOneAgentConfig(configId); const agentConfig = agentConfigRequest.data ? agentConfigRequest.data.item : null; const { isLoading, error, sendRequest: refreshAgentConfig } = agentConfigRequest; @@ -148,7 +149,13 @@ export const AgentConfigDetailsPage: React.FunctionComponent = () => { { isDivider: true }, { content: agentConfig && ( - + { + history.push(getPath('configuration_details', { configId: newAgentConfig.id })); + }} + /> ), }, ].map((item, index) => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx index 487c1c070bb3b..8b1ff0988d443 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/list_page/index.tsx @@ -189,7 +189,9 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { }), actions: [ { - render: (config: AgentConfig) => , + render: (config: AgentConfig) => ( + sendRequest()} /> + ), }, ], }, @@ -201,7 +203,7 @@ export const AgentConfigListPage: React.FunctionComponent<{}> = () => { } return cols; - }, [getHref, isFleetEnabled]); + }, [getHref, isFleetEnabled, sendRequest]); const createAgentConfigButton = useMemo( () => ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts index 05a97fd2e2a3c..412bf412d1ef5 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts @@ -31,6 +31,8 @@ export { CreateAgentConfigResponse, UpdateAgentConfigRequest, UpdateAgentConfigResponse, + CopyAgentConfigRequest, + CopyAgentConfigResponse, DeleteAgentConfigRequest, DeleteAgentConfigResponse, // API schemas - Datasource diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts index afc146cf90447..d01b361bd6ca4 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/handlers.ts @@ -15,6 +15,7 @@ import { GetOneAgentConfigRequestSchema, CreateAgentConfigRequestSchema, UpdateAgentConfigRequestSchema, + CopyAgentConfigRequestSchema, DeleteAgentConfigRequestSchema, GetFullAgentConfigRequestSchema, AgentConfig, @@ -27,6 +28,7 @@ import { GetOneAgentConfigResponse, CreateAgentConfigResponse, UpdateAgentConfigResponse, + CopyAgentConfigResponse, DeleteAgentConfigResponse, GetFullAgentConfigResponse, } from '../../../common'; @@ -177,6 +179,34 @@ export const updateAgentConfigHandler: RequestHandler< } }; +export const copyAgentConfigHandler: RequestHandler< + TypeOf, + unknown, + TypeOf +> = async (context, request, response) => { + const soClient = context.core.savedObjects.client; + const user = await appContextService.getSecurity()?.authc.getCurrentUser(request); + try { + const agentConfig = await agentConfigService.copy( + soClient, + request.params.agentConfigId, + request.body, + { + user: user || undefined, + } + ); + const body: CopyAgentConfigResponse = { item: agentConfig, success: true }; + return response.ok({ + body, + }); + } catch (e) { + return response.customError({ + statusCode: 500, + body: { message: e.message }, + }); + } +}; + export const deleteAgentConfigsHandler: RequestHandler< unknown, unknown, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts index 4f6cfb436b93b..95c7c13377366 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent_config/index.ts @@ -10,6 +10,7 @@ import { GetOneAgentConfigRequestSchema, CreateAgentConfigRequestSchema, UpdateAgentConfigRequestSchema, + CopyAgentConfigRequestSchema, DeleteAgentConfigRequestSchema, GetFullAgentConfigRequestSchema, } from '../../types'; @@ -18,6 +19,7 @@ import { getOneAgentConfigHandler, createAgentConfigHandler, updateAgentConfigHandler, + copyAgentConfigHandler, deleteAgentConfigsHandler, getFullAgentConfig, downloadFullAgentConfig, @@ -64,6 +66,16 @@ export const registerRoutes = (router: IRouter) => { updateAgentConfigHandler ); + // Copy + router.post( + { + path: AGENT_CONFIG_API_ROUTES.COPY_PATTERN, + validate: CopyAgentConfigRequestSchema, + options: { tags: [`access:${PLUGIN_ID}-all`] }, + }, + copyAgentConfigHandler + ); + // Delete router.post( { diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index eade78e3b5423..4a877ef7de13b 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -179,6 +179,48 @@ class AgentConfigService { return this._update(soClient, id, agentConfig, options?.user); } + public async copy( + soClient: SavedObjectsClientContract, + id: string, + newAgentConfigProps: Pick, + options?: { user?: AuthenticatedUser } + ): Promise { + // Copy base config + const baseAgentConfig = await this.get(soClient, id, true); + if (!baseAgentConfig) { + throw new Error('Agent config not found'); + } + const { namespace, monitoring_enabled } = baseAgentConfig; + const newAgentConfig = await this.create( + soClient, + { + namespace, + monitoring_enabled, + ...newAgentConfigProps, + }, + options + ); + + // Copy all datasources + if (baseAgentConfig.datasources.length) { + const newDatasources = (baseAgentConfig.datasources as Datasource[]).map( + (datasource: Datasource) => { + const { id: datasourceId, ...newDatasource } = datasource; + return newDatasource; + } + ); + await datasourceService.bulkCreate(soClient, newDatasources, newAgentConfig.id, options); + } + + // Get updated config + const updatedAgentConfig = await this.get(soClient, newAgentConfig.id, true); + if (!updatedAgentConfig) { + throw new Error('Copied agent config not found'); + } + + return updatedAgentConfig; + } + public async bumpRevision( soClient: SavedObjectsClientContract, id: string, @@ -203,7 +245,6 @@ class AgentConfigService { soClient, id, { - ...oldAgentConfig, datasources: uniq( [...((oldAgentConfig.datasources || []) as string[])].concat(datasourceIds) ), diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.ts b/x-pack/plugins/ingest_manager/server/services/datasource.ts index c559dac0c0dcd..3ad94ea8191d4 100644 --- a/x-pack/plugins/ingest_manager/server/services/datasource.ts +++ b/x-pack/plugins/ingest_manager/server/services/datasource.ts @@ -56,6 +56,44 @@ class DatasourceService { }; } + public async bulkCreate( + soClient: SavedObjectsClientContract, + datasources: NewDatasource[], + configId: string, + options?: { user?: AuthenticatedUser } + ): Promise { + const isoDate = new Date().toISOString(); + const { saved_objects: newSos } = await soClient.bulkCreate>( + datasources.map((datasource) => ({ + type: SAVED_OBJECT_TYPE, + attributes: { + ...datasource, + config_id: configId, + revision: 1, + created_at: isoDate, + created_by: options?.user?.username ?? 'system', + updated_at: isoDate, + updated_by: options?.user?.username ?? 'system', + }, + })) + ); + + // Assign it to the given agent config + await agentConfigService.assignDatasources( + soClient, + configId, + newSos.map((newSo) => newSo.id), + { + user: options?.user, + } + ); + + return newSos.map((newSo) => ({ + id: newSo.id, + ...newSo.attributes, + })); + } + public async get(soClient: SavedObjectsClientContract, id: string): Promise { const datasourceSO = await soClient.get(SAVED_OBJECT_TYPE, id); if (!datasourceSO) { diff --git a/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts index 2b0bce99c5bda..ee91813a48e2f 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent_config.ts @@ -8,7 +8,7 @@ import { DatasourceSchema } from './datasource'; import { AgentConfigStatus } from '../../../common'; const AgentConfigBaseSchema = { - name: schema.string(), + name: schema.string({ minLength: 1 }), namespace: schema.string({ minLength: 1 }), description: schema.maybe(schema.string()), monitoring_enabled: schema.maybe( diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts index 123a413bb8442..306aefb0d51ff 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/agent_config.ts @@ -29,6 +29,14 @@ export const UpdateAgentConfigRequestSchema = { body: NewAgentConfigSchema, }; +export const CopyAgentConfigRequestSchema = { + ...GetOneAgentConfigRequestSchema, + body: schema.object({ + name: schema.string({ minLength: 1 }), + description: schema.maybe(schema.string()), + }), +}; + export const DeleteAgentConfigRequestSchema = { body: schema.object({ agentConfigId: schema.string(), diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index b463322ea55db..3508d69ee2212 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -48,6 +48,8 @@ export class EditJobFlyoutUI extends Component { jobDescription: '', jobGroups: [], jobModelMemoryLimit: '', + jobModelSnapshotRetentionDays: 10, + jobDailyModelSnapshotRetentionAfterDays: 10, jobDetectors: [], jobDetectorDescriptions: [], jobCustomUrls: [], @@ -96,6 +98,8 @@ export class EditJobFlyoutUI extends Component { 'jobDescription', 'jobGroups', 'jobModelMemoryLimit', + 'jobModelSnapshotRetentionDays', + 'jobDailyModelSnapshotRetentionAfterDays', 'jobCustomUrls', 'jobDetectors', 'jobDetectorDescriptions', @@ -128,6 +132,15 @@ export class EditJobFlyoutUI extends Component { job.analysis_limits && job.analysis_limits.model_memory_limit ? job.analysis_limits.model_memory_limit : ''; + + const modelSnapshotRetentionDays = + job.model_snapshot_retention_days !== undefined ? job.model_snapshot_retention_days : 10; + + const dailyModelSnapshotRetentionAfterDays = + job.daily_model_snapshot_retention_after_days !== undefined + ? job.daily_model_snapshot_retention_after_days + : modelSnapshotRetentionDays; + const detectors = job.analysis_config && job.analysis_config.detectors ? [...job.analysis_config.detectors] @@ -146,6 +159,8 @@ export class EditJobFlyoutUI extends Component { jobDescription: job.description, jobGroups: job.groups !== undefined ? job.groups : [], jobModelMemoryLimit: mml, + jobModelSnapshotRetentionDays: modelSnapshotRetentionDays, + jobDailyModelSnapshotRetentionAfterDays: dailyModelSnapshotRetentionAfterDays, jobDetectors: detectors, jobDetectorDescriptions: detectors.map((d) => d.detector_description), jobBucketSpan: bucketSpan, @@ -229,6 +244,8 @@ export class EditJobFlyoutUI extends Component { description: this.state.jobDescription, groups: this.state.jobGroups, mml: this.state.jobModelMemoryLimit, + modelSnapshotRetentionDays: this.state.jobModelSnapshotRetentionDays, + dailyModelSnapshotRetentionAfterDays: this.state.jobDailyModelSnapshotRetentionAfterDays, detectorDescriptions: this.state.jobDetectorDescriptions, datafeedQuery: collapseLiteralStrings(this.state.datafeedQuery), datafeedQueryDelay: this.state.datafeedQueryDelay, @@ -275,6 +292,8 @@ export class EditJobFlyoutUI extends Component { jobDescription, jobGroups, jobModelMemoryLimit, + jobModelSnapshotRetentionDays, + jobDailyModelSnapshotRetentionAfterDays, jobDetectors, jobDetectorDescriptions, jobBucketSpan, @@ -302,6 +321,8 @@ export class EditJobFlyoutUI extends Component { jobDescription={jobDescription} jobGroups={jobGroups} jobModelMemoryLimit={jobModelMemoryLimit} + jobModelSnapshotRetentionDays={jobModelSnapshotRetentionDays} + jobDailyModelSnapshotRetentionAfterDays={jobDailyModelSnapshotRetentionAfterDays} setJobDetails={this.setJobDetails} jobGroupsValidationError={jobGroupsValidationError} jobModelMemoryLimitValidationError={jobModelMemoryLimitValidationError} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js index fcd2c09f72767..5030c48a4e367 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/edit_utils.js @@ -16,6 +16,8 @@ export function saveJob(job, newJobData, finish) { ...extractDescription(job, newJobData), ...extractGroups(job, newJobData), ...extractMML(job, newJobData), + ...extractModelSnapshotRetentionDays(job, newJobData), + ...extractDailyModelSnapshotRetentionAfterDays(job, newJobData), ...extractDetectorDescriptions(job, newJobData), ...extractCustomSettings(job, newJobData), }; @@ -175,6 +177,22 @@ function extractMML(job, newJobData) { return mmlData; } +function extractModelSnapshotRetentionDays(job, newJobData) { + const modelSnapshotRetentionDays = newJobData.modelSnapshotRetentionDays; + if (modelSnapshotRetentionDays !== job.model_snapshot_retention_days) { + return { model_snapshot_retention_days: modelSnapshotRetentionDays }; + } + return {}; +} + +function extractDailyModelSnapshotRetentionAfterDays(job, newJobData) { + const dailyModelSnapshotRetentionAfterDays = newJobData.dailyModelSnapshotRetentionAfterDays; + if (dailyModelSnapshotRetentionAfterDays !== job.daily_model_snapshot_retention_after_days) { + return { daily_model_snapshot_retention_after_days: dailyModelSnapshotRetentionAfterDays }; + } + return {}; +} + function extractDetectorDescriptions(job, newJobData) { const detectors = []; const descriptions = newJobData.detectorDescriptions.map((d, i) => ({ diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js index 974afafc08b6b..ec5ef6fce26b5 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/edit_job_flyout/tabs/job_details.js @@ -7,7 +7,14 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; -import { EuiFieldText, EuiForm, EuiFormRow, EuiSpacer, EuiComboBox } from '@elastic/eui'; +import { + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiComboBox, + EuiFieldNumber, +} from '@elastic/eui'; import { ml } from '../../../../../services/ml_api_service'; import { i18n } from '@kbn/i18n'; @@ -24,6 +31,8 @@ export class JobDetails extends Component { mml: '', mmlValidationError: '', groupsValidationError: '', + modelSnapshotRetentionDays: 1, + dailyModelSnapshotRetentionAfterDays: 1, }; this.setJobDetails = props.setJobDetails; @@ -52,6 +61,8 @@ export class JobDetails extends Component { mml: props.jobModelMemoryLimit, mmlValidationError: props.jobModelMemoryLimitValidationError, groupsValidationError: props.jobGroupsValidationError, + modelSnapshotRetentionDays: props.jobModelSnapshotRetentionDays, + dailyModelSnapshotRetentionAfterDays: props.jobDailyModelSnapshotRetentionAfterDays, }; } @@ -63,6 +74,24 @@ export class JobDetails extends Component { this.setJobDetails({ jobModelMemoryLimit: e.target.value }); }; + onModelSnapshotRetentionDaysChange = (e) => { + const jobModelSnapshotRetentionDays = Math.floor(+e.target.value); + + this.setJobDetails({ + jobModelSnapshotRetentionDays, + ...(this.state.dailyModelSnapshotRetentionAfterDays > jobModelSnapshotRetentionDays + ? { jobDailyModelSnapshotRetentionAfterDays: jobModelSnapshotRetentionDays } + : {}), + }); + }; + + onDailyModelSnapshotRetentionAfterDaysChange = (e) => { + const jobDailyModelSnapshotRetentionAfterDays = Math.floor(+e.target.value); + if (jobDailyModelSnapshotRetentionAfterDays <= this.state.modelSnapshotRetentionDays) { + this.setJobDetails({ jobDailyModelSnapshotRetentionAfterDays }); + } + }; + onGroupsChange = (selectedGroups) => { this.setJobDetails({ jobGroups: selectedGroups.map((g) => g.label) }); }; @@ -104,6 +133,8 @@ export class JobDetails extends Component { groups, mmlValidationError, groupsValidationError, + modelSnapshotRetentionDays, + dailyModelSnapshotRetentionAfterDays, } = this.state; const { datafeedRunning } = this.props; return ( @@ -172,6 +203,35 @@ export class JobDetails extends Component { disabled={datafeedRunning} /> + + } + > + + + + } + > + + ); diff --git a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts index de393e002c55b..3a01a616a4f7c 100644 --- a/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/anomaly_detectors_schema.ts @@ -69,6 +69,7 @@ export const anomalyDetectionUpdateJobSchema = schema.object({ }) ), groups: schema.maybe(schema.arrayOf(schema.maybe(schema.string()))), + model_snapshot_retention_days: schema.maybe(schema.number()), }); export const analysisConfigSchema = schema.object({ diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts index 04171dd8137e5..7bfe101b920fb 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/resolver/alerts.ts @@ -7,6 +7,7 @@ import { TypeOf } from '@kbn/config-schema'; import { RequestHandler, Logger } from 'kibana/server'; import { validateAlerts } from '../../../../common/endpoint/schema/resolver'; +import { eventsIndexPattern } from '../../../../common/endpoint/constants'; import { Fetcher } from './utils/fetch'; import { EndpointAppContext } from '../../types'; @@ -20,11 +21,9 @@ export function handleAlerts( query: { alerts, afterAlert, legacyEndpointID: endpointID }, } = req; try { - const indexRetriever = endpointAppContext.service.getIndexPatternRetriever(); const client = context.core.elasticsearch.legacy.client; - const indexPattern = await indexRetriever.getEventIndexPattern(context); - const fetcher = new Fetcher(client, id, indexPattern, endpointID); + const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID); return res.ok({ body: await fetcher.alerts(alerts, afterAlert), diff --git a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.tsx b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.tsx index 4254004dba4e0..1d0dcad73795b 100644 --- a/x-pack/plugins/uptime/public/components/common/uptime_date_picker.tsx +++ b/x-pack/plugins/uptime/public/components/common/uptime_date_picker.tsx @@ -10,17 +10,6 @@ import { useUrlParams } from '../../hooks'; import { CLIENT_DEFAULTS } from '../../../common/constants'; import { UptimeRefreshContext, UptimeSettingsContext } from '../../contexts'; -// TODO: when EUI exports types for this, this should be replaced -interface SuperDateRangePickerRangeChangedEvent { - start: string; - end: string; -} - -interface SuperDateRangePickerRefreshChangedEvent { - isPaused: boolean; - refreshInterval?: number; -} - export interface CommonlyUsedRange { from: string; to: string; @@ -52,12 +41,12 @@ export const UptimeDatePicker = () => { commonlyUsedRanges={euiCommonlyUsedRanges} isPaused={autorefreshIsPaused} refreshInterval={autorefreshInterval} - onTimeChange={({ start, end }: SuperDateRangePickerRangeChangedEvent) => { + onTimeChange={({ start, end }) => { updateUrl({ dateRangeStart: start, dateRangeEnd: end }); refreshApp(); }} onRefresh={refreshApp} - onRefreshChange={({ isPaused, refreshInterval }: SuperDateRangePickerRefreshChangedEvent) => { + onRefreshChange={({ isPaused, refreshInterval }) => { updateUrl({ autorefreshInterval: refreshInterval === undefined ? autorefreshInterval : refreshInterval, diff --git a/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts b/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts index 7281e6a06a064..caa29561a1256 100644 --- a/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts +++ b/x-pack/test/api_integration/apis/ingest_manager/agent_config.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('ingest_manager_agent_configs', () => { describe('POST /api/ingest_manager/agent_configs', () => { @@ -36,5 +37,70 @@ export default function ({ getService }: FtrProviderContext) { .expect(400); }); }); + + describe('POST /api/ingest_manager/agent_configs/{agentConfigId}/copy', () => { + before(async () => { + await esArchiver.loadIfNeeded('fleet/agents'); + }); + after(async () => { + await esArchiver.unload('fleet/agents'); + }); + + const TEST_CONFIG_ID = 'config1'; + + it('should work with valid values', async () => { + const { + body: { success, item }, + } = await supertest + .post(`/api/ingest_manager/agent_configs/${TEST_CONFIG_ID}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Copied config', + description: 'Test', + }) + .expect(200); + const { id, updated_at, ...newConfig } = item; + + expect(success).to.be(true); + expect(newConfig).to.eql({ + name: 'Copied config', + description: 'Test', + namespace: 'default', + monitoring_enabled: ['logs', 'metrics'], + revision: 1, + updated_by: 'elastic', + datasources: [], + }); + }); + + it('should return a 500 with invalid source config', async () => { + await supertest + .post(`/api/ingest_manager/agent_configs/INVALID_CONFIG_ID/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: 'Copied config', + description: '', + }) + .expect(500); + }); + + it('should return a 400 with invalid payload', async () => { + await supertest + .post(`/api/ingest_manager/agent_configs/${TEST_CONFIG_ID}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({}) + .expect(400); + }); + + it('should return a 400 with invalid name', async () => { + await supertest + .post(`/api/ingest_manager/agent_configs/${TEST_CONFIG_ID}/copy`) + .set('kbn-xsrf', 'xxxx') + .send({ + name: '', + }) + .expect(400); + }); + }); }); }