mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Fleet] Add new APIs and preconfiguration for Fleet server hosts per policy (#142746)
This commit is contained in:
parent
d206d8382a
commit
ad95c571b4
35 changed files with 1681 additions and 8 deletions
|
@ -254,6 +254,7 @@ export class KbnClientSavedObjects {
|
|||
'epm-packages',
|
||||
'epm-packages-assets',
|
||||
'fleet-preconfiguration-deletion-record',
|
||||
'fleet-fleet-server-host',
|
||||
];
|
||||
|
||||
const newOptions = { types, space: options?.space };
|
||||
|
|
|
@ -88,6 +88,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
|
|||
"file": "280f28bd48b3ad1f1a9f84c6c0ae6dd5ed1179da",
|
||||
"file-upload-usage-collection-telemetry": "8478924cf0057bd90df737155b364f98d05420a5",
|
||||
"fileShare": "3f88784b041bb8728a7f40763a08981828799a75",
|
||||
"fleet-fleet-server-host": "f00ca963f1bee868806319789cdc33f1f53a97e2",
|
||||
"fleet-preconfiguration-deletion-record": "7b28f200513c28ae774f1b7d7d7906954e3c6e16",
|
||||
"graph-workspace": "3342f2cd561afdde8f42f5fb284bf550dee8ebb5",
|
||||
"guided-onboarding-guide-state": "561db8d481b131a2bbf46b1e534d6ce960255135",
|
||||
|
|
|
@ -58,6 +58,7 @@ const previouslyRegisteredTypes = [
|
|||
'fleet-agents',
|
||||
'fleet-enrollment-api-keys',
|
||||
'fleet-preconfiguration-deletion-record',
|
||||
'fleet-fleet-server-host',
|
||||
'graph-workspace',
|
||||
'guided-setup-state',
|
||||
'guided-onboarding-guide-state',
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const FLEET_SERVER_HOST_SAVED_OBJECT_TYPE = 'fleet-fleet-server-host';
|
||||
|
||||
export const DEFAULT_FLEET_SERVER_HOST_ID = 'fleet-default-fleet-server-host';
|
|
@ -16,6 +16,7 @@ export * from './enrollment_api_key';
|
|||
export * from './settings';
|
||||
export * from './preconfiguration';
|
||||
export * from './download_source';
|
||||
export * from './fleet_server_policy_config';
|
||||
export * from './authz';
|
||||
|
||||
// TODO: This is the default `index.max_result_window` ES setting, which dictates
|
||||
|
|
|
@ -88,6 +88,15 @@ export const OUTPUT_API_ROUTES = {
|
|||
LOGSTASH_API_KEY_PATTERN: `${API_ROOT}/logstash_api_keys`,
|
||||
};
|
||||
|
||||
// Fleet server API routes
|
||||
export const FLEET_SERVER_HOST_API_ROUTES = {
|
||||
LIST_PATTERN: `${API_ROOT}/fleet_server_hosts`,
|
||||
CREATE_PATTERN: `${API_ROOT}/fleet_server_hosts`,
|
||||
INFO_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
|
||||
UPDATE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
|
||||
DELETE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
|
||||
};
|
||||
|
||||
// Settings API routes
|
||||
export const SETTINGS_API_ROUTES = {
|
||||
INFO_PATTERN: `${API_ROOT}/settings`,
|
||||
|
|
|
@ -3706,6 +3706,230 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/fleet_server_hosts": {
|
||||
"get": {
|
||||
"summary": "Fleet Server Hosts - List",
|
||||
"description": "Return a list of Fleet server host",
|
||||
"tags": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/fleet_server_host"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"perPage": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "get-fleet-server-hosts"
|
||||
},
|
||||
"post": {
|
||||
"summary": "Fleet Server Hosts - Create",
|
||||
"description": "Create a new Fleet Server Host",
|
||||
"tags": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item": {
|
||||
"$ref": "#/components/schemas/fleet_server_host"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"host_urls"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "post-fleet-server-hosts"
|
||||
}
|
||||
},
|
||||
"/fleet_server_hosts/{itemId}": {
|
||||
"get": {
|
||||
"summary": "Fleet Server Hosts - Info",
|
||||
"tags": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item": {
|
||||
"$ref": "#/components/schemas/fleet_server_host"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"item"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"operationId": "get-one-fleet-server-hosts"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": "itemId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"delete": {
|
||||
"summary": "Fleet Server Hosts - Delete",
|
||||
"operationId": "delete-fleet-server-hosts",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": "itemId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/kbn_xsrf"
|
||||
}
|
||||
]
|
||||
},
|
||||
"put": {
|
||||
"summary": "Fleet Server Hosts - Update",
|
||||
"operationId": "update-fleet-server-hosts",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"item": {
|
||||
"$ref": "#/components/schemas/fleet_server_host"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"item"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": "itemId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/parameters/kbn_xsrf"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
|
@ -3977,9 +4201,6 @@
|
|||
"has_seen_add_data_notice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"has_seen_fleet_migration_notice": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fleet_server_hosts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -5470,6 +5691,37 @@
|
|||
"name",
|
||||
"host"
|
||||
]
|
||||
},
|
||||
"fleet_server_host": {
|
||||
"title": "Fleet Server Host",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_default": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"is_preconfigured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"host_urls": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fleet_server_hosts",
|
||||
"id",
|
||||
"is_default",
|
||||
"is_preconfigured",
|
||||
"host_urls"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2289,6 +2289,145 @@ paths:
|
|||
operationId: generate-logstash-api-key
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
/fleet_server_hosts:
|
||||
get:
|
||||
summary: Fleet Server Hosts - List
|
||||
description: Return a list of Fleet server host
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/fleet_server_host'
|
||||
total:
|
||||
type: integer
|
||||
page:
|
||||
type: integer
|
||||
perPage:
|
||||
type: integer
|
||||
operationId: get-fleet-server-hosts
|
||||
post:
|
||||
summary: Fleet Server Hosts - Create
|
||||
description: Create a new Fleet Server Host
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: '#/components/schemas/fleet_server_host'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host_urls
|
||||
operationId: post-fleet-server-hosts
|
||||
/fleet_server_hosts/{itemId}:
|
||||
get:
|
||||
summary: Fleet Server Hosts - Info
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: '#/components/schemas/fleet_server_host'
|
||||
required:
|
||||
- item
|
||||
operationId: get-one-fleet-server-hosts
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
delete:
|
||||
summary: Fleet Server Hosts - Delete
|
||||
operationId: delete-fleet-server-hosts
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
put:
|
||||
summary: Fleet Server Hosts - Update
|
||||
operationId: update-fleet-server-hosts
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: '#/components/schemas/fleet_server_host'
|
||||
required:
|
||||
- item
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
- $ref: '#/components/parameters/kbn_xsrf'
|
||||
components:
|
||||
securitySchemes:
|
||||
basicAuth:
|
||||
|
@ -2474,8 +2613,6 @@ components:
|
|||
type: string
|
||||
has_seen_add_data_notice:
|
||||
type: boolean
|
||||
has_seen_fleet_migration_notice:
|
||||
type: boolean
|
||||
fleet_server_hosts:
|
||||
type: array
|
||||
items:
|
||||
|
@ -3509,5 +3646,27 @@ components:
|
|||
- is_default
|
||||
- name
|
||||
- host
|
||||
fleet_server_host:
|
||||
title: Fleet Server Host
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- fleet_server_hosts
|
||||
- id
|
||||
- is_default
|
||||
- is_preconfigured
|
||||
- host_urls
|
||||
security:
|
||||
- basicAuth: []
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
title: Fleet Server Host
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
is_preconfigured:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- fleet_server_hosts
|
||||
- id
|
||||
- is_default
|
||||
- is_preconfigured
|
||||
- host_urls
|
|
@ -124,6 +124,11 @@ paths:
|
|||
$ref: paths/agent_download_sources@{source_id}.yaml
|
||||
/logstash_api_keys:
|
||||
$ref: paths/logstash_api_keys.yaml
|
||||
# Fleet server hosts
|
||||
/fleet_server_hosts:
|
||||
$ref: paths/fleet_server_hosts.yaml
|
||||
/fleet_server_hosts/{itemId}:
|
||||
$ref: paths/fleet_server_hosts@{item_id}.yaml
|
||||
components:
|
||||
securitySchemes:
|
||||
basicAuth:
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
get:
|
||||
summary: Fleet Server Hosts - List
|
||||
description: Return a list of Fleet server hosts
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
$ref: ../components/schemas/fleet_server_host.yaml
|
||||
total:
|
||||
type: integer
|
||||
page:
|
||||
type: integer
|
||||
perPage:
|
||||
type: integer
|
||||
operationId: get-fleet-server-hosts
|
||||
post:
|
||||
summary: Fleet Server Hosts - Create
|
||||
description: 'Create a new Fleet Server Host'
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: ../components/schemas/fleet_server_host.yaml
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- host_urls
|
||||
operationId: post-fleet-server-hosts
|
|
@ -0,0 +1,80 @@
|
|||
get:
|
||||
summary: Fleet Server Hosts - Info
|
||||
tags: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: ../components/schemas/fleet_server_host.yaml
|
||||
required:
|
||||
- item
|
||||
operationId: get-one-fleet-server-hosts
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
delete:
|
||||
summary: Fleet Server Hosts - Delete
|
||||
operationId: delete-fleet-server-hosts
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
||||
put:
|
||||
summary: Fleet Server Hosts - Update
|
||||
operationId: update-fleet-server-hosts
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
is_default:
|
||||
type: boolean
|
||||
host_urls:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
item:
|
||||
$ref: ../components/schemas/fleet_server_host.yaml
|
||||
required:
|
||||
- item
|
||||
parameters:
|
||||
- schema:
|
||||
type: string
|
||||
name: itemId
|
||||
in: path
|
||||
required: true
|
||||
- $ref: ../components/headers/kbn_xsrf.yaml
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export interface NewFleetServerHost {
|
||||
name: string;
|
||||
host_urls: string[];
|
||||
is_default: boolean;
|
||||
is_preconfigured: boolean;
|
||||
}
|
||||
|
||||
export interface FleetServerHost extends NewFleetServerHost {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type FleetServerHostSOAttributes = NewFleetServerHost;
|
|
@ -16,3 +16,4 @@ export * from './enrollment_api_key';
|
|||
export * from './settings';
|
||||
export * from './preconfiguration';
|
||||
export * from './download_sources';
|
||||
export * from './fleet_server_policy_config';
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FleetServerHost } from '../models';
|
||||
|
||||
import type { ListResult } from './common';
|
||||
|
||||
export type GetFleetServerHostsResponse = ListResult<FleetServerHost>;
|
|
@ -21,6 +21,7 @@ import {
|
|||
PreconfiguredPackagesSchema,
|
||||
PreconfiguredAgentPoliciesSchema,
|
||||
PreconfiguredOutputsSchema,
|
||||
PreconfiguredFleetServerHostsSchema,
|
||||
} from './types';
|
||||
|
||||
const DEFAULT_BUNDLED_PACKAGE_LOCATION = path.join(__dirname, '../target/bundled_packages');
|
||||
|
@ -115,6 +116,7 @@ export const config: PluginConfigDescriptor = {
|
|||
packages: PreconfiguredPackagesSchema,
|
||||
agentPolicies: PreconfiguredAgentPoliciesSchema,
|
||||
outputs: PreconfiguredOutputsSchema,
|
||||
fleetServerHosts: PreconfiguredFleetServerHostsSchema,
|
||||
agentIdVerificationEnabled: schema.boolean({ defaultValue: true }),
|
||||
developer: schema.object({
|
||||
disableRegistryVersionCheck: schema.boolean({ defaultValue: false }),
|
||||
|
|
|
@ -64,6 +64,9 @@ export {
|
|||
DEFAULT_DOWNLOAD_SOURCE_URI,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
DEFAULT_DOWNLOAD_SOURCE_ID,
|
||||
// Fleet server host
|
||||
DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
// Authz
|
||||
ENDPOINT_PRIVILEGES,
|
||||
} from '../../common/constants';
|
||||
|
|
|
@ -76,6 +76,8 @@ export class OutputInvalidError extends FleetError {}
|
|||
export class OutputLicenceError extends FleetError {}
|
||||
export class DownloadSourceError extends FleetError {}
|
||||
|
||||
export class FleetServerHostUnauthorizedError extends FleetError {}
|
||||
|
||||
export class ArtifactsClientError extends FleetError {}
|
||||
export class ArtifactsClientAccessDeniedError extends FleetError {
|
||||
constructor(deniedPackageName: string, allowedPackageName: string) {
|
||||
|
|
|
@ -64,6 +64,7 @@ import {
|
|||
ASSETS_SAVED_OBJECT_TYPE,
|
||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
} from './constants';
|
||||
import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects';
|
||||
import {
|
||||
|
@ -80,6 +81,7 @@ import {
|
|||
registerPreconfigurationRoutes,
|
||||
registerDownloadSourcesRoutes,
|
||||
registerHealthCheckRoutes,
|
||||
registerFleetServerHostRoutes,
|
||||
} from './routes';
|
||||
|
||||
import type { ExternalCallback, FleetRequestHandlerContext } from './types';
|
||||
|
@ -161,6 +163,7 @@ const allSavedObjectTypes = [
|
|||
ASSETS_SAVED_OBJECT_TYPE,
|
||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -399,6 +402,7 @@ export class FleetPlugin
|
|||
registerSettingsRoutes(fleetAuthzRouter);
|
||||
registerDataStreamRoutes(fleetAuthzRouter);
|
||||
registerPreconfigurationRoutes(fleetAuthzRouter);
|
||||
registerFleetServerHostRoutes(fleetAuthzRouter);
|
||||
registerDownloadSourcesRoutes(fleetAuthzRouter);
|
||||
registerHealthCheckRoutes(fleetAuthzRouter);
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import type { RequestHandler } from '@kbn/core/server';
|
||||
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
|
||||
|
||||
import { defaultFleetErrorHandler } from '../../errors';
|
||||
import { agentPolicyService } from '../../services';
|
||||
import {
|
||||
createFleetServerHost,
|
||||
deleteFleetServerHost,
|
||||
getFleetServerHost,
|
||||
listFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../../services/fleet_server_host';
|
||||
import type {
|
||||
GetOneFleetServerHostRequestSchema,
|
||||
PostFleetServerHostRequestSchema,
|
||||
PutFleetServerHostRequestSchema,
|
||||
} from '../../types';
|
||||
|
||||
export const postFleetServerHost: RequestHandler<
|
||||
undefined,
|
||||
undefined,
|
||||
TypeOf<typeof PostFleetServerHostRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const coreContext = await context.core;
|
||||
const soClient = coreContext.savedObjects.client;
|
||||
const esClient = coreContext.elasticsearch.client.asInternalUser;
|
||||
try {
|
||||
const { id, ...data } = request.body;
|
||||
const FleetServerHost = await createFleetServerHost(
|
||||
soClient,
|
||||
{ ...data, is_preconfigured: false },
|
||||
{ id }
|
||||
);
|
||||
if (FleetServerHost.is_default) {
|
||||
await agentPolicyService.bumpAllAgentPolicies(soClient, esClient);
|
||||
}
|
||||
|
||||
const body = {
|
||||
item: FleetServerHost,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const getFleetServerPolicyHandler: RequestHandler<
|
||||
TypeOf<typeof GetOneFleetServerHostRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
try {
|
||||
const item = await getFleetServerHost(soClient, request.params.itemId);
|
||||
const body = {
|
||||
item,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound({
|
||||
body: { message: `Fleet server ${request.params.itemId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteFleetServerPolicyHandler: RequestHandler<
|
||||
TypeOf<typeof GetOneFleetServerHostRequestSchema.params>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
try {
|
||||
await deleteFleetServerHost(soClient, request.params.itemId);
|
||||
const body = {
|
||||
id: request.params.itemId,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound({
|
||||
body: { message: `Fleet server ${request.params.itemId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const putFleetServerPolicyHandler: RequestHandler<
|
||||
TypeOf<typeof PutFleetServerHostRequestSchema.params>,
|
||||
undefined,
|
||||
TypeOf<typeof PutFleetServerHostRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
try {
|
||||
const item = await updateFleetServerHost(soClient, request.params.itemId, request.body);
|
||||
const body = {
|
||||
item,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
|
||||
return response.notFound({
|
||||
body: { message: `Fleet server ${request.params.itemId} not found` },
|
||||
});
|
||||
}
|
||||
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
||||
export const getAllFleetServerPolicyHandler: RequestHandler<
|
||||
TypeOf<typeof PutFleetServerHostRequestSchema.params>,
|
||||
undefined,
|
||||
TypeOf<typeof PutFleetServerHostRequestSchema.body>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = (await context.core).savedObjects.client;
|
||||
try {
|
||||
const res = await listFleetServerHosts(soClient);
|
||||
const body = {
|
||||
items: res.items,
|
||||
page: res.page,
|
||||
perPage: res.perPage,
|
||||
total: res.total,
|
||||
};
|
||||
|
||||
return response.ok({ body });
|
||||
} catch (error) {
|
||||
return defaultFleetErrorHandler({ error, response });
|
||||
}
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { FLEET_SERVER_HOST_API_ROUTES } from '../../../common/constants';
|
||||
import {
|
||||
GetAllFleetServerHostRequestSchema,
|
||||
GetOneFleetServerHostRequestSchema,
|
||||
PostFleetServerHostRequestSchema,
|
||||
PutFleetServerHostRequestSchema,
|
||||
} from '../../types';
|
||||
|
||||
import type { FleetAuthzRouter } from '../security';
|
||||
|
||||
import {
|
||||
deleteFleetServerPolicyHandler,
|
||||
getAllFleetServerPolicyHandler,
|
||||
getFleetServerPolicyHandler,
|
||||
postFleetServerHost,
|
||||
putFleetServerPolicyHandler,
|
||||
} from './handler';
|
||||
|
||||
export const registerRoutes = (router: FleetAuthzRouter) => {
|
||||
router.get(
|
||||
{
|
||||
path: FLEET_SERVER_HOST_API_ROUTES.LIST_PATTERN,
|
||||
validate: GetAllFleetServerHostRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
getAllFleetServerPolicyHandler
|
||||
);
|
||||
router.post(
|
||||
{
|
||||
path: FLEET_SERVER_HOST_API_ROUTES.CREATE_PATTERN,
|
||||
validate: PostFleetServerHostRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
postFleetServerHost
|
||||
);
|
||||
router.get(
|
||||
{
|
||||
path: FLEET_SERVER_HOST_API_ROUTES.INFO_PATTERN,
|
||||
validate: GetOneFleetServerHostRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
getFleetServerPolicyHandler
|
||||
);
|
||||
router.delete(
|
||||
{
|
||||
path: FLEET_SERVER_HOST_API_ROUTES.DELETE_PATTERN,
|
||||
validate: GetOneFleetServerHostRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
deleteFleetServerPolicyHandler
|
||||
);
|
||||
router.put(
|
||||
{
|
||||
path: FLEET_SERVER_HOST_API_ROUTES.UPDATE_PATTERN,
|
||||
validate: PutFleetServerHostRequestSchema,
|
||||
fleetAuthz: {
|
||||
fleet: { all: true },
|
||||
},
|
||||
},
|
||||
putFleetServerPolicyHandler
|
||||
);
|
||||
};
|
|
@ -18,3 +18,4 @@ export { registerRoutes as registerAppRoutes } from './app';
|
|||
export { registerRoutes as registerPreconfigurationRoutes } from './preconfiguration';
|
||||
export { registerRoutes as registerDownloadSourcesRoutes } from './download_source';
|
||||
export { registerRoutes as registerHealthCheckRoutes } from './health_check';
|
||||
export { registerRoutes as registerFleetServerHostRoutes } from './fleet_server_policy_config';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
|
||||
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
} from '../constants';
|
||||
|
||||
import {
|
||||
|
@ -344,6 +345,22 @@ const getSavedObjectTypes = (
|
|||
},
|
||||
},
|
||||
},
|
||||
[FLEET_SERVER_HOST_SAVED_OBJECT_TYPE]: {
|
||||
name: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
hidden: false,
|
||||
namespaceType: 'agnostic',
|
||||
management: {
|
||||
importableAndExportable: false,
|
||||
},
|
||||
mappings: {
|
||||
properties: {
|
||||
name: { type: 'keyword' },
|
||||
is_default: { type: 'boolean' },
|
||||
host_urls: { type: 'keyword', index: false },
|
||||
is_preconfigured: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function registerSavedObjects(
|
||||
|
|
145
x-pack/plugins/fleet/server/services/fleet_server_host.test.ts
Normal file
145
x-pack/plugins/fleet/server/services/fleet_server_host.test.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
|
||||
import {
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
} from '../constants';
|
||||
|
||||
import { appContextService } from './app_context';
|
||||
import { migrateSettingsToFleetServerHost } from './fleet_server_host';
|
||||
import { getCloudFleetServersHosts } from './settings';
|
||||
|
||||
jest.mock('./app_context');
|
||||
|
||||
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
|
||||
|
||||
describe('getCloudFleetServersHosts', () => {
|
||||
afterEach(() => {
|
||||
mockedAppContextService.getCloud.mockReset();
|
||||
});
|
||||
it('should return undefined if cloud is not setup', () => {
|
||||
expect(getCloudFleetServersHosts()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => {
|
||||
mockedAppContextService.getCloud.mockReturnValue({
|
||||
cloudId:
|
||||
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
|
||||
isCloudEnabled: true,
|
||||
deploymentId: 'deployment-id-1',
|
||||
apm: {},
|
||||
});
|
||||
|
||||
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"https://deployment-id-1.fleet.us-east-1.aws.found.io",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return fleet server hosts if cloud is correctly setup with a default port', () => {
|
||||
mockedAppContextService.getCloud.mockReturnValue({
|
||||
cloudId:
|
||||
'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl',
|
||||
isCloudEnabled: true,
|
||||
deploymentId: 'deployment-id-1',
|
||||
apm: {},
|
||||
});
|
||||
|
||||
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"https://deployment-id-1.fleet.test.fr:9243",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('migrateSettingsToFleetServerHost', () => {
|
||||
it('should not migrate settings if a default fleet server policy config exists', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [{ id: 'test123' }] } as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should not migrate settings if there is not old settings', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [] } as any;
|
||||
}
|
||||
|
||||
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
|
||||
return {
|
||||
saved_objects: [],
|
||||
} as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
soClient.create.mockResolvedValue({
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
attributes: {},
|
||||
} as any);
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
expect(soClient.create).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should migrate settings to new saved object', async () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
soClient.find.mockImplementation(({ type }) => {
|
||||
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
|
||||
return { saved_objects: [] } as any;
|
||||
}
|
||||
|
||||
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
|
||||
return {
|
||||
saved_objects: [
|
||||
{
|
||||
attributes: {
|
||||
fleet_server_hosts: ['https://fleetserver:8220'],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as any;
|
||||
}
|
||||
|
||||
throw new Error('Not mocked');
|
||||
});
|
||||
|
||||
soClient.create.mockResolvedValue({
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
attributes: {},
|
||||
} as any);
|
||||
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
expect(soClient.create).toBeCalledWith(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
expect.objectContaining({
|
||||
is_default: true,
|
||||
host_urls: ['https://fleetserver:8220'],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
237
x-pack/plugins/fleet/server/services/fleet_server_host.ts
Normal file
237
x-pack/plugins/fleet/server/services/fleet_server_host.ts
Normal file
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
import {
|
||||
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
SO_SEARCH_LIMIT,
|
||||
} from '../constants';
|
||||
|
||||
import type {
|
||||
SettingsSOAttributes,
|
||||
FleetServerHostSOAttributes,
|
||||
FleetServerHost,
|
||||
NewFleetServerHost,
|
||||
} from '../types';
|
||||
import { FleetServerHostUnauthorizedError } from '../errors';
|
||||
|
||||
export async function createFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
data: NewFleetServerHost,
|
||||
options?: { id?: string; overwrite?: boolean; fromPreconfiguration?: boolean }
|
||||
): Promise<FleetServerHost> {
|
||||
if (data.is_default) {
|
||||
const defaultItem = await getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
defaultItem.id,
|
||||
{ is_default: false },
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const res = await soClient.create<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
data,
|
||||
{ id: options?.id, overwrite: options?.overwrite }
|
||||
);
|
||||
|
||||
return {
|
||||
id: res.id,
|
||||
...res.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string
|
||||
): Promise<FleetServerHost> {
|
||||
const res = await soClient.get<FleetServerHostSOAttributes>(
|
||||
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
id
|
||||
);
|
||||
|
||||
return {
|
||||
id: res.id,
|
||||
...res.attributes,
|
||||
};
|
||||
}
|
||||
|
||||
export async function listFleetServerHosts(soClient: SavedObjectsClientContract) {
|
||||
const res = await soClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
perPage: SO_SEARCH_LIMIT,
|
||||
});
|
||||
|
||||
return {
|
||||
items: res.saved_objects.map<FleetServerHost>((so) => ({
|
||||
id: so.id,
|
||||
...so.attributes,
|
||||
})),
|
||||
total: res.total,
|
||||
page: res.page,
|
||||
perPage: res.per_page,
|
||||
};
|
||||
}
|
||||
|
||||
export async function deleteFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string,
|
||||
options?: { fromPreconfiguration?: boolean }
|
||||
) {
|
||||
const fleetServerHost = await getFleetServerHost(soClient, id);
|
||||
|
||||
if (fleetServerHost.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot delete ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (fleetServerHost.is_default) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Default Fleet Server hosts ${id} cannot be deleted.`
|
||||
);
|
||||
}
|
||||
|
||||
return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id);
|
||||
}
|
||||
|
||||
export async function updateFleetServerHost(
|
||||
soClient: SavedObjectsClientContract,
|
||||
id: string,
|
||||
data: Partial<FleetServerHost>,
|
||||
options?: { fromPreconfiguration?: boolean }
|
||||
) {
|
||||
const originalItem = await getFleetServerHost(soClient, id);
|
||||
|
||||
if (data.is_preconfigured && !options?.fromPreconfiguration) {
|
||||
throw new FleetServerHostUnauthorizedError(
|
||||
`Cannot update ${id} preconfigured fleet server host`
|
||||
);
|
||||
}
|
||||
|
||||
if (data.is_default) {
|
||||
const defaultItem = await getDefaultFleetServerHost(soClient);
|
||||
if (defaultItem && defaultItem.id !== id) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
defaultItem.id,
|
||||
{
|
||||
is_default: false,
|
||||
},
|
||||
{ fromPreconfiguration: options?.fromPreconfiguration }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await soClient.update<FleetServerHostSOAttributes>(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id, data);
|
||||
|
||||
return {
|
||||
...originalItem,
|
||||
...data,
|
||||
};
|
||||
}
|
||||
|
||||
export async function bulkGetFleetServerHosts(
|
||||
soClient: SavedObjectsClientContract,
|
||||
ids: string[],
|
||||
{ ignoreNotFound = false } = { ignoreNotFound: true }
|
||||
) {
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const res = await soClient.bulkGet<FleetServerHostSOAttributes>(
|
||||
ids.map((id) => ({
|
||||
id,
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
}))
|
||||
);
|
||||
|
||||
return res.saved_objects
|
||||
.map((so) => {
|
||||
if (so.error) {
|
||||
if (!ignoreNotFound || so.error.statusCode !== 404) {
|
||||
throw so.error;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
id: so.id,
|
||||
...so.attributes,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(fleetServerHostOrUndefined): fleetServerHostOrUndefined is FleetServerHost =>
|
||||
typeof fleetServerHostOrUndefined !== 'undefined'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default Fleet server policy hosts or throw if it does not exists
|
||||
*/
|
||||
export async function getDefaultFleetServerHost(
|
||||
soClient: SavedObjectsClientContract
|
||||
): Promise<FleetServerHost | null> {
|
||||
const res = await soClient.find<FleetServerHostSOAttributes>({
|
||||
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
|
||||
filter: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}.attributes.is_default:true`,
|
||||
});
|
||||
|
||||
if (res.saved_objects.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: res.saved_objects[0].id,
|
||||
...res.saved_objects[0].attributes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate Global setting fleet server hosts to their own saved object
|
||||
*/
|
||||
export async function migrateSettingsToFleetServerHost(soClient: SavedObjectsClientContract) {
|
||||
const defaultFleetServerHost = await getDefaultFleetServerHost(soClient);
|
||||
if (defaultFleetServerHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await soClient.find<SettingsSOAttributes>({
|
||||
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
|
||||
});
|
||||
|
||||
const oldSettings = res.saved_objects[0];
|
||||
if (
|
||||
!oldSettings ||
|
||||
!oldSettings.attributes.fleet_server_hosts ||
|
||||
oldSettings.attributes.fleet_server_hosts.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Migrate
|
||||
await createFleetServerHost(
|
||||
soClient,
|
||||
{
|
||||
name: 'Default',
|
||||
host_urls: oldSettings.attributes.fleet_server_hosts,
|
||||
is_default: true,
|
||||
is_preconfigured: false,
|
||||
},
|
||||
{
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
overwrite: true,
|
||||
}
|
||||
);
|
||||
}
|
|
@ -59,3 +59,6 @@ export { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration';
|
|||
// Package Services
|
||||
export { PackageServiceImpl } from './epm';
|
||||
export type { PackageService, PackageClient } from './epm';
|
||||
|
||||
// Fleet server policy config
|
||||
export { migrateSettingsToFleetServerHost } from './fleet_server_host';
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getPreconfiguredFleetServerHostFromConfig } from './fleet_server_host';
|
||||
|
||||
jest.mock('../fleet_server_host');
|
||||
|
||||
describe('getPreconfiguredFleetServerHostFromConfig', () => {
|
||||
it('should work with preconfigured fleetServerHosts', () => {
|
||||
const config = {
|
||||
fleetServerHosts: [
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
is_default: true,
|
||||
host_urls: ['http://test.fr'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = getPreconfiguredFleetServerHostFromConfig(config);
|
||||
|
||||
expect(res).toEqual(config.fleetServerHosts);
|
||||
});
|
||||
|
||||
it('should work with agents.fleet_server.hosts', () => {
|
||||
const config = {
|
||||
agents: { fleet_server: { hosts: ['http://test.fr'] } },
|
||||
};
|
||||
|
||||
const res = getPreconfiguredFleetServerHostFromConfig(config);
|
||||
|
||||
expect(res).toEqual([
|
||||
{
|
||||
id: 'fleet-default-fleet-server-host',
|
||||
name: 'Default',
|
||||
host_urls: ['http://test.fr'],
|
||||
is_default: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should work with agents.fleet_server.hosts and preconfigured outputs', () => {
|
||||
const config = {
|
||||
agents: { fleet_server: { hosts: ['http://test.fr'] } },
|
||||
fleetServerHosts: [
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
is_default: false,
|
||||
host_urls: ['http://test.fr'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = getPreconfiguredFleetServerHostFromConfig(config);
|
||||
|
||||
expect(res).toHaveLength(2);
|
||||
expect(res.map(({ id }) => id)).toEqual(['fleet-123', 'fleet-default-fleet-server-host']);
|
||||
});
|
||||
|
||||
it('should throw if there is multiple default outputs', () => {
|
||||
const config = {
|
||||
agents: { fleet_server: { hosts: ['http://test.fr'] } },
|
||||
fleetServerHosts: [
|
||||
{
|
||||
id: 'fleet-123',
|
||||
name: 'TEST',
|
||||
is_default: true,
|
||||
host_urls: ['http://test.fr'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(() => getPreconfiguredFleetServerHostFromConfig(config)).toThrowError(
|
||||
/Only one default Fleet Server host is allowed/
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import type { FleetConfigType } from '../../config';
|
||||
import { DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants';
|
||||
|
||||
import type { FleetServerHost } from '../../types';
|
||||
import {
|
||||
bulkGetFleetServerHosts,
|
||||
createFleetServerHost,
|
||||
deleteFleetServerHost,
|
||||
listFleetServerHosts,
|
||||
updateFleetServerHost,
|
||||
} from '../fleet_server_host';
|
||||
|
||||
export function getPreconfiguredFleetServerHostFromConfig(config?: FleetConfigType) {
|
||||
const { fleetServerHosts: fleetServerHostsFromConfig } = config;
|
||||
|
||||
const legacyFleetServerHostsConfig = getConfigFleetServerHosts(config);
|
||||
|
||||
const fleetServerHosts: FleetServerHost[] = (fleetServerHostsFromConfig || []).concat([
|
||||
...(legacyFleetServerHostsConfig
|
||||
? [
|
||||
{
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
id: DEFAULT_FLEET_SERVER_HOST_ID,
|
||||
host_urls: legacyFleetServerHostsConfig,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
|
||||
if (fleetServerHosts.filter((fleetServerHost) => fleetServerHost.is_default).length > 1) {
|
||||
throw new Error('Only one default Fleet Server host is allowed');
|
||||
}
|
||||
|
||||
return fleetServerHosts;
|
||||
}
|
||||
|
||||
export async function ensurePreconfiguredFleetServerHosts(
|
||||
soClient: SavedObjectsClientContract,
|
||||
preconfiguredFleetServerHosts: FleetServerHost[]
|
||||
) {
|
||||
await createOrUpdatePreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts);
|
||||
await cleanPreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts);
|
||||
}
|
||||
|
||||
export async function createOrUpdatePreconfiguredFleetServerHosts(
|
||||
soClient: SavedObjectsClientContract,
|
||||
preconfiguredFleetServerHosts: FleetServerHost[]
|
||||
) {
|
||||
const existingFleetServerHosts = await bulkGetFleetServerHosts(
|
||||
soClient,
|
||||
preconfiguredFleetServerHosts.map(({ id }) => id),
|
||||
{ ignoreNotFound: true }
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
preconfiguredFleetServerHosts.map(async (preconfiguredFleetServerHost) => {
|
||||
const existingHost = existingFleetServerHosts.find(
|
||||
(fleetServerHost) => fleetServerHost.id === preconfiguredFleetServerHost.id
|
||||
);
|
||||
|
||||
const { id, ...data } = preconfiguredFleetServerHost;
|
||||
|
||||
const isCreate = !existingHost;
|
||||
const isUpdateWithNewData =
|
||||
existingHost &&
|
||||
(!existingHost.is_preconfigured ||
|
||||
existingHost.is_default !== preconfiguredFleetServerHost.is_default ||
|
||||
existingHost.name !== preconfiguredFleetServerHost.name ||
|
||||
!isEqual(existingHost?.host_urls, preconfiguredFleetServerHost.host_urls));
|
||||
|
||||
if (isCreate) {
|
||||
await createFleetServerHost(
|
||||
soClient,
|
||||
{
|
||||
...data,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
{ id, overwrite: true, fromPreconfiguration: true }
|
||||
);
|
||||
} else if (isUpdateWithNewData) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
id,
|
||||
{
|
||||
...data,
|
||||
is_preconfigured: true,
|
||||
},
|
||||
{ fromPreconfiguration: true }
|
||||
);
|
||||
// TODO Bump revision of all policies using that output
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function cleanPreconfiguredFleetServerHosts(
|
||||
soClient: SavedObjectsClientContract,
|
||||
preconfiguredFleetServerHosts: FleetServerHost[]
|
||||
) {
|
||||
const existingFleetServerHosts = await listFleetServerHosts(soClient);
|
||||
const existingPreconfiguredHosts = existingFleetServerHosts.items.filter(
|
||||
(o) => o.is_preconfigured === true
|
||||
);
|
||||
|
||||
for (const existingFleetServerHost of existingPreconfiguredHosts) {
|
||||
const hasBeenDelete = !preconfiguredFleetServerHosts.find(
|
||||
({ id }) => existingFleetServerHost.id === id
|
||||
);
|
||||
if (!hasBeenDelete) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existingFleetServerHost.is_default) {
|
||||
await updateFleetServerHost(
|
||||
soClient,
|
||||
existingFleetServerHost.id,
|
||||
{ is_preconfigured: false },
|
||||
{
|
||||
fromPreconfiguration: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await deleteFleetServerHost(soClient, existingFleetServerHost.id, {
|
||||
fromPreconfiguration: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigFleetServerHosts(config?: FleetConfigType) {
|
||||
return config?.agents?.fleet_server?.hosts && config.agents.fleet_server.hosts.length > 0
|
||||
? config?.agents?.fleet_server?.hosts
|
||||
: undefined;
|
||||
}
|
|
@ -44,6 +44,11 @@ import { upgradeManagedPackagePolicies } from './managed_package_policies';
|
|||
import { getBundledPackages } from './epm/packages';
|
||||
import { upgradePackageInstallVersion } from './setup/upgrade_package_install_version';
|
||||
import { upgradeAgentPolicySchemaVersion } from './setup/upgrade_agent_policy_schema_version';
|
||||
import { migrateSettingsToFleetServerHost } from './fleet_server_host';
|
||||
import {
|
||||
ensurePreconfiguredFleetServerHosts,
|
||||
getPreconfiguredFleetServerHostFromConfig,
|
||||
} from './preconfiguration/fleet_server_host';
|
||||
|
||||
export interface SetupStatus {
|
||||
isInitialized: boolean;
|
||||
|
@ -70,25 +75,32 @@ async function createSetupSideEffects(
|
|||
|
||||
const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } =
|
||||
appContextService.getConfig() ?? {};
|
||||
|
||||
const policies = policiesOrUndefined ?? [];
|
||||
let packages = packagesOrUndefined ?? [];
|
||||
|
||||
logger.debug('Setting Fleet server config');
|
||||
await migrateSettingsToFleetServerHost(soClient);
|
||||
logger.debug('Setting up Fleet download source');
|
||||
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
|
||||
|
||||
logger.debug('Setting up Fleet outputs');
|
||||
|
||||
await ensurePreconfiguredFleetServerHosts(
|
||||
soClient,
|
||||
getPreconfiguredFleetServerHostFromConfig(appContextService.getConfig())
|
||||
);
|
||||
await Promise.all([
|
||||
ensurePreconfiguredOutputs(
|
||||
soClient,
|
||||
esClient,
|
||||
getPreconfiguredOutputFromConfig(appContextService.getConfig())
|
||||
),
|
||||
|
||||
settingsService.settingsSetup(soClient),
|
||||
]);
|
||||
|
||||
const defaultOutput = await outputService.ensureDefaultOutput(soClient);
|
||||
|
||||
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
|
||||
|
||||
if (appContextService.getConfig()?.agentIdVerificationEnabled) {
|
||||
logger.debug('Setting up Fleet Elasticsearch assets');
|
||||
await ensureFleetGlobalEsAssets(soClient, esClient);
|
||||
|
|
|
@ -36,6 +36,9 @@ export type {
|
|||
OutputType,
|
||||
EnrollmentAPIKey,
|
||||
EnrollmentAPIKeySOAttributes,
|
||||
NewFleetServerHost,
|
||||
FleetServerHost,
|
||||
FleetServerHostSOAttributes,
|
||||
Installation,
|
||||
EpmPackageInstallStatus,
|
||||
InstallationStatus,
|
||||
|
|
|
@ -85,6 +85,16 @@ export const PreconfiguredOutputsSchema = schema.arrayOf(
|
|||
}
|
||||
);
|
||||
|
||||
export const PreconfiguredFleetServerHostsSchema = schema.arrayOf(
|
||||
schema.object({
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
is_default: schema.boolean({ defaultValue: false }),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
);
|
||||
|
||||
export const PreconfiguredAgentPoliciesSchema = schema.arrayOf(
|
||||
schema.object({
|
||||
...AgentPolicyBaseSchema,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
export const PostFleetServerHostRequestSchema = {
|
||||
body: schema.object({
|
||||
id: schema.maybe(schema.string()),
|
||||
name: schema.string(),
|
||||
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
|
||||
is_default: schema.boolean({ defaultValue: false }),
|
||||
}),
|
||||
};
|
||||
|
||||
export const GetOneFleetServerHostRequestSchema = {
|
||||
params: schema.object({ itemId: schema.string() }),
|
||||
};
|
||||
|
||||
export const PutFleetServerHostRequestSchema = {
|
||||
params: schema.object({ itemId: schema.string() }),
|
||||
body: schema.object({
|
||||
name: schema.maybe(schema.string()),
|
||||
host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
|
||||
is_default: schema.maybe(schema.boolean({ defaultValue: false })),
|
||||
}),
|
||||
};
|
||||
|
||||
export const GetAllFleetServerHostRequestSchema = {};
|
|
@ -11,6 +11,7 @@ export * from './agent';
|
|||
export * from './package_policy';
|
||||
export * from './epm';
|
||||
export * from './enrollment_api_key';
|
||||
export * from './fleet_server_policy_config';
|
||||
export * from './output';
|
||||
export * from './preconfiguration';
|
||||
export * from './settings';
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { skipIfNoDockerRegistry } from '../../helpers';
|
||||
import { setupFleetAndAgents } from '../agents/services';
|
||||
|
||||
export default function (providerContext: FtrProviderContext) {
|
||||
const { getService } = providerContext;
|
||||
const supertest = getService('supertest');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
|
||||
describe('fleet_fleet_server_hosts_crud', async function () {
|
||||
skipIfNoDockerRegistry(providerContext);
|
||||
before(async () => {
|
||||
await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
setupFleetAndAgents(providerContext);
|
||||
|
||||
let defaultFleetServerHostId: string;
|
||||
|
||||
before(async function () {
|
||||
await kibanaServer.savedObjects.clean({
|
||||
types: ['fleet-fleet-server-host'],
|
||||
});
|
||||
const { body: defaultRes } = await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
id: 'test-default-123',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Test',
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
defaultFleetServerHostId = defaultRes.item.id;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
|
||||
});
|
||||
|
||||
describe('GET /fleet_server_hosts', () => {
|
||||
it('should list the fleet server hosts', async () => {
|
||||
const { body: res } = await supertest.get(`/api/fleet/fleet_server_hosts`).expect(200);
|
||||
|
||||
expect(res.items.length).to.be(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /fleet_server_hosts/{itemId}', () => {
|
||||
it('should return the requested fleet server host', async () => {
|
||||
const { body: fleetServerHost } = await supertest
|
||||
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fleetServerHost).to.eql({
|
||||
item: {
|
||||
id: 'test-default-123',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
|
||||
is_preconfigured: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return a 404 when retrieving a non existing fleet server host', async function () {
|
||||
await supertest.get(`/api/fleet/fleet_server_hosts/idonotexists`).expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /fleet_server_hosts/{itemId}', () => {
|
||||
it('should allow to update an existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Default updated',
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const {
|
||||
body: { item: fleetServerHost },
|
||||
} = await supertest
|
||||
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(fleetServerHost.name).to.eql('Default updated');
|
||||
});
|
||||
|
||||
it('should return a 404 when updating a non existing fleet server host', async function () {
|
||||
await supertest
|
||||
.put(`/api/fleet/fleet_server_hosts/idonotexists`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'new host1',
|
||||
})
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -58,5 +58,8 @@ export default function ({ loadTestFile, getService }) {
|
|||
|
||||
// Integrations
|
||||
loadTestFile(require.resolve('./integrations'));
|
||||
|
||||
// Fleet server hosts
|
||||
loadTestFile(require.resolve('./fleet_server_hosts/crud'));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue