|
4 | 4 | * you may not use this file except in compliance with the Elastic License. |
5 | 5 | */ |
6 | 6 |
|
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'; |
18 | 8 | 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'; |
21 | 10 | import { ArchiveEntry, getBufferExtractor } from '../registry/extract'; |
| 11 | +import { parseAndVerifyArchive } from './validation'; |
22 | 12 |
|
23 | 13 | export async function loadArchivePackage({ |
24 | 14 | archiveBuffer, |
@@ -74,252 +64,3 @@ export async function unpackArchiveToCache( |
74 | 64 | } |
75 | 65 | return paths; |
76 | 66 | } |
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