From d3f6fb9870ff7629644626abb0c3d57b4510b65f Mon Sep 17 00:00:00 2001 From: Mason Herron <46727170+Supplementing@users.noreply.github.com> Date: Wed, 18 Jun 2025 10:11:45 -0600 Subject: [PATCH] [Fleet] Add feature-flag gate for single agent migrations UI/API & bulk migration API (#224143) ## Summary Closes https://github.com/elastic/ingest-dev/issues/5694 - Adds a feature flag gate for the `single` agent migration UI and API - Also gates the bulk migrate `endpoint`, UI will be gated separately as part of https://github.com/elastic/kibana/pull/224334 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks N/A --------- Co-authored-by: Elastic Machine Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- oas_docs/bundle.json | 312 +----------------- oas_docs/bundle.serverless.json | 312 +----------------- oas_docs/output/kibana.serverless.yaml | 206 +----------- oas_docs/output/kibana.yaml | 206 +----------- .../fleet/common/experimental_features.ts | 1 + .../components/table_row_actions.test.tsx | 20 +- .../components/table_row_actions.tsx | 4 +- .../shared/fleet/server/routes/agent/index.ts | 121 +++---- .../server/routes/agent/migrate_handlers.ts | 1 + .../apis/agents/migrate.ts | 1 - .../test/fleet_api_integration/config.base.ts | 5 +- 11 files changed, 96 insertions(+), 1093 deletions(-) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index 4ab01093103a..474f0996eb2d 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -17238,159 +17238,6 @@ ] } }, - "/api/fleet/agents/bulk_migrate": { - "post": { - "description": "Bulk migrate agents to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.", - "operationId": "post-fleet-agents-bulk-migrate", - "parameters": [ - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "agents": { - "items": { - "type": "string" - }, - "type": "array" - }, - "enrollment_token": { - "type": "string" - }, - "settings": { - "additionalProperties": false, - "properties": { - "ca_sha256": { - "type": "string" - }, - "certificate_authorities": { - "type": "string" - }, - "elastic_agent_cert": { - "type": "string" - }, - "elastic_agent_cert_key": { - "type": "string" - }, - "elastic_agent_cert_key_passphrase": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "insecure": { - "type": "boolean" - }, - "proxy_disabled": { - "type": "boolean" - }, - "proxy_headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "proxy_url": { - "type": "string" - }, - "staging": { - "type": "boolean" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "uri": { - "format": "uri", - "type": "string" - } - }, - "required": [ - "agents", - "uri", - "enrollment_token" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "actionId": { - "type": "string" - } - }, - "required": [ - "actionId" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "attributes": {}, - "error": { - "type": "string" - }, - "errorType": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message", - "attributes" - ], - "type": "object" - } - } - } - } - }, - "summary": "Migrate multiple agents", - "tags": [ - "Elastic Agents" - ] - } - }, "/api/fleet/agents/bulk_reassign": { "post": { "description": "[Required authorization] Route required privileges: fleet-agents-all.", @@ -19406,7 +19253,7 @@ } } }, - "summary": "Update an agent", + "summary": "Update an agent by ID", "tags": [ "Elastic Agents" ] @@ -19621,163 +19468,6 @@ ] } }, - "/api/fleet/agents/{agentId}/migrate": { - "post": { - "description": "Migrate a single agent to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.", - "operationId": "post-fleet-agents-agentid-migrate", - "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": "agentId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "enrollment_token": { - "type": "string" - }, - "settings": { - "additionalProperties": false, - "properties": { - "ca_sha256": { - "type": "string" - }, - "certificate_authorities": { - "type": "string" - }, - "elastic_agent_cert": { - "type": "string" - }, - "elastic_agent_cert_key": { - "type": "string" - }, - "elastic_agent_cert_key_passphrase": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "insecure": { - "type": "boolean" - }, - "proxy_disabled": { - "type": "boolean" - }, - "proxy_headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "proxy_url": { - "type": "string" - }, - "replace_token": { - "type": "boolean" - }, - "staging": { - "type": "boolean" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "uri": { - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri", - "enrollment_token" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "actionId": { - "type": "string" - } - }, - "required": [ - "actionId" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "attributes": {}, - "error": { - "type": "string" - }, - "errorType": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message", - "attributes" - ], - "type": "object" - } - } - } - } - }, - "summary": "Migrate a single agent", - "tags": [ - "Elastic Agents" - ] - } - }, "/api/fleet/agents/{agentId}/reassign": { "post": { "description": "[Required authorization] Route required privileges: fleet-agents-all.", diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 92a15c7a933a..29bf90683334 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -17238,159 +17238,6 @@ ] } }, - "/api/fleet/agents/bulk_migrate": { - "post": { - "description": "Bulk migrate agents to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.", - "operationId": "post-fleet-agents-bulk-migrate", - "parameters": [ - { - "description": "A required header to protect against CSRF attacks", - "in": "header", - "name": "kbn-xsrf", - "required": true, - "schema": { - "example": "true", - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "agents": { - "items": { - "type": "string" - }, - "type": "array" - }, - "enrollment_token": { - "type": "string" - }, - "settings": { - "additionalProperties": false, - "properties": { - "ca_sha256": { - "type": "string" - }, - "certificate_authorities": { - "type": "string" - }, - "elastic_agent_cert": { - "type": "string" - }, - "elastic_agent_cert_key": { - "type": "string" - }, - "elastic_agent_cert_key_passphrase": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "insecure": { - "type": "boolean" - }, - "proxy_disabled": { - "type": "boolean" - }, - "proxy_headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "proxy_url": { - "type": "string" - }, - "staging": { - "type": "boolean" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "uri": { - "format": "uri", - "type": "string" - } - }, - "required": [ - "agents", - "uri", - "enrollment_token" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "actionId": { - "type": "string" - } - }, - "required": [ - "actionId" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "attributes": {}, - "error": { - "type": "string" - }, - "errorType": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message", - "attributes" - ], - "type": "object" - } - } - } - } - }, - "summary": "Migrate multiple agents", - "tags": [ - "Elastic Agents" - ] - } - }, "/api/fleet/agents/bulk_reassign": { "post": { "description": "[Required authorization] Route required privileges: fleet-agents-all.", @@ -19406,7 +19253,7 @@ } } }, - "summary": "Update an agent", + "summary": "Update an agent by ID", "tags": [ "Elastic Agents" ] @@ -19621,163 +19468,6 @@ ] } }, - "/api/fleet/agents/{agentId}/migrate": { - "post": { - "description": "Migrate a single agent to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.", - "operationId": "post-fleet-agents-agentid-migrate", - "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": "agentId", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "enrollment_token": { - "type": "string" - }, - "settings": { - "additionalProperties": false, - "properties": { - "ca_sha256": { - "type": "string" - }, - "certificate_authorities": { - "type": "string" - }, - "elastic_agent_cert": { - "type": "string" - }, - "elastic_agent_cert_key": { - "type": "string" - }, - "elastic_agent_cert_key_passphrase": { - "type": "string" - }, - "headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "insecure": { - "type": "boolean" - }, - "proxy_disabled": { - "type": "boolean" - }, - "proxy_headers": { - "additionalProperties": { - "type": "string" - }, - "type": "object" - }, - "proxy_url": { - "type": "string" - }, - "replace_token": { - "type": "boolean" - }, - "staging": { - "type": "boolean" - }, - "tags": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "type": "object" - }, - "uri": { - "format": "uri", - "type": "string" - } - }, - "required": [ - "uri", - "enrollment_token" - ], - "type": "object" - } - } - } - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "properties": { - "actionId": { - "type": "string" - } - }, - "required": [ - "actionId" - ], - "type": "object" - } - } - } - }, - "400": { - "content": { - "application/json": { - "schema": { - "additionalProperties": false, - "description": "Generic Error", - "properties": { - "attributes": {}, - "error": { - "type": "string" - }, - "errorType": { - "type": "string" - }, - "message": { - "type": "string" - }, - "statusCode": { - "type": "number" - } - }, - "required": [ - "message", - "attributes" - ], - "type": "object" - } - } - } - } - }, - "summary": "Migrate a single agent", - "tags": [ - "Elastic Agents" - ] - } - }, "/api/fleet/agents/{agentId}/reassign": { "post": { "description": "[Required authorization] Route required privileges: fleet-agents-all.", diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 009493d4cecb..b9a24fb65287 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -21371,7 +21371,7 @@ paths: required: - message - attributes - summary: Update an agent + summary: Update an agent by ID tags: - Elastic Agents /api/fleet/agents/{agentId}/actions: @@ -21514,109 +21514,6 @@ paths: summary: Create an agent action tags: - Elastic Agent actions - /api/fleet/agents/{agentId}/migrate: - post: - description: 'Migrate a single agent to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.' - operationId: post-fleet-agents-agentid-migrate - 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: agentId - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - enrollment_token: - type: string - settings: - additionalProperties: false - type: object - properties: - ca_sha256: - type: string - certificate_authorities: - type: string - elastic_agent_cert: - type: string - elastic_agent_cert_key: - type: string - elastic_agent_cert_key_passphrase: - type: string - headers: - additionalProperties: - type: string - type: object - insecure: - type: boolean - proxy_disabled: - type: boolean - proxy_headers: - additionalProperties: - type: string - type: object - proxy_url: - type: string - replace_token: - type: boolean - staging: - type: boolean - tags: - items: - type: string - type: array - uri: - format: uri - type: string - required: - - uri - - enrollment_token - responses: - '200': - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - actionId: - type: string - required: - - actionId - '400': - content: - application/json: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - attributes: {} - error: - type: string - errorType: - type: string - message: - type: string - statusCode: - type: number - required: - - message - - attributes - summary: Migrate a single agent - tags: - - Elastic Agents /api/fleet/agents/{agentId}/reassign: post: description: '[Required authorization] Route required privileges: fleet-agents-all.' @@ -22214,107 +22111,6 @@ paths: summary: Get available agent versions tags: - Elastic Agents - /api/fleet/agents/bulk_migrate: - post: - description: 'Bulk migrate agents to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.' - operationId: post-fleet-agents-bulk-migrate - parameters: - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - requestBody: - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - agents: - items: - type: string - type: array - enrollment_token: - type: string - settings: - additionalProperties: false - type: object - properties: - ca_sha256: - type: string - certificate_authorities: - type: string - elastic_agent_cert: - type: string - elastic_agent_cert_key: - type: string - elastic_agent_cert_key_passphrase: - type: string - headers: - additionalProperties: - type: string - type: object - insecure: - type: boolean - proxy_disabled: - type: boolean - proxy_headers: - additionalProperties: - type: string - type: object - proxy_url: - type: string - staging: - type: boolean - tags: - items: - type: string - type: array - uri: - format: uri - type: string - required: - - agents - - uri - - enrollment_token - responses: - '200': - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - actionId: - type: string - required: - - actionId - '400': - content: - application/json: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - attributes: {} - error: - type: string - errorType: - type: string - message: - type: string - statusCode: - type: number - required: - - message - - attributes - summary: Migrate multiple agents - tags: - - Elastic Agents /api/fleet/agents/bulk_reassign: post: description: '[Required authorization] Route required privileges: fleet-agents-all.' diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 0ad909ae6ff0..14f8de187a57 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -23613,7 +23613,7 @@ paths: required: - message - attributes - summary: Update an agent + summary: Update an agent by ID tags: - Elastic Agents /api/fleet/agents/{agentId}/actions: @@ -23756,109 +23756,6 @@ paths: summary: Create an agent action tags: - Elastic Agent actions - /api/fleet/agents/{agentId}/migrate: - post: - description: 'Migrate a single agent to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.' - operationId: post-fleet-agents-agentid-migrate - 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: agentId - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - enrollment_token: - type: string - settings: - additionalProperties: false - type: object - properties: - ca_sha256: - type: string - certificate_authorities: - type: string - elastic_agent_cert: - type: string - elastic_agent_cert_key: - type: string - elastic_agent_cert_key_passphrase: - type: string - headers: - additionalProperties: - type: string - type: object - insecure: - type: boolean - proxy_disabled: - type: boolean - proxy_headers: - additionalProperties: - type: string - type: object - proxy_url: - type: string - replace_token: - type: boolean - staging: - type: boolean - tags: - items: - type: string - type: array - uri: - format: uri - type: string - required: - - uri - - enrollment_token - responses: - '200': - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - actionId: - type: string - required: - - actionId - '400': - content: - application/json: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - attributes: {} - error: - type: string - errorType: - type: string - message: - type: string - statusCode: - type: number - required: - - message - - attributes - summary: Migrate a single agent - tags: - - Elastic Agents /api/fleet/agents/{agentId}/reassign: post: description: '[Required authorization] Route required privileges: fleet-agents-all.' @@ -24456,107 +24353,6 @@ paths: summary: Get available agent versions tags: - Elastic Agents - /api/fleet/agents/bulk_migrate: - post: - description: 'Bulk migrate agents to another cluster.

[Required authorization] Route required privileges: fleet-agents-all.' - operationId: post-fleet-agents-bulk-migrate - parameters: - - description: A required header to protect against CSRF attacks - in: header - name: kbn-xsrf - required: true - schema: - example: 'true' - type: string - requestBody: - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - agents: - items: - type: string - type: array - enrollment_token: - type: string - settings: - additionalProperties: false - type: object - properties: - ca_sha256: - type: string - certificate_authorities: - type: string - elastic_agent_cert: - type: string - elastic_agent_cert_key: - type: string - elastic_agent_cert_key_passphrase: - type: string - headers: - additionalProperties: - type: string - type: object - insecure: - type: boolean - proxy_disabled: - type: boolean - proxy_headers: - additionalProperties: - type: string - type: object - proxy_url: - type: string - staging: - type: boolean - tags: - items: - type: string - type: array - uri: - format: uri - type: string - required: - - agents - - uri - - enrollment_token - responses: - '200': - content: - application/json: - schema: - additionalProperties: false - type: object - properties: - actionId: - type: string - required: - - actionId - '400': - content: - application/json: - schema: - additionalProperties: false - description: Generic Error - type: object - properties: - attributes: {} - error: - type: string - errorType: - type: string - message: - type: string - statusCode: - type: number - required: - - message - - attributes - summary: Migrate multiple agents - tags: - - Elastic Agents /api/fleet/agents/bulk_reassign: post: description: '[Required authorization] Route required privileges: fleet-agents-all.' diff --git a/x-pack/platform/plugins/shared/fleet/common/experimental_features.ts b/x-pack/platform/plugins/shared/fleet/common/experimental_features.ts index 0ae2635938f3..37aa1dfe99a3 100644 --- a/x-pack/platform/plugins/shared/fleet/common/experimental_features.ts +++ b/x-pack/platform/plugins/shared/fleet/common/experimental_features.ts @@ -15,6 +15,7 @@ const _allowedExperimentalValues = { enableSSLSecrets: false, installedIntegrationsTabularUI: true, enabledUpgradeAgentlessDeploymentsTask: true, + enableAgentMigrations: false, }; /** diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx index f4513f26c49f..60c239388cf5 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.test.tsx @@ -53,7 +53,7 @@ function renderTableRowActions({ } describe('TableRowActions', () => { beforeEach(() => { - mockedExperimentalFeaturesService.get.mockReturnValue({} as any); + mockedExperimentalFeaturesService.get.mockReturnValue({ enableAgentMigrations: true } as any); // mock the flag as true so the test runs and the table action item renders mockedUseAuthz.mockReturnValue({ fleet: { all: true, @@ -128,6 +128,24 @@ describe('TableRowActions', () => { } as AgentPolicy, }); + expect(res).toBe(null); + }); + it('should not render an active action button when feature flag is disabled', async () => { + mockedExperimentalFeaturesService.get.mockReturnValue({ + enableAgentMigrations: false, + } as any); + const res = renderAndGetMigrateButton({ + agent: { + active: true, + status: 'online', + local_metadata: { elastic: { agent: { version: '8.8.0' } } }, + } as any, + agentPolicy: { + is_managed: false, + is_protected: false, + } as AgentPolicy, + }); + expect(res).toBe(null); }); }); diff --git a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx index 1f242e5c85fc..55aa9569711a 100644 --- a/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx +++ b/x-pack/platform/plugins/shared/fleet/public/applications/fleet/sections/agents/agent_list_page/components/table_row_actions.tsx @@ -20,6 +20,7 @@ import { useLink } from '../../../../hooks'; import { useAuthz } from '../../../../../../hooks/use_authz'; import { ContextMenuActions } from '../../../../components'; import { isAgentUpgradeable } from '../../../../services'; +import { ExperimentalFeaturesService } from '../../../../services'; export const TableRowActions: React.FunctionComponent<{ agent: Agent; @@ -46,6 +47,7 @@ export const TableRowActions: React.FunctionComponent<{ const authz = useAuthz(); const isFleetServerAgent = agentPolicy?.package_policies?.some((p) => p.package?.name === FLEET_SERVER_PACKAGE) ?? false; + const agentMigrationsEnabled = ExperimentalFeaturesService.get().enableAgentMigrations; const isUnenrolling = agent.status === 'unenrolling'; const [isMenuOpen, setIsMenuOpen] = useState(false); const menuItems = [ @@ -57,7 +59,7 @@ export const TableRowActions: React.FunctionComponent<{ , ]; - if (!agentPolicy?.is_protected && !isFleetServerAgent) { + if (!agentPolicy?.is_protected && !isFleetServerAgent && agentMigrationsEnabled) { menuItems.push( MigrateSingleAgentResponseSchema, - }, - 400: { - body: genericErrorResponse, + summary: `Migrate a single agent`, + description: `Migrate a single agent to another cluster.`, + options: { + tags: ['oas-tag:Elastic Agents'], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: MigrateSingleAgentRequestSchema, + response: { + 200: { + body: () => MigrateSingleAgentResponseSchema, + }, + 400: { + body: genericErrorResponse, + }, }, }, }, - }, - migrateSingleAgentHandler - ); - // Bulk migrate multiple agents - router.versioned - .post({ - path: AGENT_API_ROUTES.BULK_MIGRATE_PATTERN, - security: { - authz: { - requiredPrivileges: [FLEET_API_PRIVILEGES.AGENTS.ALL], + + migrateSingleAgentHandler + ); + + // Bulk migrate multiple agents + router.versioned + .post({ + path: AGENT_API_ROUTES.BULK_MIGRATE_PATTERN, + security: { + authz: { + requiredPrivileges: [FLEET_API_PRIVILEGES.AGENTS.ALL], + }, }, - }, - summary: `Migrate multiple agents`, - description: `Bulk migrate agents to another cluster.`, - options: { - tags: ['oas-tag:Elastic Agents'], - }, - }) - .addVersion( - { - version: API_VERSIONS.public.v1, - validate: { - request: BulkMigrateAgentsRequestSchema, - response: { - 200: { - body: () => BulkMigrateAgentsResponseSchema, - }, - 400: { - body: genericErrorResponse, + summary: `Migrate multiple agents`, + description: `Bulk migrate agents to another cluster.`, + options: { + tags: ['oas-tag:Elastic Agents'], + }, + }) + .addVersion( + { + version: API_VERSIONS.public.v1, + validate: { + request: BulkMigrateAgentsRequestSchema, + response: { + 200: { + body: () => BulkMigrateAgentsResponseSchema, + }, + 400: { + body: genericErrorResponse, + }, }, }, }, - }, - bulkMigrateAgentsHandler - ); + bulkMigrateAgentsHandler + ); + } // Update router.versioned .put({ @@ -201,7 +208,7 @@ export const registerAPIRoutes = (router: FleetAuthzRouter, config: FleetConfigT requiredPrivileges: [FLEET_API_PRIVILEGES.AGENTS.ALL], }, }, - summary: `Update an agent`, + summary: `Update an agent by ID`, description: `Update an agent by ID.`, options: { tags: ['oas-tag:Elastic Agents'], diff --git a/x-pack/platform/plugins/shared/fleet/server/routes/agent/migrate_handlers.ts b/x-pack/platform/plugins/shared/fleet/server/routes/agent/migrate_handlers.ts index 6e1eb7e99efe..f58c5b35bffe 100644 --- a/x-pack/platform/plugins/shared/fleet/server/routes/agent/migrate_handlers.ts +++ b/x-pack/platform/plugins/shared/fleet/server/routes/agent/migrate_handlers.ts @@ -23,6 +23,7 @@ export const migrateSingleAgentHandler: FleetRequestHandler< const esClient = coreContext.elasticsearch.client.asInternalUser; const soClient = coreContext.savedObjects.client; const options = request.body; + // First validate the agent exists const agent = await AgentService.getAgentById(esClient, soClient, request.params.agentId); // Using the agent id, get the agent policy diff --git a/x-pack/test/fleet_api_integration/apis/agents/migrate.ts b/x-pack/test/fleet_api_integration/apis/agents/migrate.ts index 1af1a10d2e5e..db2af0915688 100644 --- a/x-pack/test/fleet_api_integration/apis/agents/migrate.ts +++ b/x-pack/test/fleet_api_integration/apis/agents/migrate.ts @@ -6,7 +6,6 @@ */ import { AGENTS_INDEX } from '@kbn/fleet-plugin/common'; - import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; export default function (providerContext: FtrProviderContext) { diff --git a/x-pack/test/fleet_api_integration/config.base.ts b/x-pack/test/fleet_api_integration/config.base.ts index 958ff3e25f0c..669fb87f9b0e 100644 --- a/x-pack/test/fleet_api_integration/config.base.ts +++ b/x-pack/test/fleet_api_integration/config.base.ts @@ -86,7 +86,10 @@ export default async function ({ readConfigFile, log }: FtrConfigProviderContext './apis/fixtures/package_verification/signatures/fleet_test_key_public.asc' )}`, `--xpack.securitySolution.enableExperimental=${JSON.stringify(['endpointRbacEnabled'])}`, - `--xpack.fleet.enableExperimental=${JSON.stringify(['enableAutomaticAgentUpgrades'])}`, + `--xpack.fleet.enableExperimental=${JSON.stringify([ + 'enableAutomaticAgentUpgrades', + 'enableAgentMigrations', + ])}`, `--xpack.cloud.id='123456789'`, `--xpack.fleet.agentless.enabled=true`, `--xpack.fleet.agentless.api.url=http://localhost:8089/agentless-api`,