Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet][EPM] Add TS type for package-spec. Clarify EPR relationship with spec #84946

Merged
merged 11 commits into from
Dec 7, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { installationStatuses } from '../constants';
import { PackageInfo } from '../types';
import { packageToPackagePolicy, packageToPackagePolicyInputs } from './package_to_package_policy';

Expand All @@ -14,9 +13,9 @@ describe('Fleet - packageToPackagePolicy', () => {
version: '0.0.0',
latestVersion: '0.0.0',
description: 'description',
type: 'mock',
type: 'integration',
categories: [],
requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } },
conditions: { kibana: { version: '' } },
format_version: '',
download: '',
path: '',
Expand All @@ -29,7 +28,11 @@ describe('Fleet - packageToPackagePolicy', () => {
map: [],
},
},
status: installationStatuses.NotInstalled,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly required for this change. Only because I was in here and it's still type safe but w/one less import

status: 'not_installed',
release: 'experimental',
owner: {
github: 'elastic/fleet',
},
};

describe('packageToPackagePolicyInputs', () => {
Expand Down
88 changes: 41 additions & 47 deletions x-pack/plugins/fleet/common/types/models/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
requiredPackages,
} from '../../constants';
import { ValueOf } from '../../types';
import { PackageSpecManifest, PackageSpecScreenshot } from './package_spec';

export type InstallationStatus = typeof installationStatuses;

Expand Down Expand Up @@ -67,63 +68,63 @@ export enum ElasticsearchAssetType {

export type DataType = typeof dataTypes;

export type RegistryRelease = 'ga' | 'beta' | 'experimental';
export type InstallablePackage = RegistryPackage | ArchivePackage;

// Fields common to packages that come from direct upload and the registry
export interface InstallablePackage {
name: string;
title?: string;
version: string;
release?: RegistryRelease;
readme?: string;
description: string;
type: string;
categories: string[];
screenshots?: RegistryImage[];
icons?: RegistryImage[];
assets?: string[];
internal?: boolean;
format_version: string;
data_streams?: RegistryDataStream[];
policy_templates?: RegistryPolicyTemplate[];
}
export type ArchivePackage = PackageSpecManifest &
// should an uploaded package be able to specify `internal`?
Pick<RegistryPackage, 'readme' | 'assets' | 'data_streams' | 'internal'>;

// Uploaded package archives don't have extra fields
// Linter complaint disabled because this extra type is meant for better code readability
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ArchivePackage extends InstallablePackage {}
export type RegistryPackage = PackageSpecManifest &
Partial<RegistryOverridesToOptional> &
RegistryAdditionalProperties &
RegistryOverridePropertyValue;

// Registry packages do have extra fields.
// cf. type Package struct at https://github.com/elastic/package-registry/blob/master/util/package.go
export interface RegistryPackage extends InstallablePackage {
requirement: RequirementsByServiceName;
type RegistryOverridesToOptional = Pick<PackageSpecManifest, 'title' | 'release'>;

// our current types have `download`, & `path` as required but they're are optional (have `omitempty`) according to
// https://github.com/elastic/package-registry/blob/master/util/package.go#L57
// & https://github.com/elastic/package-registry/blob/master/util/package.go#L80-L81
// However, they are always present in every registry response I checked. Chose to keep types unchanged for now
// and confirm with Registry if they are really optional. Can update types and ~4 places in code later if neccessary
interface RegistryAdditionalProperties {
assets?: string[];
download: string;
path: string;
readme?: string;
internal?: boolean; // Registry addition[0] and EPM uses it[1] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L63 [1]
data_streams?: RegistryDataStream[]; // Registry addition [0] [0]: https://github.com/elastic/package-registry/blob/dd7b021893aa8d66a5a5fde963d8ff2792a9b8fa/util/package.go#L65
}
interface RegistryOverridePropertyValue {
icons?: RegistryImage[];
screenshots?: RegistryImage[];
}

export type RegistryRelease = PackageSpecManifest['release'];
export interface RegistryImage {
src: string;
path: string;
title?: string;
size?: string;
type?: string;
}

export interface RegistryPolicyTemplate {
name: string;
title: string;
description: string;
inputs: RegistryInput[];
inputs?: RegistryInput[];
multiple?: boolean;
}

export interface RegistryInput {
type: string;
title: string;
description?: string;
vars?: RegistryVarsEntry[];
description: string;
template_path?: string;
condition?: string;
vars?: RegistryVarsEntry[];
}

export interface RegistryStream {
input: string;
title: string;
Expand Down Expand Up @@ -152,15 +153,15 @@ export type RegistrySearchResult = Pick<
| 'release'
| 'description'
| 'type'
| 'icons'
| 'internal'
| 'download'
| 'path'
| 'icons'
| 'internal'
| 'data_streams'
| 'policy_templates'
>;

export type ScreenshotItem = RegistryImage;
export type ScreenshotItem = RegistryImage | PackageSpecScreenshot;

// from /categories
// https://github.com/elastic/package-registry/blob/master/docs/api/categories.json
Expand All @@ -172,7 +173,7 @@ export interface CategorySummaryItem {
count: number;
}

export type RequirementsByServiceName = Record<ServiceName, ServiceRequirements>;
export type RequirementsByServiceName = PackageSpecManifest['conditions'];
export interface AssetParts {
pkgkey: string;
dataset?: string;
Expand Down Expand Up @@ -241,31 +242,24 @@ export interface RegistryVarsEntry {

// some properties are optional in Registry responses but required in EPM
// internal until we need them
interface PackageAdditions {
export interface EpmPackageAdditions {
title: string;
latestVersion: string;
assets: AssetsGroupedByServiceByType;
removable?: boolean;
}

type Merge<FirstType, SecondType> = Omit<FirstType, Extract<keyof FirstType, keyof SecondType>> &
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://github.com/sindresorhus/type-fest/#utilities. Can move elsewhere later if needed.

SecondType;

// Managers public HTTP response types
export type PackageList = PackageListItem[];

export type PackageListItem = Installable<RegistrySearchResult>;
export type PackagesGroupedByStatus = Record<ValueOf<InstallationStatus>, PackageList>;
export type PackageInfo =
| Installable<
// remove the properties we'll be altering/replacing from the base type
Omit<RegistryPackage, keyof PackageAdditions> &
// now add our replacement definitions
PackageAdditions
>
| Installable<
// remove the properties we'll be altering/replacing from the base type
Omit<ArchivePackage, keyof PackageAdditions> &
// now add our replacement definitions
PackageAdditions
>;
| Installable<Merge<RegistryPackage, EpmPackageAdditions>>
| Installable<Merge<ArchivePackage, EpmPackageAdditions>>;

export interface Installation extends SavedObjectAttributes {
installed_kibana: KibanaAssetReference[];
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export * from './package_policy';
export * from './data_stream';
export * from './output';
export * from './epm';
export * from './package_spec';
export * from './enrollment_api_key';
export * from './settings';
71 changes: 71 additions & 0 deletions x-pack/plugins/fleet/common/types/models/package_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { RegistryPolicyTemplate } from './epm';

// Based on https://github.com/elastic/package-spec/blob/master/versions/1/manifest.spec.yml#L8
export interface PackageSpecManifest {
jfsiii marked this conversation as resolved.
Show resolved Hide resolved
format_version: string;
name: string;
title: string;
description: string;
version: string;
license?: 'basic';
type?: 'integration';
release: 'experimental' | 'beta' | 'ga';
categories?: Array<PackageSpecCategory | undefined>;
conditions?: PackageSpecConditions;
icons?: PackageSpecIcon[];
screenshots?: PackageSpecScreenshot[];
policy_templates?: RegistryPolicyTemplate[];
owner: { github: string };
}

export type PackageSpecCategory =
| 'aws'
| 'azure'
| 'cloud'
| 'config_management'
| 'containers'
| 'crm'
| 'custom'
| 'datastore'
| 'elastic_stack'
| 'google_cloud'
| 'kubernetes'
| 'languages'
| 'message_queue'
| 'monitoring'
| 'network'
| 'notification'
| 'os_system'
| 'productivity'
| 'security'
| 'support'
| 'ticketing'
| 'version_control'
| 'web';

export type PackageSpecConditions = Record<
'kibana',
{
version: string;
}
>;

export interface PackageSpecIcon {
src: string;
title?: string;
size?: string;
type?: string;
}

export interface PackageSpecScreenshot {
src: string;
title: string;
size?: string;
type?: string;
}
11 changes: 2 additions & 9 deletions x-pack/plugins/fleet/common/types/rest_spec/epm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
AssetReference,
CategorySummaryList,
Installable,
RegistryPackage,
RegistrySearchResult,
PackageInfo,
} from '../models/epm';

Expand All @@ -30,14 +30,7 @@ export interface GetPackagesRequest {
}

export interface GetPackagesResponse {
response: Array<
Installable<
Pick<
RegistryPackage,
'name' | 'title' | 'version' | 'description' | 'type' | 'icons' | 'download' | 'path'
>
>
>;
response: Array<Installable<RegistrySearchResult>>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference existing type instead of duplicating the definition

}

export interface GetLimitedPackagesResponse {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor, EuiTitle } from '@elastic/eui';
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { RequirementsByServiceName, entries } from '../../../types';
import { RequirementsByServiceName, ServiceName, entries } from '../../../types';
import { ServiceTitleMap } from '../constants';
import { Version } from './version';

Expand All @@ -23,6 +23,14 @@ const StyledVersion = styled(Version)`
font-size: ${(props) => props.theme.eui.euiFontSizeXS};
`;

// both our custom `entries` and this type seem unnecessary & duplicate effort but they work for now
type RequirementEntry = [
Extract<ServiceName, 'kibana'>,
{
version: string;
}
];

export function Requirements(props: RequirementsProps) {
const { requirements } = props;

Expand All @@ -40,15 +48,15 @@ export function Requirements(props: RequirementsProps) {
</EuiTitle>
</EuiFlexItem>
</FlexGroup>
{entries(requirements).map(([service, requirement]) => (
{entries(requirements).map(([service, requirement]: RequirementEntry) => (
<EuiFlexGroup key={service}>
<EuiFlexItem grow={true}>
<EuiTextColor color="subdued" key={service}>
{ServiceTitleMap[service]}:
</EuiTextColor>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<StyledVersion version={requirement.versions} />
<StyledVersion version={requirement.version} />
</EuiFlexItem>
</EuiFlexGroup>
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export const AssetTitleMap: Record<AssetType, string> = {
map: 'Map',
};

export const ServiceTitleMap: Record<ServiceName, string> = {
elasticsearch: 'Elasticsearch',
export const ServiceTitleMap: Record<Extract<ServiceName, 'kibana'>, string> = {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Package spec (currently) limits values to kibana

kibana: 'Kibana',
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { useStartServices } from '../../../hooks/use_core';
import { PLUGIN_ID } from '../../../constants';
import { epmRouteService } from '../../../services';
import { RegistryImage } from '../../../../../../common';
import { PackageSpecIcon, PackageSpecScreenshot, RegistryImage } from '../../../../../../common';

const removeRelativePath = (relativePath: string): string =>
new URL(relativePath, 'http://example.com').pathname;
Expand All @@ -15,12 +15,19 @@ export function useLinks() {
const { http } = useStartServices();
return {
toAssets: (path: string) => http.basePath.prepend(`/plugins/${PLUGIN_ID}/assets/${path}`),
toPackageImage: (img: RegistryImage, pkgName: string, pkgVersion: string): string =>
img.src
? http.basePath.prepend(
epmRouteService.getFilePath(`/package/${pkgName}/${pkgVersion}${img.src}`)
)
: http.basePath.prepend(epmRouteService.getFilePath(img.path)),
toPackageImage: (
img: PackageSpecIcon | PackageSpecScreenshot | RegistryImage,
pkgName: string,
pkgVersion: string
): string | undefined => {
const sourcePath = img.src
? `/package/${pkgName}/${pkgVersion}${img.src}`
: 'path' in img && img.path;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path doesn't exist in PackageSpec*

if (sourcePath) {
const filePath = epmRouteService.getFilePath(sourcePath);
return http.basePath.prepend(filePath);
}
},
toRelativeImage: ({
path,
packageName,
Expand Down
Loading