Skip to content

Commit 4dea063

Browse files
author
John Schulz
authored
Move parseAndVerify* functions to validation.ts (#82845) (#82936)
## Summary Basic cut-and-paste of `parseAndVerify*` functions from `archive/index.ts` to `archive/validation.ts`. Should be easier to mock now, replace later, etc.
1 parent adf7db6 commit 4dea063

File tree

2 files changed

+265
-262
lines changed

2 files changed

+265
-262
lines changed

x-pack/plugins/ingest_manager/server/services/epm/archive/index.ts

Lines changed: 3 additions & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,11 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7-
import yaml from 'js-yaml';
8-
import { uniq } from 'lodash';
9-
10-
import {
11-
ArchivePackage,
12-
RegistryPolicyTemplate,
13-
RegistryDataStream,
14-
RegistryInput,
15-
RegistryStream,
16-
RegistryVarsEntry,
17-
} from '../../../../common/types';
7+
import { ArchivePackage } from '../../../../common/types';
188
import { PackageInvalidArchiveError, PackageUnsupportedMediaTypeError } from '../../../errors';
19-
import { pkgToPkgKey } from '../registry';
20-
import { cacheGet, cacheSet, setArchiveFilelist } from '../registry/cache';
9+
import { cacheSet, setArchiveFilelist } from '../registry/cache';
2110
import { ArchiveEntry, getBufferExtractor } from '../registry/extract';
11+
import { parseAndVerifyArchive } from './validation';
2212

2313
export async function loadArchivePackage({
2414
archiveBuffer,
@@ -74,252 +64,3 @@ export async function unpackArchiveToCache(
7464
}
7565
return paths;
7666
}
77-
78-
// TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the
79-
// package registry. At some point this should probably be replaced (or enhanced) with verification based on
80-
// https://github.com/elastic/package-spec/
81-
82-
function parseAndVerifyArchive(paths: string[]): ArchivePackage {
83-
// The top-level directory must match pkgName-pkgVersion, and no other top-level files or directories may be present
84-
const toplevelDir = paths[0].split('/')[0];
85-
paths.forEach((path) => {
86-
if (path.split('/')[0] !== toplevelDir) {
87-
throw new PackageInvalidArchiveError('Package contains more than one top-level directory.');
88-
}
89-
});
90-
91-
// The package must contain a manifest file ...
92-
const manifestFile = `${toplevelDir}/manifest.yml`;
93-
const manifestBuffer = cacheGet(manifestFile);
94-
if (!paths.includes(manifestFile) || !manifestBuffer) {
95-
throw new PackageInvalidArchiveError('Package must contain a top-level manifest.yml file.');
96-
}
97-
98-
// ... which must be valid YAML
99-
let manifest;
100-
try {
101-
manifest = yaml.load(manifestBuffer.toString());
102-
} catch (error) {
103-
throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`);
104-
}
105-
106-
// Package name and version from the manifest must match those from the toplevel directory
107-
const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version });
108-
if (toplevelDir !== pkgKey) {
109-
throw new PackageInvalidArchiveError(
110-
`Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}`
111-
);
112-
}
113-
114-
const { name, version, description, type, categories, format_version: formatVersion } = manifest;
115-
// check for mandatory fields
116-
if (!(name && version && description && type && categories && formatVersion)) {
117-
throw new PackageInvalidArchiveError(
118-
'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version'
119-
);
120-
}
121-
122-
const dataStreams = parseAndVerifyDataStreams(paths, name, version);
123-
const policyTemplates = parseAndVerifyPolicyTemplates(manifest);
124-
125-
return {
126-
name,
127-
version,
128-
description,
129-
type,
130-
categories,
131-
format_version: formatVersion,
132-
data_streams: dataStreams,
133-
policy_templates: policyTemplates,
134-
};
135-
}
136-
137-
function parseAndVerifyDataStreams(
138-
paths: string[],
139-
pkgName: string,
140-
pkgVersion: string
141-
): RegistryDataStream[] {
142-
// A data stream is made up of a subdirectory of name-version/data_stream/, containing a manifest.yml
143-
let dataStreamPaths: string[] = [];
144-
const dataStreams: RegistryDataStream[] = [];
145-
const pkgKey = pkgToPkgKey({ name: pkgName, version: pkgVersion });
146-
147-
// pick all paths matching name-version/data_stream/DATASTREAM_PATH/...
148-
// from those, pick all unique data stream paths
149-
paths
150-
.filter((path) => path.startsWith(`${pkgKey}/data_stream/`))
151-
.forEach((path) => {
152-
const parts = path.split('/');
153-
if (parts.length > 2 && parts[2]) dataStreamPaths.push(parts[2]);
154-
});
155-
156-
dataStreamPaths = uniq(dataStreamPaths);
157-
158-
dataStreamPaths.forEach((dataStreamPath) => {
159-
const manifestFile = `${pkgKey}/data_stream/${dataStreamPath}/manifest.yml`;
160-
const manifestBuffer = cacheGet(manifestFile);
161-
if (!paths.includes(manifestFile) || !manifestBuffer) {
162-
throw new PackageInvalidArchiveError(
163-
`No manifest.yml file found for data stream '${dataStreamPath}'`
164-
);
165-
}
166-
167-
let manifest;
168-
try {
169-
manifest = yaml.load(manifestBuffer.toString());
170-
} catch (error) {
171-
throw new PackageInvalidArchiveError(
172-
`Could not parse package manifest for data stream '${dataStreamPath}': ${error}.`
173-
);
174-
}
175-
176-
const {
177-
title: dataStreamTitle,
178-
release,
179-
ingest_pipeline: ingestPipeline,
180-
type,
181-
dataset,
182-
} = manifest;
183-
if (!(dataStreamTitle && release && type)) {
184-
throw new PackageInvalidArchiveError(
185-
`Invalid manifest for data stream '${dataStreamPath}': one or more fields missing of 'title', 'release', 'type'`
186-
);
187-
}
188-
const streams = parseAndVerifyStreams(manifest, dataStreamPath);
189-
190-
// default ingest pipeline name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L26
191-
return dataStreams.push({
192-
dataset: dataset || `${pkgName}.${dataStreamPath}`,
193-
title: dataStreamTitle,
194-
release,
195-
package: pkgName,
196-
ingest_pipeline: ingestPipeline || 'default',
197-
path: dataStreamPath,
198-
type,
199-
streams,
200-
});
201-
});
202-
203-
return dataStreams;
204-
}
205-
206-
function parseAndVerifyStreams(manifest: any, dataStreamPath: string): RegistryStream[] {
207-
const streams: RegistryStream[] = [];
208-
const manifestStreams = manifest.streams;
209-
if (manifestStreams && manifestStreams.length > 0) {
210-
manifestStreams.forEach((manifestStream: any) => {
211-
const {
212-
input,
213-
title: streamTitle,
214-
description,
215-
enabled,
216-
vars: manifestVars,
217-
template_path: templatePath,
218-
} = manifestStream;
219-
if (!(input && streamTitle)) {
220-
throw new PackageInvalidArchiveError(
221-
`Invalid manifest for data stream ${dataStreamPath}: stream is missing one or more fields of: input, title`
222-
);
223-
}
224-
const vars = parseAndVerifyVars(manifestVars, `data stream ${dataStreamPath}`);
225-
// default template path name see https://github.com/elastic/package-registry/blob/master/util/dataset.go#L143
226-
streams.push({
227-
input,
228-
title: streamTitle,
229-
description,
230-
enabled,
231-
vars,
232-
template_path: templatePath || 'stream.yml.hbs',
233-
});
234-
});
235-
}
236-
return streams;
237-
}
238-
239-
function parseAndVerifyVars(manifestVars: any[], location: string): RegistryVarsEntry[] {
240-
const vars: RegistryVarsEntry[] = [];
241-
if (manifestVars && manifestVars.length > 0) {
242-
manifestVars.forEach((manifestVar) => {
243-
const {
244-
name,
245-
title: varTitle,
246-
description,
247-
type,
248-
required,
249-
show_user: showUser,
250-
multi,
251-
def,
252-
os,
253-
} = manifestVar;
254-
if (!(name && type)) {
255-
throw new PackageInvalidArchiveError(
256-
`Invalid var definition for ${location}: one of mandatory fields 'name' and 'type' missing in var: ${manifestVar}`
257-
);
258-
}
259-
vars.push({
260-
name,
261-
title: varTitle,
262-
description,
263-
type,
264-
required,
265-
show_user: showUser,
266-
multi,
267-
default: def,
268-
os,
269-
});
270-
});
271-
}
272-
return vars;
273-
}
274-
275-
function parseAndVerifyPolicyTemplates(manifest: any): RegistryPolicyTemplate[] {
276-
const policyTemplates: RegistryPolicyTemplate[] = [];
277-
const manifestPolicyTemplates = manifest.policy_templates;
278-
if (manifestPolicyTemplates && manifestPolicyTemplates > 0) {
279-
manifestPolicyTemplates.forEach((policyTemplate: any) => {
280-
const { name, title: policyTemplateTitle, description, inputs, multiple } = policyTemplate;
281-
if (!(name && policyTemplateTitle && description && inputs)) {
282-
throw new PackageInvalidArchiveError(
283-
`Invalid top-level manifest: one of mandatory fields 'name', 'title', 'description', 'input' missing in policy template: ${policyTemplate}`
284-
);
285-
}
286-
287-
const parsedInputs = parseAndVerifyInputs(inputs, `config template ${name}`);
288-
289-
// defaults to true if undefined, but may be explicitly set to false.
290-
let parsedMultiple = true;
291-
if (typeof multiple === 'boolean' && multiple === false) parsedMultiple = false;
292-
293-
policyTemplates.push({
294-
name,
295-
title: policyTemplateTitle,
296-
description,
297-
inputs: parsedInputs,
298-
multiple: parsedMultiple,
299-
});
300-
});
301-
}
302-
return policyTemplates;
303-
}
304-
305-
function parseAndVerifyInputs(manifestInputs: any, location: string): RegistryInput[] {
306-
const inputs: RegistryInput[] = [];
307-
if (manifestInputs && manifestInputs.length > 0) {
308-
manifestInputs.forEach((input: any) => {
309-
const { type, title: inputTitle, description, vars } = input;
310-
if (!(type && inputTitle)) {
311-
throw new PackageInvalidArchiveError(
312-
`Invalid top-level manifest: one of mandatory fields 'type', 'title' missing in input: ${input}`
313-
);
314-
}
315-
const parsedVars = parseAndVerifyVars(vars, location);
316-
inputs.push({
317-
type,
318-
title: inputTitle,
319-
description,
320-
vars: parsedVars,
321-
});
322-
});
323-
}
324-
return inputs;
325-
}

0 commit comments

Comments
 (0)