Merge pull request #918 from spotify/freben/annotations
catalog backend: parse but do not store labels + annotations
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
apiVersion: catalog.backstage.io/v1
|
||||
apiVersion: backstage.io/v1beta1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: component3
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
apiVersion: catalog.backstage.io/v1
|
||||
apiVersion: backstage.io/v1beta1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: component1
|
||||
spec:
|
||||
type: service
|
||||
---
|
||||
apiVersion: catalog.backstage.io/v1
|
||||
apiVersion: backstage.io/v1beta1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: component2
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"fs-extra": "^9.0.0",
|
||||
"helmet": "^3.22.0",
|
||||
"knex": "^0.21.1",
|
||||
"lodash": "^4.17.15",
|
||||
"morgan": "^1.10.0",
|
||||
"sqlite3": "^4.2.0",
|
||||
"uuid": "^8.0.0",
|
||||
@@ -33,6 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.1.1-alpha.5",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"@types/uuid": "^7.0.3",
|
||||
"@types/yup": "^0.28.2",
|
||||
"jest-fetch-mock": "^3.0.3",
|
||||
|
||||
@@ -29,7 +29,7 @@ export class StaticItemsCatalog implements ItemsCatalog {
|
||||
}
|
||||
|
||||
async component(name: string): Promise<Component> {
|
||||
const item = this._components.find((i) => i.name === name);
|
||||
const item = this._components.find(i => i.name === name);
|
||||
if (!item) {
|
||||
throw new NotFoundError(`Found no component with name ${name}`);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import Knex from 'knex';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { NotFoundError } from '../../../../packages/backend-common/src/errors';
|
||||
import { NotFoundError } from '@backstage/backend-common';
|
||||
import {
|
||||
AddDatabaseComponent,
|
||||
AddDatabaseLocation,
|
||||
@@ -28,7 +28,7 @@ export class Database {
|
||||
constructor(private readonly database: Knex) {}
|
||||
|
||||
async addOrUpdateComponent(component: AddDatabaseComponent): Promise<void> {
|
||||
await this.database.transaction(async (tx) => {
|
||||
await this.database.transaction(async tx => {
|
||||
// TODO(freben): Currently, several locations can compete for the same component
|
||||
// TODO(freben): If locationId is unset in the input, it won't be overwritten - should we instead replace with null?
|
||||
const count = await tx<DatabaseComponent>('components')
|
||||
@@ -60,7 +60,7 @@ export class Database {
|
||||
}
|
||||
|
||||
async addLocation(location: AddDatabaseLocation): Promise<DatabaseLocation> {
|
||||
return await this.database.transaction<DatabaseLocation>(async (tx) => {
|
||||
return await this.database.transaction<DatabaseLocation>(async tx => {
|
||||
const existingLocation = await tx<DatabaseLocation>('locations')
|
||||
.where({
|
||||
target: location.target,
|
||||
@@ -79,9 +79,9 @@ export class Database {
|
||||
target,
|
||||
});
|
||||
|
||||
return (
|
||||
await tx<DatabaseLocation>('locations').where({ id }).select()
|
||||
)![0];
|
||||
return (await tx<DatabaseLocation>('locations')
|
||||
.where({ id })
|
||||
.select())![0];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ describe('DatabaseManager', () => {
|
||||
} as unknown) as Database;
|
||||
|
||||
const desc: ComponentDescriptor = {
|
||||
apiVersion: 'backstage.io/v1beta1',
|
||||
kind: 'Component',
|
||||
metadata: { name: 'c1' },
|
||||
spec: { type: 'service' },
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as Knex from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<any> {
|
||||
return knex.schema
|
||||
.createTable('locations', (table) => {
|
||||
.createTable('locations', table => {
|
||||
table.comment(
|
||||
'Registered locations that shall be contiuously scanned for catalog item updates',
|
||||
);
|
||||
@@ -29,7 +29,7 @@ export async function up(knex: Knex): Promise<any> {
|
||||
.notNullable()
|
||||
.comment('The actual target of the location');
|
||||
})
|
||||
.createTable('components', (table) => {
|
||||
.createTable('components', table => {
|
||||
table.comment('All components currently stored in the catalog');
|
||||
table.uuid('id').primary().comment('Auto-generated ID of the component');
|
||||
table
|
||||
|
||||
@@ -14,22 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentDescriptorV1Parser } from './descriptors/ComponentDescriptorV1Parser';
|
||||
import { parseDescriptorEnvelope } from './descriptors/DescriptorEnvelope';
|
||||
import { EnvelopeParser } from './descriptors/types';
|
||||
import { DescriptorEnvelopeParser } from './descriptors/DescriptorEnvelopeParser';
|
||||
import { ComponentDescriptorV1beta1Parser } from './descriptors/ComponentDescriptorV1beta1Parser';
|
||||
import { KindParser } from './descriptors/types';
|
||||
import { DescriptorParser, ParserOutput } from './types';
|
||||
import { makeValidator } from '../validation';
|
||||
|
||||
export class DescriptorParsers implements DescriptorParser {
|
||||
static create(): DescriptorParser {
|
||||
return new DescriptorParsers([new ComponentDescriptorV1Parser()]);
|
||||
const validators = makeValidator();
|
||||
return new DescriptorParsers(new DescriptorEnvelopeParser(validators), [
|
||||
new ComponentDescriptorV1beta1Parser(),
|
||||
]);
|
||||
}
|
||||
|
||||
constructor(private readonly parsers: EnvelopeParser[]) {}
|
||||
constructor(
|
||||
private readonly envelopeParser: DescriptorEnvelopeParser,
|
||||
private readonly kindParsers: KindParser[],
|
||||
) {}
|
||||
|
||||
async parse(descriptor: object): Promise<ParserOutput> {
|
||||
const envelope = await parseDescriptorEnvelope(descriptor);
|
||||
|
||||
for (const parser of this.parsers) {
|
||||
const envelope = await this.envelopeParser.parse(descriptor);
|
||||
for (const parser of this.kindParsers) {
|
||||
const parsed = await parser.tryParse(envelope);
|
||||
if (parsed) {
|
||||
return parsed;
|
||||
|
||||
+26
-21
@@ -16,48 +16,53 @@
|
||||
|
||||
import * as yup from 'yup';
|
||||
import { ParserOutput } from '../types';
|
||||
import { DescriptorEnvelope } from './DescriptorEnvelope';
|
||||
import { EnvelopeParser } from './types';
|
||||
import { DescriptorEnvelope } from './DescriptorEnvelopeParser';
|
||||
import { KindParser } from './types';
|
||||
|
||||
export type ComponentDescriptorV1 = {
|
||||
export interface ComponentDescriptorV1beta1 extends DescriptorEnvelope {
|
||||
metadata: {
|
||||
name: string;
|
||||
};
|
||||
spec: {
|
||||
type: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const schema: yup.Schema<ComponentDescriptorV1> = yup.object({
|
||||
metadata: yup.object({
|
||||
name: yup.string().required(),
|
||||
}),
|
||||
spec: yup.object({
|
||||
type: yup.string().required(),
|
||||
}),
|
||||
});
|
||||
export class ComponentDescriptorV1beta1Parser implements KindParser {
|
||||
private schema: yup.Schema<any>;
|
||||
|
||||
constructor() {
|
||||
this.schema = yup.object<Partial<ComponentDescriptorV1beta1>>({
|
||||
metadata: yup
|
||||
.object({
|
||||
name: yup.string().required(),
|
||||
})
|
||||
.required(),
|
||||
spec: yup
|
||||
.object({
|
||||
type: yup.string().required(),
|
||||
})
|
||||
.required(),
|
||||
});
|
||||
}
|
||||
|
||||
export class ComponentDescriptorV1Parser implements EnvelopeParser {
|
||||
async tryParse(
|
||||
envelope: DescriptorEnvelope,
|
||||
): Promise<ParserOutput | undefined> {
|
||||
if (
|
||||
envelope.apiVersion !== 'catalog.backstage.io/v1' ||
|
||||
envelope.apiVersion !== 'backstage.io/v1beta1' ||
|
||||
envelope.kind !== 'Component'
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let component;
|
||||
try {
|
||||
component = await schema.validate(envelope, { strict: true });
|
||||
return {
|
||||
kind: 'Component',
|
||||
component: await this.schema.validate(envelope, { strict: true }),
|
||||
};
|
||||
} catch (e) {
|
||||
throw new Error(`Malformed component, ${e}`);
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'Component',
|
||||
component,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
|
||||
export type DescriptorEnvelope = {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata?: object;
|
||||
spec?: object;
|
||||
};
|
||||
|
||||
// The schema of the envelope that's common to all versions/kinds
|
||||
const descriptorEnvelopeSchema: yup.Schema<DescriptorEnvelope> = yup
|
||||
.object({
|
||||
apiVersion: yup.string().required(),
|
||||
kind: yup.string().required(),
|
||||
metadata: yup.object(),
|
||||
spec: yup.object(),
|
||||
})
|
||||
.noUnknown();
|
||||
|
||||
// Validate some raw structured data as a descriptor envelope
|
||||
export async function parseDescriptorEnvelope(
|
||||
data: object,
|
||||
): Promise<DescriptorEnvelope> {
|
||||
try {
|
||||
return await descriptorEnvelopeSchema.validate(data, { strict: true });
|
||||
} catch (e) {
|
||||
throw new Error(`Malformed envelope, ${e}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import yaml from 'yaml';
|
||||
import { makeValidator } from '../../validation';
|
||||
import { DescriptorEnvelopeParser } from './DescriptorEnvelopeParser';
|
||||
|
||||
describe('DescriptorEnvelopeParser', () => {
|
||||
let data: any;
|
||||
let parser: DescriptorEnvelopeParser;
|
||||
|
||||
beforeEach(() => {
|
||||
data = yaml.parse(`
|
||||
apiVersion: backstage.io/v1beta1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: my-component-yay
|
||||
namespace: the-namespace
|
||||
labels:
|
||||
backstage.io/custom: ValueStuff
|
||||
annotations:
|
||||
example.com/bindings: are-secret
|
||||
spec:
|
||||
custom: stuff
|
||||
`);
|
||||
parser = new DescriptorEnvelopeParser(makeValidator());
|
||||
});
|
||||
|
||||
it('works for the happy path', async () => {
|
||||
await expect(parser.parse(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects missing apiVersion', async () => {
|
||||
delete data.apiVersion;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects wrong root type', async () => {
|
||||
await expect(parser.parse(7)).rejects.toThrow(/object/);
|
||||
});
|
||||
|
||||
it('rejects bad apiVersion', async () => {
|
||||
data.apiVersion = 'a#b';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/apiVersion/);
|
||||
});
|
||||
|
||||
it('rejects missing kind', async () => {
|
||||
delete data.kind;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('rejects bad kind', async () => {
|
||||
data.kind = 'a#b';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/kind/);
|
||||
});
|
||||
|
||||
it('accepts missing metadata', async () => {
|
||||
delete data.medatata;
|
||||
await expect(parser.parse(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects non-object metadata', async () => {
|
||||
data.metadata = 7;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/metadata/);
|
||||
});
|
||||
|
||||
it('accepts missing spec', async () => {
|
||||
delete data.spec;
|
||||
await expect(parser.parse(data)).resolves.toBe(data);
|
||||
});
|
||||
|
||||
it('rejects non-object spec', async () => {
|
||||
data.spec = 7;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/spec/);
|
||||
});
|
||||
|
||||
it('rejects bad name', async () => {
|
||||
data.metadata.name = 7;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/name/);
|
||||
});
|
||||
|
||||
it('rejects bad namespace', async () => {
|
||||
data.metadata.namespace = 7;
|
||||
await expect(parser.parse(data)).rejects.toThrow(/namespace/);
|
||||
});
|
||||
|
||||
it('rejects bad label key', async () => {
|
||||
data.metadata.labels['a#b'] = 'value';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/label.*key/i);
|
||||
});
|
||||
|
||||
it('rejects bad label value', async () => {
|
||||
data.metadata.labels.a = 'a#b';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/label.*value/i);
|
||||
});
|
||||
|
||||
it('rejects bad annotation key', async () => {
|
||||
data.metadata.annotations['a#b'] = 'value';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/annotation.*key/i);
|
||||
});
|
||||
|
||||
it('rejects bad annotation value', async () => {
|
||||
data.metadata.annotations.a = [];
|
||||
await expect(parser.parse(data)).rejects.toThrow(/annotation.*value/i);
|
||||
});
|
||||
|
||||
it('rejects unknown root keys', async () => {
|
||||
data.spec2 = {};
|
||||
await expect(parser.parse(data)).rejects.toThrow(/spec2/i);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in the spec root', async () => {
|
||||
data.spec.apiVersion = 'a/b';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/spec.*apiVersion/i);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in labels', async () => {
|
||||
data.metadata.labels.apiVersion = 'a';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/label.*apiVersion/i);
|
||||
});
|
||||
|
||||
it('rejects reserved keys in annotations', async () => {
|
||||
data.metadata.annotations.apiVersion = 'a';
|
||||
await expect(parser.parse(data)).rejects.toThrow(/annotation.*apiVersion/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as yup from 'yup';
|
||||
import { Validators } from '../../validation';
|
||||
|
||||
/**
|
||||
* The format envelope that's common to all versions/kinds
|
||||
*/
|
||||
export type DescriptorEnvelope = {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
metadata?: {
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
labels?: object;
|
||||
annotations?: object;
|
||||
};
|
||||
spec?: object;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses some raw structured data as a descriptor envelope
|
||||
*/
|
||||
export class DescriptorEnvelopeParser {
|
||||
private schema: yup.Schema<DescriptorEnvelope>;
|
||||
|
||||
constructor(validators: Validators) {
|
||||
const apiVersionSchema = yup
|
||||
.string()
|
||||
.required()
|
||||
.test(
|
||||
'apiVersion',
|
||||
'The apiVersion is not formatted according to schema',
|
||||
validators.isValidApiVersion,
|
||||
);
|
||||
|
||||
const kindSchema = yup
|
||||
.string()
|
||||
.required()
|
||||
.test(
|
||||
'kind',
|
||||
'The kind is not formatted according to schema',
|
||||
validators.isValidKind,
|
||||
);
|
||||
|
||||
const nameSchema = yup
|
||||
.string()
|
||||
.notRequired()
|
||||
.test(
|
||||
'metadata.name',
|
||||
'The name is not formatted according to schema',
|
||||
value => value === undefined || validators.isValidEntityName(value),
|
||||
);
|
||||
|
||||
const namespaceSchema = yup
|
||||
.string()
|
||||
.notRequired()
|
||||
.test(
|
||||
'metadata.namespace',
|
||||
'The namespace is malformed',
|
||||
value => value === undefined || validators.isValidEntityName(value),
|
||||
);
|
||||
|
||||
const labelsSchema = yup
|
||||
.object()
|
||||
.notRequired()
|
||||
.test({
|
||||
name: 'metadata.labels.keys',
|
||||
message: 'Label keys not formatted according to schema',
|
||||
test(value: object) {
|
||||
return (
|
||||
value === undefined ||
|
||||
Object.keys(value).every(validators.isValidLabelKey)
|
||||
);
|
||||
},
|
||||
})
|
||||
.test({
|
||||
name: 'metadata.labels.values',
|
||||
message: 'Label values not formatted according to schema',
|
||||
test(value: object) {
|
||||
return (
|
||||
value === undefined ||
|
||||
Object.values(value).every(validators.isValidLabelValue)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const annotationsSchema = yup
|
||||
.object()
|
||||
.notRequired()
|
||||
.test({
|
||||
name: 'metadata.annotations.keys',
|
||||
message: 'Annotation keys not formatted according to schema',
|
||||
test(value: object) {
|
||||
return (
|
||||
value === undefined ||
|
||||
Object.keys(value).every(validators.isValidAnnotationKey)
|
||||
);
|
||||
},
|
||||
})
|
||||
.test({
|
||||
name: 'metadata.annotations.values',
|
||||
message: 'Annotation values not formatted according to schema',
|
||||
test(value: object) {
|
||||
return (
|
||||
value === undefined ||
|
||||
Object.values(value).every(validators.isValidAnnotationValue)
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const metadataSchema = yup
|
||||
.object({
|
||||
name: nameSchema,
|
||||
namespace: namespaceSchema,
|
||||
labels: labelsSchema,
|
||||
annotations: annotationsSchema,
|
||||
})
|
||||
.notRequired();
|
||||
|
||||
const specSchema = yup.object({}).notRequired();
|
||||
|
||||
this.schema = yup
|
||||
.object({
|
||||
apiVersion: apiVersionSchema,
|
||||
kind: kindSchema,
|
||||
metadata: metadataSchema,
|
||||
spec: specSchema,
|
||||
})
|
||||
.noUnknown();
|
||||
}
|
||||
|
||||
async parse(data: any): Promise<DescriptorEnvelope> {
|
||||
let result: DescriptorEnvelope;
|
||||
try {
|
||||
result = await this.schema.validate(data, { strict: true });
|
||||
} catch (e) {
|
||||
throw new Error(`Malformed envelope, ${e}`);
|
||||
}
|
||||
|
||||
// These are keys with specific semantic meaning in a document, that we do
|
||||
// not want to appear in the root of the spec, or as labels or as
|
||||
// annotations, because they will lead to confusion.
|
||||
const reservedKeys = [
|
||||
'apiVersion',
|
||||
'kind',
|
||||
'name',
|
||||
'namespace',
|
||||
'labels',
|
||||
'annotations',
|
||||
'spec',
|
||||
];
|
||||
for (const key of reservedKeys) {
|
||||
if (result.spec?.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`The spec may not contain the key ${key}, because it has reserved meaning`,
|
||||
);
|
||||
}
|
||||
if (result.metadata?.labels?.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`A label may not have the key ${key}, because it has reserved meaning`,
|
||||
);
|
||||
}
|
||||
if (result.metadata?.annotations?.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`An annotation may not have the key ${key}, because it has reserved meaning`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -15,16 +15,20 @@
|
||||
*/
|
||||
|
||||
import { ParserOutput } from '../types';
|
||||
import { DescriptorEnvelope } from './DescriptorEnvelope';
|
||||
import { DescriptorEnvelope } from './DescriptorEnvelopeParser';
|
||||
|
||||
export type EnvelopeParser = {
|
||||
export type KindParser = {
|
||||
/**
|
||||
* Parses and validates a single envelope into its materialized type.
|
||||
* Parses and validates a single envelope into its materialized kind.
|
||||
*
|
||||
* @param envelope A descriptor envelope
|
||||
* These parsers may assume that the envelope is already validated and
|
||||
* well formed.
|
||||
*
|
||||
* @param envelope A valid descriptor envelope
|
||||
* @returns A materialized type, or undefined if the given version/kind is
|
||||
* not handled by this parser
|
||||
* @throws An Error if the type was not properly formatted
|
||||
* @throws An Error if the type was handled and found to not be properly
|
||||
* formatted
|
||||
*/
|
||||
tryParse(envelope: DescriptorEnvelope): Promise<ParserOutput | undefined>;
|
||||
};
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentDescriptorV1 } from './descriptors/ComponentDescriptorV1Parser';
|
||||
import { ComponentDescriptorV1beta1 } from './descriptors/ComponentDescriptorV1beta1Parser';
|
||||
|
||||
export type ComponentDescriptor = ComponentDescriptorV1;
|
||||
export type ComponentDescriptor = ComponentDescriptorV1beta1;
|
||||
|
||||
export type ParserOutput = {
|
||||
kind: 'Component';
|
||||
|
||||
@@ -22,7 +22,7 @@ const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 3003;
|
||||
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
|
||||
const logger = getRootLogger();
|
||||
|
||||
startStandaloneServer({ port, enableCors, logger }).catch((err) => {
|
||||
startStandaloneServer({ port, enableCors, logger }).catch(err => {
|
||||
logger.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
export function runPeriodically(fn: () => any, delayMs: number): () => void {
|
||||
let cancel: () => void;
|
||||
let cancelled = false;
|
||||
const cancellationPromise = new Promise((resolve) => {
|
||||
const cancellationPromise = new Promise(resolve => {
|
||||
cancel = () => {
|
||||
resolve();
|
||||
cancelled = true;
|
||||
@@ -43,7 +43,7 @@ export function runPeriodically(fn: () => any, delayMs: number): () => void {
|
||||
}
|
||||
|
||||
await Promise.race([
|
||||
new Promise((resolve) => setTimeout(resolve, delayMs)),
|
||||
new Promise(resolve => setTimeout(resolve, delayMs)),
|
||||
cancellationPromise,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommonValidatorFunctions } from './CommonValidatorFunctions';
|
||||
|
||||
describe('CommonValidatorFunctions', () => {
|
||||
describe('isValidPrefixAndOrSuffix', () => {
|
||||
it('only accepts strings', () => {
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
null,
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
7,
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
() => 'hello',
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('only accepts one or two parts', () => {
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a',
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a/b',
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a/b/c',
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('checks the prefix and suffix', () => {
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a/b',
|
||||
'/',
|
||||
() => true,
|
||||
() => true,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a/b',
|
||||
'/',
|
||||
() => false,
|
||||
() => true,
|
||||
),
|
||||
).toBe(false);
|
||||
expect(
|
||||
CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
'a/b',
|
||||
'/',
|
||||
() => true,
|
||||
() => false,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, true],
|
||||
[undefined, false],
|
||||
[1, true],
|
||||
['a', true],
|
||||
[() => 'a', false],
|
||||
[Symbol('a'), false],
|
||||
[[], true],
|
||||
[[1], true],
|
||||
[[undefined], false],
|
||||
[{}, true],
|
||||
[{ a: 1 }, true],
|
||||
[{ a: undefined }, false],
|
||||
] as [any, boolean][])('isJsonSafe', (value, result) => {
|
||||
expect(CommonValidatorFunctions.isJsonSafe(value)).toBe(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, false],
|
||||
[7, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', false],
|
||||
['adam.bertil.caesar', true],
|
||||
['adam.ber-til.caesar', true],
|
||||
['adam.-bertil.caesar', false],
|
||||
['adam.bertil-.caesar', false],
|
||||
['adam/bertil.caesar', false],
|
||||
[`a.${'b'.repeat(63)}.c`, true],
|
||||
[`a.${'b'.repeat(64)}.c`, false],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(61)}`,
|
||||
true,
|
||||
],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(62)}`,
|
||||
false,
|
||||
],
|
||||
])('isValidDnsSubdomain', (value, result) => {
|
||||
expect(CommonValidatorFunctions.isValidDnsSubdomain(value)).toBe(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[null, false],
|
||||
[7, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', false],
|
||||
[`${'a'.repeat(63)}`, true],
|
||||
[`${'a'.repeat(64)}`, false],
|
||||
])('isValidDnsLabel', (value, result) => {
|
||||
expect(CommonValidatorFunctions.isValidDnsLabel(value)).toBe(result);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['', ''],
|
||||
['a', 'a'],
|
||||
['a-b', 'ab'],
|
||||
['-a-b', 'ab'],
|
||||
['a_b', 'ab'],
|
||||
[`${'a'.repeat(6000)}`, `${'a'.repeat(6000)}`],
|
||||
['_:;>!"#€', ''],
|
||||
])('normalizeToLowercaseAlphanum', (value, result) => {
|
||||
expect(CommonValidatorFunctions.normalizeToLowercaseAlphanum(value)).toBe(
|
||||
result,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import lodash from 'lodash';
|
||||
|
||||
/**
|
||||
* Contains various helper validation and normalization functions that can be
|
||||
* composed to form a Validator.
|
||||
*/
|
||||
export class CommonValidatorFunctions {
|
||||
/**
|
||||
* Checks that the value is on the form <suffix> or <prefix><separator><suffix>, and validates
|
||||
* those parts separately.
|
||||
*
|
||||
* @param value The value to check
|
||||
* @param separator The separator between parts
|
||||
* @param isValidPrefix Checks that the part before the separator is valid, if present
|
||||
* @param isValidSuffix Checks that the part after the separator (or the entire value if there is no separator) is valid
|
||||
*/
|
||||
static isValidPrefixAndOrSuffix(
|
||||
value: any,
|
||||
separator: string,
|
||||
isValidPrefix: (value: string) => boolean,
|
||||
isValidSuffix: (value: string) => boolean,
|
||||
): boolean {
|
||||
if (typeof value !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = value.split(separator);
|
||||
if (parts.length === 1) {
|
||||
return isValidSuffix(parts[0]);
|
||||
} else if (parts.length === 2) {
|
||||
return isValidPrefix(parts[0]) && isValidSuffix(parts[1]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the value can be safely transferred as JSON.
|
||||
*
|
||||
* @param value The value to check
|
||||
*/
|
||||
static isJsonSafe(value: any): boolean {
|
||||
try {
|
||||
return lodash.isEqual(value, JSON.parse(JSON.stringify(value)));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the value is a valid DNS subdomain name.
|
||||
*
|
||||
* @param value The value to check
|
||||
* @see https://tools.ietf.org/html/rfc1123
|
||||
*/
|
||||
static isValidDnsSubdomain(value: any): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 253 &&
|
||||
value.split('.').every(CommonValidatorFunctions.isValidDnsLabel)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the value is a valid DNS label.
|
||||
*
|
||||
* @param value The value to check
|
||||
* @see https://tools.ietf.org/html/rfc1123
|
||||
*/
|
||||
static isValidDnsLabel(value: any): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 63 &&
|
||||
/^[a-z0-9]+(\-[a-z0-9]+)*$/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes by keeping only a-z, A-Z, and 0-9; and converts to lowercase.
|
||||
*
|
||||
* @param value The value to normalize
|
||||
*/
|
||||
static normalizeToLowercaseAlphanum(value: string): string {
|
||||
return value
|
||||
.split('')
|
||||
.filter(x => /[a-zA-Z0-9]/.test(x))
|
||||
.join('')
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { KubernetesValidatorFunctions } from './KubernetesValidatorFunctions';
|
||||
|
||||
describe('KubernetesValidatorFunctions', () => {
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a-b', false],
|
||||
['a_b', false],
|
||||
['a.b', false],
|
||||
['a/a', true],
|
||||
['a/aAb5C', true],
|
||||
['a-b.c/v1', true],
|
||||
['a--b.c/v1', false],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
61,
|
||||
)}/v1`,
|
||||
true,
|
||||
],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
62,
|
||||
)}/v1`,
|
||||
false,
|
||||
],
|
||||
[`a/${'a'.repeat(63)}`, true],
|
||||
[`a/${'a'.repeat(64)}`, false],
|
||||
])('isValidApiVersion', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidApiVersion(value)).toBe(matches);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['9AZ', false],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a-b', false],
|
||||
])('isValidKind', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidKind(value)).toBe(matches);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a/b', false],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', true],
|
||||
['a.b', true],
|
||||
])('isValidObjectName', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidObjectName(value)).toBe(matches);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a/b', true],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', true],
|
||||
['a.b', true],
|
||||
['a/a', true],
|
||||
['a-b.c/a', true],
|
||||
['a--b.c/a', false],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
61,
|
||||
)}/a`,
|
||||
true,
|
||||
],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
62,
|
||||
)}/a`,
|
||||
false,
|
||||
],
|
||||
[`a/${'a'.repeat(63)}`, true],
|
||||
[`a/${'a'.repeat(64)}`, false],
|
||||
])('isValidLabelKey', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidLabelKey(value)).toBe(matches);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', true],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a/b', false],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', true],
|
||||
['a.b', true],
|
||||
])('isValidLabelValue', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidLabelValue(value)).toBe(matches);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', false],
|
||||
['a', true],
|
||||
['AZ09', true],
|
||||
['a'.repeat(63), true],
|
||||
['a'.repeat(64), false],
|
||||
['a/b', true],
|
||||
['a-b', true],
|
||||
['-a-b', false],
|
||||
['a-b-', false],
|
||||
['a--b', false],
|
||||
['a_b', true],
|
||||
['a.b', true],
|
||||
['a/a', true],
|
||||
['a-b.c/a', true],
|
||||
['a--b.c/a', false],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
61,
|
||||
)}/a`,
|
||||
true,
|
||||
],
|
||||
[
|
||||
`${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(
|
||||
62,
|
||||
)}/a`,
|
||||
false,
|
||||
],
|
||||
[`a/${'a'.repeat(63)}`, true],
|
||||
[`a/${'a'.repeat(64)}`, false],
|
||||
])('isValidAnnotationKey', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidAnnotationKey(value)).toBe(
|
||||
matches,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[7, false],
|
||||
[null, false],
|
||||
['', true],
|
||||
['a', true],
|
||||
['/'.repeat(6000), true],
|
||||
])('isValidAnnotationValue', (value, matches) => {
|
||||
expect(KubernetesValidatorFunctions.isValidAnnotationValue(value)).toBe(
|
||||
matches,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommonValidatorFunctions } from './CommonValidatorFunctions';
|
||||
|
||||
/**
|
||||
* Contains validation functions that match the Kubernetes spec, usable to
|
||||
* build a catalog that is compatible with those rule sets.
|
||||
*
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/names/
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set
|
||||
*/
|
||||
export class KubernetesValidatorFunctions {
|
||||
static isValidApiVersion(value: any): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
CommonValidatorFunctions.isValidDnsSubdomain,
|
||||
n => n.length >= 1 && n.length <= 63 && /^[a-z0-9A-Z]+$/.test(n),
|
||||
);
|
||||
}
|
||||
|
||||
static isValidKind(value: any): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 63 &&
|
||||
/^[a-zA-Z][a-z0-9A-Z]*$/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
static isValidObjectName(value: any): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
value.length <= 63 &&
|
||||
/^[a-z0-9A-Z]+([-_.][a-z0-9A-Z]+)*$/.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
static isValidLabelKey(value: any): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
CommonValidatorFunctions.isValidDnsSubdomain,
|
||||
KubernetesValidatorFunctions.isValidObjectName,
|
||||
);
|
||||
}
|
||||
|
||||
static isValidLabelValue(value: any): boolean {
|
||||
return (
|
||||
value === '' || KubernetesValidatorFunctions.isValidObjectName(value)
|
||||
);
|
||||
}
|
||||
|
||||
static isValidAnnotationKey(value: any): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
CommonValidatorFunctions.isValidDnsSubdomain,
|
||||
KubernetesValidatorFunctions.isValidObjectName,
|
||||
);
|
||||
}
|
||||
|
||||
static isValidAnnotationValue(value: any): boolean {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from './CommonValidatorFunctions';
|
||||
export * from './KubernetesValidatorFunctions';
|
||||
export * from './makeValidator';
|
||||
export * from './types';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Validators } from './types';
|
||||
import { CommonValidatorFunctions } from './CommonValidatorFunctions';
|
||||
import { KubernetesValidatorFunctions } from './KubernetesValidatorFunctions';
|
||||
|
||||
const defaultValidators: Validators = {
|
||||
isValidApiVersion: KubernetesValidatorFunctions.isValidApiVersion,
|
||||
isValidKind: KubernetesValidatorFunctions.isValidKind,
|
||||
isValidEntityName: KubernetesValidatorFunctions.isValidObjectName,
|
||||
normalizeEntityName: CommonValidatorFunctions.normalizeToLowercaseAlphanum,
|
||||
isValidLabelKey: KubernetesValidatorFunctions.isValidLabelKey,
|
||||
isValidLabelValue: KubernetesValidatorFunctions.isValidLabelValue,
|
||||
isValidAnnotationKey: KubernetesValidatorFunctions.isValidAnnotationKey,
|
||||
isValidAnnotationValue: KubernetesValidatorFunctions.isValidAnnotationValue,
|
||||
};
|
||||
|
||||
export function makeValidator(overrides: Partial<Validators> = {}): Validators {
|
||||
return {
|
||||
...defaultValidators,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export type Validators = {
|
||||
isValidApiVersion(value: any): boolean;
|
||||
isValidKind(value: any): boolean;
|
||||
isValidEntityName(value: any): boolean;
|
||||
normalizeEntityName(value: string): string;
|
||||
isValidLabelKey(value: any): boolean;
|
||||
isValidLabelValue(value: any): boolean;
|
||||
isValidAnnotationKey(value: any): boolean;
|
||||
isValidAnnotationValue(value: any): boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user