Merge pull request #2893 from spotify/freben/validator-any
chore(catalog-model): use unknown instead of any for validators
This commit is contained in:
@@ -57,7 +57,7 @@ export function buildPgDatabaseConfig(
|
||||
* Gets the postgres connection config
|
||||
*
|
||||
* @param dbConfig The database config
|
||||
* @param parseConnectionString Flag to explictly control connection string parsing
|
||||
* @param parseConnectionString Flag to explicitly control connection string parsing
|
||||
*/
|
||||
export function getPgConnectionConfig(
|
||||
dbConfig: Config,
|
||||
|
||||
@@ -14,37 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogBuilder,
|
||||
createRouter,
|
||||
DatabaseEntitiesCatalog,
|
||||
DatabaseLocationsCatalog,
|
||||
DatabaseManager,
|
||||
HigherOrderOperations,
|
||||
LocationReaders,
|
||||
runPeriodically,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
reader,
|
||||
database,
|
||||
}: PluginEnvironment) {
|
||||
const locationReader = new LocationReaders({ logger, reader, config });
|
||||
|
||||
const db = await DatabaseManager.createDatabase(await database.getClient(), {
|
||||
logger,
|
||||
});
|
||||
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
|
||||
const locationsCatalog = new DatabaseLocationsCatalog(db);
|
||||
const higherOrderOperation = new HigherOrderOperations(
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
const builder = new CatalogBuilder(env);
|
||||
const {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
locationReader,
|
||||
logger,
|
||||
);
|
||||
higherOrderOperation,
|
||||
} = await builder.build();
|
||||
|
||||
useHotCleanup(
|
||||
module,
|
||||
@@ -55,6 +39,6 @@ export default async function createPlugin({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
higherOrderOperation,
|
||||
logger,
|
||||
logger: env.logger,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { Entity } from './entity';
|
||||
/*
|
||||
* 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 { EntityPolicies } from './EntityPolicies';
|
||||
import { EntityPolicy } from './types';
|
||||
|
||||
describe('EntityPolicies', () => {
|
||||
const p1: jest.Mocked<EntityPolicy> = { enforce: jest.fn() };
|
||||
const p2: jest.Mocked<EntityPolicy> = { enforce: jest.fn() };
|
||||
const entity1: Entity = {
|
||||
apiVersion: 'a1',
|
||||
kind: 'k1',
|
||||
metadata: { name: 'n1' },
|
||||
};
|
||||
const entity2: Entity = {
|
||||
apiVersion: 'a2',
|
||||
kind: 'k2',
|
||||
metadata: { name: 'n2' },
|
||||
};
|
||||
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('allOf', () => {
|
||||
it('resolves when no policies', async () => {
|
||||
const policy = EntityPolicies.allOf([]);
|
||||
await expect(policy.enforce(entity1)).resolves.toBe(entity1);
|
||||
});
|
||||
|
||||
it('resolves when all resolve', async () => {
|
||||
p1.enforce.mockResolvedValue(entity1);
|
||||
p2.enforce.mockResolvedValue(entity1);
|
||||
const policy = EntityPolicies.allOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).resolves.toBe(entity1);
|
||||
});
|
||||
|
||||
it('rejects when any rejects', async () => {
|
||||
p1.enforce.mockResolvedValue(entity1);
|
||||
p2.enforce.mockRejectedValue(new Error('a'));
|
||||
const policy = EntityPolicies.allOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).rejects.toThrow('a');
|
||||
});
|
||||
|
||||
it('rejects when any ignores', async () => {
|
||||
p1.enforce.mockResolvedValue(entity1);
|
||||
p2.enforce.mockResolvedValue(undefined);
|
||||
const policy = EntityPolicies.allOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).rejects.toThrow(
|
||||
/did not return a result/,
|
||||
);
|
||||
});
|
||||
|
||||
it('passes through transforms properly', async () => {
|
||||
p1.enforce.mockResolvedValue(entity2);
|
||||
p2.enforce.mockResolvedValue(entity2);
|
||||
const policy = EntityPolicies.allOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).resolves.toBe(entity2);
|
||||
expect(p1.enforce).toBeCalledWith(entity1);
|
||||
expect(p2.enforce).toBeCalledWith(entity2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('oneOf', () => {
|
||||
it('rejects when no policies', async () => {
|
||||
const policy = EntityPolicies.oneOf([]);
|
||||
await expect(policy.enforce(entity1)).rejects.toThrow(/did not match/);
|
||||
});
|
||||
|
||||
it('resolves when one resolves', async () => {
|
||||
p1.enforce.mockResolvedValue(undefined);
|
||||
p2.enforce.mockResolvedValue(entity1);
|
||||
const policy = EntityPolicies.oneOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).resolves.toBe(entity1);
|
||||
});
|
||||
|
||||
it('rejects when one rejects first', async () => {
|
||||
p1.enforce.mockRejectedValue(new Error('a'));
|
||||
p2.enforce.mockResolvedValue(entity1);
|
||||
const policy = EntityPolicies.oneOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).rejects.toThrow('a');
|
||||
});
|
||||
|
||||
it('resolves first resolution when several resolve', async () => {
|
||||
p1.enforce.mockResolvedValue(entity1);
|
||||
p2.enforce.mockResolvedValue(entity2);
|
||||
const policy = EntityPolicies.oneOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).resolves.toBe(entity1);
|
||||
});
|
||||
|
||||
it('rejects when all ignore', async () => {
|
||||
p1.enforce.mockResolvedValue(undefined);
|
||||
p2.enforce.mockResolvedValue(undefined);
|
||||
const policy = EntityPolicies.oneOf([p1, p2]);
|
||||
await expect(policy.enforce(entity1)).rejects.toThrow(/did not match/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,22 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
DefaultNamespaceEntityPolicy,
|
||||
Entity,
|
||||
FieldFormatEntityPolicy,
|
||||
NoForeignRootFieldsEntityPolicy,
|
||||
ReservedFieldsEntityPolicy,
|
||||
SchemaValidEntityPolicy,
|
||||
} from './entity';
|
||||
import {
|
||||
apiEntityV1alpha1Policy,
|
||||
componentEntityV1alpha1Policy,
|
||||
groupEntityV1alpha1Policy,
|
||||
locationEntityV1alpha1Policy,
|
||||
templateEntityV1alpha1Policy,
|
||||
userEntityV1alpha1Policy,
|
||||
} from './kinds';
|
||||
import { Entity } from './entity';
|
||||
import { EntityPolicy } from './types';
|
||||
|
||||
// Helper that requires that all of a set of policies can be successfully
|
||||
@@ -68,42 +53,11 @@ class AnyEntityPolicy implements EntityPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
export class EntityPolicies implements EntityPolicy {
|
||||
private readonly policy: EntityPolicy;
|
||||
|
||||
static defaultPolicies(): EntityPolicy {
|
||||
return EntityPolicies.allOf([
|
||||
EntityPolicies.allOf([
|
||||
new SchemaValidEntityPolicy(),
|
||||
new DefaultNamespaceEntityPolicy(),
|
||||
new NoForeignRootFieldsEntityPolicy(),
|
||||
new FieldFormatEntityPolicy(),
|
||||
new ReservedFieldsEntityPolicy(),
|
||||
]),
|
||||
EntityPolicies.anyOf([
|
||||
componentEntityV1alpha1Policy,
|
||||
groupEntityV1alpha1Policy,
|
||||
userEntityV1alpha1Policy,
|
||||
locationEntityV1alpha1Policy,
|
||||
templateEntityV1alpha1Policy,
|
||||
apiEntityV1alpha1Policy,
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
static allOf(policies: EntityPolicy[]): EntityPolicy {
|
||||
export const EntityPolicies = {
|
||||
allOf(policies: EntityPolicy[]) {
|
||||
return new AllEntityPolicies(policies);
|
||||
}
|
||||
|
||||
static anyOf(policies: EntityPolicy[]): EntityPolicy {
|
||||
},
|
||||
oneOf(policies: EntityPolicy[]) {
|
||||
return new AnyEntityPolicy(policies);
|
||||
}
|
||||
|
||||
constructor(policy: EntityPolicy = EntityPolicies.defaultPolicies()) {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
enforce(entity: Entity): Promise<Entity | undefined> {
|
||||
return this.policy.enforce(entity);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ describe('CommonValidatorFunctions', () => {
|
||||
[{}, true],
|
||||
[{ a: 1 }, true],
|
||||
[{ a: undefined }, false],
|
||||
] as [any, boolean][])(`isJsonSafe %p ? %p`, (value, result) => {
|
||||
] as [unknown, boolean][])(`isJsonSafe %p ? %p`, (value, result) => {
|
||||
expect(CommonValidatorFunctions.isJsonSafe(value)).toBe(result);
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class CommonValidatorFunctions {
|
||||
* @param isValidSuffix Checks that the part after the separator (or the entire value if there is no separator) is valid
|
||||
*/
|
||||
static isValidPrefixAndOrSuffix(
|
||||
value: any,
|
||||
value: unknown,
|
||||
separator: string,
|
||||
isValidPrefix: (value: string) => boolean,
|
||||
isValidSuffix: (value: string) => boolean,
|
||||
@@ -55,7 +55,7 @@ export class CommonValidatorFunctions {
|
||||
*
|
||||
* @param value The value to check
|
||||
*/
|
||||
static isJsonSafe(value: any): boolean {
|
||||
static isJsonSafe(value: unknown): boolean {
|
||||
try {
|
||||
return lodash.isEqual(value, JSON.parse(JSON.stringify(value)));
|
||||
} catch {
|
||||
@@ -69,7 +69,7 @@ export class CommonValidatorFunctions {
|
||||
* @param value The value to check
|
||||
* @see https://tools.ietf.org/html/rfc1123
|
||||
*/
|
||||
static isValidDnsSubdomain(value: any): boolean {
|
||||
static isValidDnsSubdomain(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
@@ -84,7 +84,7 @@ export class CommonValidatorFunctions {
|
||||
* @param value The value to check
|
||||
* @see https://tools.ietf.org/html/rfc1123
|
||||
*/
|
||||
static isValidDnsLabel(value: any): boolean {
|
||||
static isValidDnsLabel(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
|
||||
@@ -25,7 +25,7 @@ import { CommonValidatorFunctions } from './CommonValidatorFunctions';
|
||||
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/#syntax-and-character-set
|
||||
*/
|
||||
export class KubernetesValidatorFunctions {
|
||||
static isValidApiVersion(value: any): boolean {
|
||||
static isValidApiVersion(value: unknown): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
@@ -34,7 +34,7 @@ export class KubernetesValidatorFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
static isValidKind(value: any): boolean {
|
||||
static isValidKind(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
@@ -43,7 +43,7 @@ export class KubernetesValidatorFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
static isValidObjectName(value: any): boolean {
|
||||
static isValidObjectName(value: unknown): boolean {
|
||||
return (
|
||||
typeof value === 'string' &&
|
||||
value.length >= 1 &&
|
||||
@@ -52,11 +52,11 @@ export class KubernetesValidatorFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
static isValidNamespace(value: any): boolean {
|
||||
static isValidNamespace(value: unknown): boolean {
|
||||
return CommonValidatorFunctions.isValidDnsLabel(value);
|
||||
}
|
||||
|
||||
static isValidLabelKey(value: any): boolean {
|
||||
static isValidLabelKey(value: unknown): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
@@ -65,13 +65,13 @@ export class KubernetesValidatorFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
static isValidLabelValue(value: any): boolean {
|
||||
static isValidLabelValue(value: unknown): boolean {
|
||||
return (
|
||||
value === '' || KubernetesValidatorFunctions.isValidObjectName(value)
|
||||
);
|
||||
}
|
||||
|
||||
static isValidAnnotationKey(value: any): boolean {
|
||||
static isValidAnnotationKey(value: unknown): boolean {
|
||||
return CommonValidatorFunctions.isValidPrefixAndOrSuffix(
|
||||
value,
|
||||
'/',
|
||||
@@ -80,7 +80,7 @@ export class KubernetesValidatorFunctions {
|
||||
);
|
||||
}
|
||||
|
||||
static isValidAnnotationValue(value: any): boolean {
|
||||
static isValidAnnotationValue(value: unknown): boolean {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
*/
|
||||
|
||||
export type Validators = {
|
||||
isValidApiVersion(value: any): boolean;
|
||||
isValidKind(value: any): boolean;
|
||||
isValidEntityName(value: any): boolean;
|
||||
isValidNamespace(value: any): boolean;
|
||||
isValidLabelKey(value: any): boolean;
|
||||
isValidLabelValue(value: any): boolean;
|
||||
isValidAnnotationKey(value: any): boolean;
|
||||
isValidAnnotationValue(value: any): boolean;
|
||||
isValidTag(value: any): boolean;
|
||||
isValidApiVersion(value: unknown): boolean;
|
||||
isValidKind(value: unknown): boolean;
|
||||
isValidEntityName(value: unknown): boolean;
|
||||
isValidNamespace(value: unknown): boolean;
|
||||
isValidLabelKey(value: unknown): boolean;
|
||||
isValidLabelValue(value: unknown): boolean;
|
||||
isValidAnnotationKey(value: unknown): boolean;
|
||||
isValidAnnotationValue(value: unknown): boolean;
|
||||
isValidTag(value: unknown): boolean;
|
||||
};
|
||||
|
||||
@@ -1,44 +1,28 @@
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
import {
|
||||
CatalogBuilder,
|
||||
createRouter,
|
||||
DatabaseEntitiesCatalog,
|
||||
DatabaseLocationsCatalog,
|
||||
DatabaseManager,
|
||||
HigherOrderOperations,
|
||||
LocationReaders,
|
||||
runPeriodically,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import { PluginEnvironment } from '../types';
|
||||
import { useHotCleanup } from '@backstage/backend-common';
|
||||
|
||||
export default async function createPlugin({
|
||||
logger,
|
||||
config,
|
||||
reader,
|
||||
database,
|
||||
}: PluginEnvironment) {
|
||||
const locationReader = new LocationReaders({ logger, reader, config });
|
||||
|
||||
const db = await DatabaseManager.createDatabase(await database.getClient(),
|
||||
{ logger },
|
||||
);
|
||||
const entitiesCatalog = new DatabaseEntitiesCatalog(db);
|
||||
const locationsCatalog = new DatabaseLocationsCatalog(db);
|
||||
const higherOrderOperation = new HigherOrderOperations(
|
||||
export default async function createPlugin(env: PluginEnvironment) {
|
||||
const builder = new CatalogBuilder(env);
|
||||
const {
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
locationReader,
|
||||
logger,
|
||||
);
|
||||
higherOrderOperation,
|
||||
} = await builder.build();
|
||||
|
||||
useHotCleanup(
|
||||
module,
|
||||
runPeriodically(() => higherOrderOperation.refreshAllLocations(), 10000),
|
||||
runPeriodically(() => higherOrderOperation.refreshAllLocations(), 100000),
|
||||
);
|
||||
|
||||
return await createRouter({
|
||||
entitiesCatalog,
|
||||
locationsCatalog,
|
||||
higherOrderOperation,
|
||||
logger,
|
||||
logger: env.logger,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user