[Fleet] Deprecate savedObject from Api responses (#159396)

## Summary
- Replacing `savedObject` in the following endpoints with a new object
`installationInfo` that is more "flattened". `savedObject` is marked as
deprecated but not removed for now as it might be used by other
stakeholders.

  - `GET api/fleet/epm/packages/{pkgName}/{version}`
  - `GET api/fleet/epm/packages`
- Replaced all the usages of `savedObject.attributes` in the frontend
with `installationInfo`


### Testing
- in dev tools, run `GET kbn:/api/fleet/epm/packages` and search for a
package that has `savedObject` in it.
- For instance `fleet-server` or `endpoint`, verify that has both
`savedObject` and `installationInfo`:
```
 {
    name: 'endpoint',
    title: 'Elastic Defend',
    version: '8.8.0',
    release: 'ga',
    description: 'Protect your hosts and cloud workloads with threat prevention, detection, and deep security data visibility.',
    type: 'integration',
    download: '/epr/endpoint/endpoint-8.8.0.zip',
    path: '/package/endpoint/8.8.0',
    icons: [ [Object] ],
    policy_templates: [ [Object] ],
    conditions: { kibana: [Object] },
    owner: { github: 'elastic/security-onboarding-and-lifecycle-mgt' },
    categories: [ 'security', 'edr_xdr' ],
    signature_path: '/epr/endpoint/endpoint-8.8.0.zip.sig',
    id: 'endpoint',
    status: 'installed',
    savedObject: {..},
    installationInfo: {
      created_at: '2023-05-25T10:36:47.450Z',
      updated_at: '2023-05-25T10:37:05.168Z',
      version: '8.8.0',
      installed_kibana: [],
      installed_kibana_space_id: 'default',
      installed_es: [Array],
      package_assets: [Array],
      es_index_patterns: [Object],
      name: 'endpoint',
      install_version: '8.8.0',
      install_status: 'installed',
      install_started_at: '2023-05-25T10:36:47.450Z',
      install_source: 'registry',
      install_format_schema_version: '1.0.0',
      verification_status: 'unknown'
    }
  },
```

- Run `GET kbn:/api/fleet/epm/packages/fleet_server/1.3.0` and
verifythat has both `savedObject` and `installationInfo`:
```
{
  "item": {
    "name": "fleet_server",
    "title": "Fleet Server",
    "version": "1.3.0",
    "release": "ga",
    "description": "Centrally manage Elastic Agents with the Fleet Server integration.",
    "type": "integration",
    "download": "/epr/fleet_server/fleet_server-1.3.0.zip",
    "path": "/package/fleet_server/1.3.0",
    "icons": [
      {
        "src": "/img/logo_fleet_server.svg",
        "path": "/package/fleet_server/1.3.0/img/logo_fleet_server.svg",
        "title": "logo Fleet Server",
        "size": "64x64",
        "type": "image/svg+xml"
      }
    ],
    "conditions": {
      "kibana": {
        "version": "^8.8.0"
      }
    },
    "owner": {
      "github": "elastic/elastic-agent-control-plane"
    },
    "categories": [
      "elastic_stack"
    ],
    "signature_path": "/epr/fleet_server/fleet_server-1.3.0.zip.sig",
    "format_version": "1.0.0",
    "readme": "/package/fleet_server/1.3.0/docs/README.md",
    "license": "basic",
    "assets": {},
    "policy_templates": [
      {
        "name": "fleet_server",
        "title": "Fleet Server",
        "description": "Fleet Server setup",
        "inputs": [
          {
            "type": "fleet-server",
            "vars": [... ],
     },
    "latestVersion": "1.3.0",
    "licensePath": "/package/fleet_server/1.3.0/LICENSE.txt",
    "keepPoliciesUpToDate": false,
    "status": "installed",
    "savedObject": {
      "id": "fleet_server",
      "type": "epm-packages",
      "namespaces": [],
      "updated_at": "2023-05-25T10:32:33.033Z",
      "created_at": "2023-05-25T10:32:32.042Z",
      "version": "WzEyNCwxXQ==",
      "attributes": {
        "installed_kibana": [],
        "installed_kibana_space_id": "default",
        "installed_es": [],
        "package_assets": [...],
        "es_index_patterns": {},
        "name": "fleet_server",
        "version": "1.3.0",
        "install_version": "1.3.0",
        "install_status": "installed",
        "install_started_at": "2023-05-25T10:32:32.042Z",
        "install_source": "registry",
        "install_format_schema_version": "1.0.0",
        "verification_status": "unknown"
      },
      "references": [],
      "managed": false,
      "coreMigrationVersion": "8.8.0",
      "typeMigrationVersion": "8.6.0"
    },
    "installationInfo": {
      "created_at": "2023-05-25T10:32:32.042Z",
      "updated_at": "2023-05-25T10:32:33.033Z",
      "version": "1.3.0",
      "namespaces": [],
      "type": "epm-packages",
      "installed_kibana": [],
      "installed_kibana_space_id": "default",
      "installed_es": [],
      "package_assets": [...],
      "es_index_patterns": {},
      "name": "fleet_server",
      "install_version": "1.3.0",
      "install_status": "installed",
      "install_started_at": "2023-05-25T10:32:32.042Z",
      "install_source": "registry",
      "install_format_schema_version": "1.0.0",
      "verification_status": "unknown"
    }
  }
}
```



### Checklist

- [ ]
[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

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cristina Amico 2023-06-13 14:57:44 +02:00 committed by GitHub
parent ad2593b7b4
commit 7a3ce25d2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 514 additions and 167 deletions

View file

@ -5740,6 +5740,159 @@
"items"
]
},
"kibana_saved_object_type": {
"title": "Kibana saved object asset type",
"type": "string",
"enum": [
"dashboard",
"visualization",
"search",
"index-pattern",
"map",
"lens",
"ml-module",
"security-rule",
"csp_rule_template"
]
},
"elasticsearch_asset_type": {
"title": "Elasticsearch asset type",
"type": "string",
"enum": [
"component_template",
"ingest_pipeline",
"index_template",
"ilm_policy",
"transform",
"data_stream_ilm_policy"
]
},
"installation_info": {
"title": "Installation info object",
"type": "object",
"properties": {
"type": {
"type": "string"
},
"created_at": {
"type": "string"
},
"updated_at": {
"type": "string"
},
"namespaces": {
"type": "array",
"items": {
"type": "string"
}
},
"installed_kibana": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/kibana_saved_object_type"
}
}
},
"installed_es": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"deferred": {
"type": "boolean"
},
"type": {
"$ref": "#/components/schemas/elasticsearch_asset_type"
}
}
},
"name": {
"type": "string"
},
"version": {
"type": "string"
},
"install_status": {
"type": "string",
"enum": [
"installed",
"installing",
"install_failed"
]
},
"install_source": {
"type": "string",
"enum": [
"registry",
"upload",
"bundled"
]
},
"install_kibana_space_id": {
"type": "string"
},
"install_format_schema_version": {
"type": "string"
},
"verification_status": {
"type": "string",
"enum": [
"verified",
"unverified",
"unknown"
]
},
"verification_key_id": {
"type": "string",
"nullable": true
},
"experimental_data_stream_features": {
"type": "array",
"properties": {
"data_stream": {
"type": "string"
},
"features": {
"type": "object",
"properties": {
"synthetic_source": {
"type": "boolean",
"nullable": true
},
"tsdb": {
"type": "boolean",
"nullable": true
},
"doc_value_only_numeric": {
"type": "boolean",
"nullable": true
},
"doc_value_only_other": {
"type": "boolean",
"nullable": true
}
}
}
}
}
},
"required": [
"installed_kibana",
"installed_es",
"name",
"version",
"install_status",
"install_version",
"install_started_at",
"install_source",
"verification_status"
]
},
"search_result": {
"title": "Search result",
"type": "object",
@ -5771,6 +5924,9 @@
"status": {
"type": "string"
},
"installationInfo": {
"$ref": "#/components/schemas/installation_info"
},
"savedObject": {
"type": "object",
"deprecated": true
@ -5810,33 +5966,6 @@
"items"
]
},
"kibana_saved_object_type": {
"title": "Kibana saved object asset type",
"type": "string",
"enum": [
"dashboard",
"visualization",
"search",
"index-pattern",
"map",
"lens",
"ml-module",
"security-rule",
"csp_rule_template"
]
},
"elasticsearch_asset_type": {
"title": "Elasticsearch asset type",
"type": "string",
"enum": [
"component_template",
"ingest_pipeline",
"index_template",
"ilm_policy",
"transform",
"data_stream_ilm_policy"
]
},
"bulk_install_packages_response": {
"title": "Bulk install packages response",
"type": "object",

View file

@ -3602,6 +3602,118 @@ components:
- count
required:
- items
kibana_saved_object_type:
title: Kibana saved object asset type
type: string
enum:
- dashboard
- visualization
- search
- index-pattern
- map
- lens
- ml-module
- security-rule
- csp_rule_template
elasticsearch_asset_type:
title: Elasticsearch asset type
type: string
enum:
- component_template
- ingest_pipeline
- index_template
- ilm_policy
- transform
- data_stream_ilm_policy
installation_info:
title: Installation info object
type: object
properties:
type:
type: string
created_at:
type: string
updated_at:
type: string
namespaces:
type: array
items:
type: string
installed_kibana:
type: object
properties:
id:
type: string
type:
$ref: '#/components/schemas/kibana_saved_object_type'
installed_es:
type: object
properties:
id:
type: string
deferred:
type: boolean
type:
$ref: '#/components/schemas/elasticsearch_asset_type'
name:
type: string
version:
type: string
install_status:
type: string
enum:
- installed
- installing
- install_failed
install_source:
type: string
enum:
- registry
- upload
- bundled
install_kibana_space_id:
type: string
install_format_schema_version:
type: string
verification_status:
type: string
enum:
- verified
- unverified
- unknown
verification_key_id:
type: string
nullable: true
experimental_data_stream_features:
type: array
properties:
data_stream:
type: string
features:
type: object
properties:
synthetic_source:
type: boolean
nullable: true
tsdb:
type: boolean
nullable: true
doc_value_only_numeric:
type: boolean
nullable: true
doc_value_only_other:
type: boolean
nullable: true
required:
- installed_kibana
- installed_es
- name
- version
- install_status
- install_version
- install_started_at
- install_source
- verification_status
search_result:
title: Search result
type: object
@ -3624,6 +3736,8 @@ components:
type: string
status:
type: string
installationInfo:
$ref: '#/components/schemas/installation_info'
savedObject:
type: object
deprecated: true
@ -3652,29 +3766,6 @@ components:
$ref: '#/components/schemas/search_result'
required:
- items
kibana_saved_object_type:
title: Kibana saved object asset type
type: string
enum:
- dashboard
- visualization
- search
- index-pattern
- map
- lens
- ml-module
- security-rule
- csp_rule_template
elasticsearch_asset_type:
title: Elasticsearch asset type
type: string
enum:
- component_template
- ingest_pipeline
- index_template
- ilm_policy
- transform
- data_stream_ilm_policy
bulk_install_packages_response:
title: Bulk install packages response
type: object

View file

@ -0,0 +1,88 @@
title: Installation info object
type: object
properties:
type:
type: string
created_at:
type: string
updated_at:
type: string
namespaces:
type: array
items:
type: string
installed_kibana:
type: object
properties:
id:
type: string
type:
$ref: ./kibana_saved_object_type.yaml
installed_es:
type: object
properties:
id:
type: string
deferred:
type: boolean
type:
$ref: ./elasticsearch_asset_type.yaml
name:
type: string
version:
type: string
install_status:
type: string
enum:
- installed
- installing
- install_failed
install_source:
type: string
enum:
- registry
- upload
- bundled
install_kibana_space_id:
type: string
install_format_schema_version:
type: string
verification_status:
type: string
enum:
- verified
- unverified
- unknown
verification_key_id:
type: string
nullable: true
experimental_data_stream_features:
type: array
properties:
data_stream:
type: string
features:
type: object
properties:
synthetic_source:
type: boolean
nullable: true
tsdb:
type: boolean
nullable: true
doc_value_only_numeric:
type: boolean
nullable: true
doc_value_only_other:
type: boolean
nullable: true
required:
- installed_kibana
- installed_es
- name
- version
- install_status
- install_version
- install_started_at
- install_source
- verification_status

View file

@ -19,6 +19,8 @@ properties:
type: string
status:
type: string
installationInfo:
$ref: ./installation_info.yaml
savedObject:
type: object
deprecated: true

View file

@ -365,18 +365,16 @@ describe('Fleet - packageToPackagePolicy', () => {
packageToPackagePolicy(
{
...mockPackage,
savedObject: {
attributes: {
experimental_data_stream_features: [
{
data_stream: 'metrics-test.testdataset',
features: {
synthetic_source: true,
tsdb: true,
},
installationInfo: {
experimental_data_stream_features: [
{
data_stream: 'metrics-test.testdataset',
features: {
synthetic_source: true,
tsdb: true,
},
],
},
},
],
} as any,
},
'1'

View file

@ -206,8 +206,8 @@ export const packageToPackagePolicy = (
integrationToEnable?: string
): NewPackagePolicy => {
const experimentalDataStreamFeatures =
'savedObject' in packageInfo
? packageInfo.savedObject?.attributes?.experimental_data_stream_features
'installationInfo' in packageInfo
? packageInfo.installationInfo?.experimental_data_stream_features
: undefined;
const packagePolicy: NewPackagePolicy = {

View file

@ -421,7 +421,7 @@ export interface RegistryVarsEntry {
// Deprecated as part of the removing public references to saved object schemas
// See https://github.com/elastic/kibana/issues/149098
/**
* @deprecated
* @deprecated replaced with installationInfo
*/
export interface InstallableSavedObject {
type: string;
@ -434,6 +434,21 @@ export interface InstallableSavedObject {
coreMigrationVersion?: string;
namespaces?: string[];
}
export type InstallationInfo = {
type: string;
created_at?: string;
updated_at?: string;
namespaces?: string[];
} & Omit<
Installation,
| 'package_assets'
| 'es_index_patterns'
| 'install_version'
| 'install_started_at'
| 'keep_policies_up_to_date'
| 'internal'
| 'removable'
>;
// Deprecated as part of the removing public references to saved object schemas
// See https://github.com/elastic/kibana/issues/149098
@ -462,9 +477,12 @@ type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, key
// Managers public HTTP response types
export type PackageList = PackageListItem[];
// Remove savedObject when addressing the deprecation
export type PackageListItem = Installable<RegistrySearchResult> & {
id: string;
integration?: string;
installationInfo?: InstallationInfo;
savedObject?: InstallableSavedObject;
};
export type PackagesGroupedByStatus = Record<ValueOf<InstallationStatus>, PackageList>;
@ -504,14 +522,11 @@ export interface Installation {
install_format_schema_version?: string;
verification_status: PackageVerificationStatus;
verification_key_id?: string | null;
// TypeScript doesn't like using the `ExperimentalDataStreamFeature` type defined above here
experimental_data_stream_features?: Array<{
data_stream: string;
features: Partial<Record<ExperimentalIndexingFeature, boolean>>;
}>;
experimental_data_stream_features?: ExperimentalDataStreamFeature[];
internal?: boolean;
removable?: boolean;
}
export interface PackageUsageStats {
agent_policy_count: number;
}
@ -530,11 +545,13 @@ export type InstallStatusExcluded<T = {}> = T & {
export type InstalledRegistry<T = {}> = T & {
status: InstallationStatus['Installed'];
savedObject?: InstallableSavedObject;
installationInfo?: InstallationInfo;
};
export type Installing<T = {}> = T & {
status: InstallationStatus['Installing'];
savedObject?: InstallableSavedObject;
installationInfo?: InstallationInfo;
};
export type NotInstalled<T = {}> = T & {

View file

@ -37,8 +37,8 @@ describe('Install unverified package assets', () => {
// save mocking out the whole package response, but make it so that fleet server is always uninstalled
cy.intercept('GET', '/api/fleet/epm/packages/fleet_server*', (req) => {
req.continue((res) => {
if (res.body?.item?.savedObject) {
delete res.body.item.savedObject;
if (res.body?.item?.installationInfo) {
delete res.body.item.installationInfo;
}
if (res.body?.item?.status) {
res.body.item.status = 'not_installed';

View file

@ -22,7 +22,7 @@ describe('Add Integration - Mock API', () => {
name: 'apache',
id: 'apache',
version: newVersion,
savedObject: { attributes: { version: oldVersion } },
installationInfo: { version: oldVersion },
status: 'installed',
},
],
@ -35,7 +35,7 @@ describe('Add Integration - Mock API', () => {
latestVersion: newVersion,
status: 'installed',
assets: [],
savedObject: { attributes: { version: oldVersion } },
installationInfo: { version: oldVersion },
},
});
cy.intercept('/api/fleet/epm/packages/apache/stats', { response: { agent_policy_count: 1 } });
@ -106,7 +106,7 @@ describe('Add Integration - Mock API', () => {
latestVersion: newVersion,
status: 'installed',
assets: [],
savedObject: { attributes: { version: newVersion } },
installationInfo: { version: newVersion },
},
}).as('updatePackage');
cy.getBySel(UPDATE_PACKAGE_BTN).click();
@ -123,7 +123,7 @@ describe('Add Integration - Mock API', () => {
latestVersion: newVersion,
status: 'installed',
assets: [],
savedObject: { attributes: { version: newVersion } },
installationInfo: { version: newVersion },
},
});
cy.intercept('/api/fleet/epm/packages/apache/stats', { response: { agent_policy_count: 1 } });

View file

@ -62,10 +62,10 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
useEffect(() => {
const fetchAssetSavedObjects = async () => {
if ('savedObject' in packageInfo) {
if ('installationInfo' in packageInfo) {
if (spaces) {
const { id: spaceId } = await spaces.getActiveSpace();
const assetInstallSpaceId = packageInfo.savedObject?.attributes.installed_kibana_space_id;
const assetInstallSpaceId = packageInfo.installationInfo?.installed_kibana_space_id;
// if assets are installed in a different space no need to attempt to load them.
if (assetInstallSpaceId && assetInstallSpaceId !== spaceId) {
@ -75,25 +75,26 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
}
}
const packageAttributes = packageInfo.savedObject?.attributes;
const pkgInstallationInfo = packageInfo.installationInfo;
if (
packageAttributes?.installed_es &&
Array.isArray(packageAttributes.installed_es) &&
packageAttributes.installed_es.length > 0
pkgInstallationInfo?.installed_es &&
Array.isArray(pkgInstallationInfo.installed_es) &&
pkgInstallationInfo.installed_es.length > 0
) {
const deferredAssets = packageAttributes.installed_es.filter(
const deferredAssets = pkgInstallationInfo.installed_es.filter(
(asset) => asset.deferred === true
);
setDeferredInstallations(deferredAssets);
}
const authorizedTransforms = (packageAttributes?.installed_es || []).filter(
const authorizedTransforms = (pkgInstallationInfo?.installed_es || []).filter(
(asset) => asset.type === ElasticsearchAssetType.transform && !asset.deferred
);
if (
authorizedTransforms?.length === 0 &&
(!packageAttributes?.installed_kibana || packageAttributes.installed_kibana.length === 0)
(!pkgInstallationInfo?.installed_kibana ||
pkgInstallationInfo.installed_kibana.length === 0)
) {
setIsLoading(false);
return;
@ -101,7 +102,7 @@ export const AssetsPage = ({ packageInfo }: AssetsPanelProps) => {
try {
const assetIds: AssetSOObject[] = [
...authorizedTransforms,
...(packageAttributes?.installed_kibana || []),
...(pkgInstallationInfo?.installed_kibana || []),
].map(({ id, type }) => ({
id,
type,

View file

@ -175,9 +175,9 @@ export function Detail() {
const updateAvailable =
packageInfo &&
'savedObject' in packageInfo &&
packageInfo.savedObject &&
semverLt(packageInfo.savedObject.attributes.version, packageInfo.latestVersion);
'installationInfo' in packageInfo &&
packageInfo.installationInfo?.version &&
semverLt(packageInfo.installationInfo.version, packageInfo.latestVersion);
const [prereleaseIntegrationsEnabled, setPrereleaseIntegrationsEnabled] = React.useState<
boolean | undefined
@ -256,8 +256,8 @@ export function Detail() {
let installedVersion;
const { name } = packageInfoData.item;
if ('savedObject' in packageInfoResponse) {
installedVersion = packageInfoResponse.savedObject?.attributes.version;
if ('installationInfo' in packageInfoResponse) {
installedVersion = packageInfoResponse.installationInfo?.version;
}
const status: InstallStatus = packageInfoResponse?.status as any;
if (name) {

View file

@ -455,9 +455,9 @@ export const SettingsPage: React.FC<Props> = memo(({ packageInfo, theme$ }: Prop
<ReinstallButton
{...packageInfo}
installSource={
'savedObject' in packageInfo &&
packageInfo.savedObject?.attributes.install_source
? packageInfo.savedObject.attributes.install_source
'installationInfo' in packageInfo &&
packageInfo.installationInfo?.install_source
? packageInfo.installationInfo.install_source
: ''
}
/>

View file

@ -101,8 +101,8 @@ export const mapToCard = ({
: item.uiExternalLink || getAbsolutePath(item.uiInternalPath);
} else {
let urlVersion = item.version;
if ('savedObject' in item && item?.savedObject?.attributes.version) {
urlVersion = item.savedObject.attributes.version || item.version;
if (item?.installationInfo?.version) {
urlVersion = item.installationInfo.version || item.version;
isUnverified = isPackageUnverified(item, packageVerificationKeyId);
isUpdateAvailable = isPackageUpdatable(item);
@ -151,9 +151,8 @@ export const EPMHomePage: React.FC = () => {
const unverifiedPackageCount = installedPackages.filter(
(pkg) =>
'savedObject' in pkg &&
pkg.savedObject?.attributes.verification_status &&
pkg.savedObject.attributes.verification_status === 'unverified'
pkg.installationInfo?.verification_status &&
pkg.installationInfo.verification_status === 'unverified'
).length;
const upgradeablePackageCount = installedPackages.filter(isPackageUpdatable).length;

View file

@ -171,8 +171,7 @@ export const InstalledPackages: React.FC<{
() =>
installedPackages.filter(
(item) =>
item.savedObject?.attributes.version &&
semverLt(item.savedObject.attributes.version, item.version)
item?.installationInfo?.version && semverLt(item.installationInfo.version, item.version)
),
[installedPackages]
);

View file

@ -24,11 +24,9 @@ describe('useIsPackagePolicyUpgradable', () => {
items: [
{
status: 'installed',
savedObject: {
attributes: {
name: 'test',
version: '1.0.0',
},
installationInfo: {
name: 'test',
version: '1.0.0',
},
},
],

View file

@ -31,11 +31,11 @@ export const useIsPackagePolicyUpgradable = () => {
const { name, version } = pkgPolicy.package;
const installedPackage = allInstalledPackages.find(
(installedPkg) =>
'savedObject' in installedPkg && installedPkg.savedObject?.attributes.name === name
'installationInfo' in installedPkg && installedPkg.installationInfo?.name === name
);
if (
installedPackage?.savedObject?.attributes.version &&
semverLt(version, installedPackage.savedObject.attributes.version)
installedPackage?.installationInfo?.version &&
semverLt(version, installedPackage.installationInfo.version)
) {
return true;
}

View file

@ -44,9 +44,9 @@ export const usePackageInstallationsQuery = () => {
() =>
allInstalledPackages.filter(
(item) =>
'savedObject' in item &&
item.savedObject?.attributes.version &&
semverLt(item.savedObject.attributes.version, item.version)
'installationInfo' in item &&
item.installationInfo?.version &&
semverLt(item.installationInfo.version, item.version)
),
[allInstalledPackages]
);
@ -59,16 +59,16 @@ export const usePackageInstallationsQuery = () => {
const { name, version } = pkgPolicy.package;
const installedPackage = allInstalledPackages.find(
(installedPkg) =>
'savedObject' in installedPkg && installedPkg?.savedObject?.attributes.name === name
'installationInfo' in installedPkg && installedPkg?.installationInfo?.name === name
);
if (
installedPackage &&
'savedObject' in installedPackage &&
installedPackage?.savedObject?.attributes?.version &&
semverLt(version, installedPackage.savedObject.attributes.version)
'installationInfo' in installedPackage &&
installedPackage?.installationInfo?.version &&
semverLt(version, installedPackage.installationInfo.version)
) {
const packageData = result.get(name) ?? {
currentVersion: installedPackage.savedObject.attributes.version,
currentVersion: installedPackage.installationInfo.version,
policiesToUpgrade: [],
};
packageData.policiesToUpgrade.push({

View file

@ -32,7 +32,7 @@ const testResponse: GetPackagesResponse['items'] = [
name: 'test',
path: 'test',
release: 'experimental',
savedObject: {} as any,
installationInfo: {} as any,
status: 'installed',
title: 'test',
version: 'test',

View file

@ -32,23 +32,16 @@ const createPackage = ({
policy_templates: [],
// @ts-ignore
assets: {},
savedObject: {
id: '1234',
installationInfo: {
type: 'epm-package',
references: [],
attributes: {
installed_kibana: [],
installed_es: installedEs ?? [],
es_index_patterns: {},
name: 'test-package',
version: '0.0.1',
install_status: 'installed',
install_version: '0.0.1',
install_started_at: new Date().toString(),
install_source: 'registry',
verification_status: 'verified',
verification_key_id: '',
},
installed_kibana: [],
installed_es: installedEs ?? [],
name: 'test-package',
version: '0.0.1',
install_status: 'installed',
install_source: 'registry',
verification_status: 'verified',
verification_key_id: '',
},
});
@ -61,10 +54,10 @@ describe('isPackageUnverified', () => {
} as ReturnType<typeof ExperimentalFeaturesService['get']>);
});
it('Should return false for a package with no saved object', () => {
it('Should return false for a package with no installationInfo', () => {
const noSoPkg = createPackage();
// @ts-ignore we know pkg has savedObject but ts doesn't
delete noSoPkg.savedObject;
// @ts-ignore we know pkg has installationInfo but ts doesn't
delete noSoPkg.installationInfo;
expect(hasDeferredInstallations(noSoPkg)).toEqual(false);
});
@ -75,7 +68,7 @@ describe('isPackageUnverified', () => {
{ id: '', type: ElasticsearchAssetType.transform, deferred: true },
],
});
// @ts-ignore we know pkg has savedObject but ts doesn't
expect(hasDeferredInstallations(pkgWithDeferredInstallations)).toEqual(true);
});

View file

@ -10,8 +10,8 @@ import type { PackageInfo, PackageListItem } from '../../common';
export const getDeferredInstallationsCnt = (pkg?: PackageInfo | PackageListItem | null): number => {
if (!pkg) return 0;
return pkg && 'savedObject' in pkg && pkg.savedObject
? pkg.savedObject.attributes?.installed_es?.filter((d) => d.deferred).length
return pkg && 'installationInfo' in pkg && pkg.installationInfo
? pkg.installationInfo.installed_es?.filter((d) => d.deferred).length
: 0;
};

View file

@ -9,6 +9,6 @@ import semverLt from 'semver/functions/lt';
import type { PackageListItem } from '../types';
export const isPackageUpdatable = (pkg: PackageListItem): boolean =>
'savedObject' in pkg && pkg.savedObject?.attributes.version
? semverLt(pkg.savedObject.attributes.version, pkg.version)
'installationInfo' in pkg && pkg.installationInfo?.version
? semverLt(pkg.installationInfo?.version, pkg.version)
: false;

View file

@ -30,23 +30,16 @@ const createPackage = ({
policy_templates: [],
// @ts-ignore
assets: {},
savedObject: {
id: '1234',
installationInfo: {
type: 'epm-package',
references: [],
attributes: {
installed_kibana: [],
installed_es: [],
es_index_patterns: {},
name: 'test-package',
version: '0.0.1',
install_status: 'installed',
install_version: '0.0.1',
install_started_at: new Date().toString(),
install_source: 'registry',
verification_status: verificationStatus,
...(verificationKeyId && { verification_key_id: verificationKeyId }),
},
installed_kibana: [],
installed_es: [],
name: 'test-package',
version: '0.0.1',
install_status: 'installed',
install_source: 'registry',
verification_status: verificationStatus,
...(verificationKeyId && { verification_key_id: verificationKeyId }),
},
});
@ -61,8 +54,8 @@ describe('isPackageUnverified', () => {
it('Should return false for a package with no saved object', () => {
const noSoPkg = createPackage();
// @ts-ignore we know pkg has savedObject but ts doesn't
delete noSoPkg.savedObject;
// @ts-ignore we know pkg has installationInfo but ts doesn't
delete noSoPkg.installationInfo;
expect(isPackageUnverified(noSoPkg)).toEqual(false);
});
it('Should return false for an unverified package', () => {
@ -100,8 +93,8 @@ describe('isPackageUnverified', () => {
});
it('Should return false for a package with no saved object', () => {
const noSoPkg = createPackage();
// @ts-ignore we know pkg has savedObject but ts doesn't
delete noSoPkg.savedObject;
// @ts-ignore we know pkg has installationInfo but ts doesn't
delete noSoPkg.installationInfo;
expect(isPackageUnverified(noSoPkg)).toEqual(false);
});
it('Should return false for a verified package', () => {

View file

@ -17,10 +17,10 @@ export function isPackageUnverified(
pkg: PackageInfo | PackageListItem,
packageVerificationKeyId?: string
) {
if (!('savedObject' in pkg) || !pkg.savedObject?.attributes) return false;
if (!('installationInfo' in pkg) || !pkg.installationInfo) return false;
const { verification_status: verificationStatus, verification_key_id: verificationKeyId } =
pkg?.savedObject?.attributes;
pkg?.installationInfo;
const { packageVerification: isPackageVerificationEnabled } = ExperimentalFeaturesService.get();
const isKeyOutdated = !!verificationKeyId && verificationKeyId !== packageVerificationKeyId;

View file

@ -12,6 +12,8 @@ import mime from 'mime-types';
import semverValid from 'semver/functions/valid';
import type { ResponseHeaders, KnownHeaders, HttpResponseOptions } from '@kbn/core/server';
import { pick } from 'lodash';
import { HTTPAuthorizationHeader } from '../../../common/http_authorization_header';
import { generateTransformSecondaryAuthHeaders } from '../../services/api_keys/transform_api_keys';
import { handleTransformReauthorizeAndStart } from '../../services/epm/elasticsearch/transform/reauthorize';
@ -72,7 +74,14 @@ import { getAsset } from '../../services/epm/archive/storage';
import { getPackageUsageStats } from '../../services/epm/packages/get';
import { updatePackage } from '../../services/epm/packages/update';
import { getGpgKeyIdOrUndefined } from '../../services/epm/packages/package_verification';
import type { ReauthorizeTransformRequestSchema, SimpleSOAssetAttributes } from '../../types';
import type {
ReauthorizeTransformRequestSchema,
SimpleSOAssetAttributes,
PackageListItem,
PackageList,
PackageInfo,
InstallationInfo,
} from '../../types';
import type { KibanaSavedObjectType, ElasticsearchAssetType } from '../../../common/types/models';
import { getDataStreams } from '../../services/epm/data_streams';
import { allowedAssetTypesLookup } from '../../../common/constants';
@ -109,9 +118,9 @@ export const getListHandler: FleetRequestHandler<
savedObjectsClient,
...request.query,
});
const flattenedRes = res.map((pkg) => soToInstallationInfo(pkg)) as PackageList;
const body: GetPackagesResponse = {
items: res,
items: flattenedRes,
response: res,
};
return response.ok({
@ -291,9 +300,10 @@ export const getInfoHandler: FleetRequestHandler<
ignoreUnverified,
prerelease,
});
const flattenedRes = soToInstallationInfo(res) as PackageInfo;
const body: GetInfoResponse = {
item: res,
item: flattenedRes,
};
return response.ok({ body });
} catch (error) {
@ -594,3 +604,29 @@ export const reauthorizeTransformsHandler: FleetRequestHandler<
return defaultFleetErrorHandler({ error, response });
}
};
// Don't expose the whole SO in the API response, only selected fields
const soToInstallationInfo = (pkg: PackageListItem | PackageInfo) => {
if ('savedObject' in pkg && pkg.savedObject?.attributes) {
const { attributes } = pkg.savedObject;
const installationInfo: InstallationInfo = {
...pick(pkg.savedObject, ['created_at', 'updated_at', 'namespaces', 'type']),
installed_kibana: attributes.installed_kibana,
installed_kibana_space_id: attributes.installed_kibana_space_id,
installed_es: attributes.installed_es,
install_status: attributes.install_status,
install_source: attributes.install_source,
name: attributes.name,
version: attributes.version,
verification_status: attributes.verification_status,
verification_key_id: attributes.verification_key_id,
experimental_data_stream_features: attributes.experimental_data_stream_features,
};
return {
// When savedObject gets removed, replace `pkg` with `...omit(pkg, 'savedObject')`
...pkg,
installationInfo,
};
}
return pkg;
};

View file

@ -86,6 +86,9 @@ export type {
SecretElasticDoc,
VarSecretReference,
PolicySecretReference,
PackageListItem,
PackageList,
InstallationInfo,
} from '../../common/types';
export { ElasticsearchAssetType, KibanaAssetType, KibanaSavedObjectType } from '../../common/types';
export { dataTypes } from '../../common/constants';