feat(catalog): add tags to entity metadata
This add a field tags to the entity metadata to specifiy a user defined classifications. Closes #1997
This commit is contained in:
@@ -42,6 +42,7 @@ software catalog API.
|
||||
"labels": {
|
||||
"system": "public-websites"
|
||||
},
|
||||
"tags": ["java"],
|
||||
"name": "artist-web",
|
||||
"uid": "2152f463-549d-4d8d-a94d-ce2b7676c6e2"
|
||||
},
|
||||
@@ -66,6 +67,8 @@ metadata:
|
||||
annotations:
|
||||
example.com/service-discovery: artistweb
|
||||
circleci.com/project-slug: gh/example-org/artist-website
|
||||
tags:
|
||||
- java
|
||||
spec:
|
||||
type: website
|
||||
lifecycle: production
|
||||
@@ -232,6 +235,20 @@ The `backstage.io/` prefix is reserved for use by Backstage core components.
|
||||
|
||||
Values can be of any length, but are limited to being strings.
|
||||
|
||||
### `tags` [optional]
|
||||
|
||||
A list of single-valued strings, for example to classify catalog entities in
|
||||
various ways. This is different to the labels in metadata, as labels are
|
||||
key-value pairs.
|
||||
|
||||
The values are user defined, for example the programming language used for the
|
||||
component, like `java` or `go`.
|
||||
|
||||
This field is optional, and currently has no special semantics.
|
||||
|
||||
Each tag must be sequences of `[a-zA-Z0-9]` separated by `-`, at most 63
|
||||
characters in total.
|
||||
|
||||
## Kind: Component
|
||||
|
||||
Describes the following entity kind:
|
||||
|
||||
@@ -113,6 +113,12 @@ export type EntityMeta = JsonObject & {
|
||||
* entity.
|
||||
*/
|
||||
annotations?: Record<string, string>;
|
||||
|
||||
/**
|
||||
* A list of single-valued strings, to for example classify catalog entities in
|
||||
* various ways.
|
||||
*/
|
||||
tags?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,9 @@ describe('FieldFormatEntityPolicy', () => {
|
||||
backstage.io/custom: ValueStuff
|
||||
annotations:
|
||||
example.com/bindings: are-secret
|
||||
tags:
|
||||
- java
|
||||
- data-service
|
||||
spec:
|
||||
custom: stuff
|
||||
`);
|
||||
@@ -102,4 +105,9 @@ describe('FieldFormatEntityPolicy', () => {
|
||||
data.metadata.annotations.a = 7;
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/annotation.*7/i);
|
||||
});
|
||||
|
||||
it('rejects bad tag value', async () => {
|
||||
data.metadata.tags.push('Hello World');
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/tags.*"Hello World"/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,12 @@ export class FieldFormatEntityPolicy implements EntityPolicy {
|
||||
require(`annotations.${k}`, v, this.validators.isValidAnnotationValue);
|
||||
}
|
||||
|
||||
const tags = entity.metadata.tags ?? [];
|
||||
|
||||
for (let i = 0; i < tags.length; ++i) {
|
||||
require(`tags.${i}`, tags[i], this.validators.isValidTag);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ describe('ReservedFieldsEntityPolicy', () => {
|
||||
backstage.io/custom: ValueStuff
|
||||
annotations:
|
||||
example.com/bindings: are-secret
|
||||
tags:
|
||||
- java
|
||||
- data
|
||||
spec:
|
||||
custom: stuff
|
||||
`);
|
||||
|
||||
@@ -29,6 +29,7 @@ const DEFAULT_RESERVED_ENTITY_FIELDS: string[] = [
|
||||
'metadata.description',
|
||||
'metadata.labels',
|
||||
'metadata.annotations',
|
||||
'metadata.tags',
|
||||
// The below items are known to appear in core kinds, and therefore should
|
||||
// not be appearing in metadata (which would indicate that the user made a
|
||||
// mistake in where to place them).
|
||||
|
||||
@@ -36,6 +36,9 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
backstage.io/custom: ValueStuff
|
||||
annotations:
|
||||
example.com/bindings: are-secret
|
||||
tags:
|
||||
- java
|
||||
- data
|
||||
spec:
|
||||
custom: stuff
|
||||
`);
|
||||
@@ -190,6 +193,11 @@ describe('SchemaValidEntityPolicy', () => {
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/annotations/);
|
||||
});
|
||||
|
||||
it('rejects bad tags type', async () => {
|
||||
data.metadata.tags = 7;
|
||||
await expect(policy.enforce(data)).rejects.toThrow(/tags/);
|
||||
});
|
||||
|
||||
//
|
||||
// spec
|
||||
//
|
||||
|
||||
@@ -31,6 +31,7 @@ const DEFAULT_ENTITY_SCHEMA = yup.object({
|
||||
description: yup.string().notRequired(),
|
||||
labels: yup.object<Record<string, string>>().notRequired(),
|
||||
annotations: yup.object<Record<string, string>>().notRequired(),
|
||||
tags: yup.array<string>().notRequired(),
|
||||
})
|
||||
.required(),
|
||||
spec: yup.object({}).notRequired(),
|
||||
|
||||
@@ -28,6 +28,7 @@ const defaultValidators: Validators = {
|
||||
isValidLabelValue: KubernetesValidatorFunctions.isValidLabelValue,
|
||||
isValidAnnotationKey: KubernetesValidatorFunctions.isValidAnnotationKey,
|
||||
isValidAnnotationValue: KubernetesValidatorFunctions.isValidAnnotationValue,
|
||||
isValidTag: CommonValidatorFunctions.isValidDnsLabel,
|
||||
};
|
||||
|
||||
export function makeValidator(overrides: Partial<Validators> = {}): Validators {
|
||||
|
||||
@@ -24,4 +24,5 @@ export type Validators = {
|
||||
isValidLabelValue(value: any): boolean;
|
||||
isValidAnnotationKey(value: any): boolean;
|
||||
isValidAnnotationValue(value: any): boolean;
|
||||
isValidTag(value: any): boolean;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user