Merge pull request #17125 from dawngerpony/dawngerpony/parent-domain-v1

Allow a domain entity to have an optional domain
This commit is contained in:
Fredrik Adelöw
2024-04-22 17:20:56 +02:00
committed by GitHub
14 changed files with 91 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/catalog-model': minor
---
Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---
Emit well known relationships for the Domain entity kind.
@@ -1265,6 +1265,7 @@ metadata:
description: Everything about artists
spec:
owner: artist-relations-team
subdomainOf: audio-domain
```
In addition to the [common envelope metadata](#common-to-all-kinds-the-metadata)
@@ -1293,6 +1294,15 @@ but there will always be one ultimate owner.
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
### `spec.subdomainOf` [optional]
An [entity reference](references.md#string-references) to another domain of
which the domain is a part, e.g. `audio`. This field is optional.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| --------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------- |
| [`Domain`](#kind-domain) (default) | Same as this entity, typically `default` | [`partOf`, and reverse `hasPart`](well-known-relations.md#partof-and-haspart) |
## Kind: Location
Describes the following entity kind:
@@ -99,11 +99,12 @@ A relation with a [Domain](descriptor-format.md#kind-domain),
[System](descriptor-format.md#kind-system) or
[Component](descriptor-format.md#kind-component) entity, typically from a
[Component](descriptor-format.md#kind-component),
[API](descriptor-format.md#kind-api), or
[System](descriptor-format.md#kind-system).
[API](descriptor-format.md#kind-api),
[System](descriptor-format.md#kind-system), or
[Domain](descriptor-format.md#kind-domain),
These relations express that a component belongs to a larger component; a
component, API or resource belongs to a system; or that a system is grouped
under a domain.
component, API or resource belongs to a system; that a system is grouped
under a domain; or that a domain belongs to a larger domain.
This relation is commonly based on `spec.system` or `spec.domain`.
+1
View File
@@ -122,6 +122,7 @@ interface DomainEntityV1alpha1 extends Entity {
// (undocumented)
spec: {
owner: string;
subdomainOf?: string;
};
}
export { DomainEntityV1alpha1 as DomainEntity };
@@ -5,5 +5,6 @@ metadata:
description: A collection of all Backstage example domains
spec:
targets:
- ./domains/audio-domain.yaml
- ./domains/artists-domain.yaml
- ./domains/playback-domain.yaml
@@ -12,3 +12,4 @@ metadata:
icon: dashboard
spec:
owner: team-a
subdomainOf: audio
@@ -0,0 +1,13 @@
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
name: audio
description: Everything related to audio
links:
- url: http://example.com/domain/audio/
title: Domain Readme
- url: http://example.com/domains/audio/dashboard
title: Domain Metrics Dashboard
icon: dashboard
spec:
owner: acme-corp
@@ -5,3 +5,4 @@ metadata:
description: Everything related to audio playback
spec:
owner: user:frank.tiernan
subdomainOf: audio
@@ -31,6 +31,7 @@ describe('DomainV1alpha1Validator', () => {
},
spec: {
owner: 'me',
subdomainOf: 'parent-domain',
},
};
});
@@ -68,4 +69,19 @@ describe('DomainV1alpha1Validator', () => {
(entity as any).spec.owner = '';
await expect(validator.check(entity)).rejects.toThrow(/owner/);
});
it('accepts missing subdomainOf', async () => {
delete (entity as any).spec.subdomainOf;
await expect(validator.check(entity)).resolves.toBe(true);
});
it('rejects wrong subdomainOf', async () => {
(entity as any).spec.subdomainOf = 7;
await expect(validator.check(entity)).rejects.toThrow(/subdomainOf/);
});
it('rejects empty subdomainOf', async () => {
(entity as any).spec.subdomainOf = '';
await expect(validator.check(entity)).rejects.toThrow(/subdomainOf/);
});
});
@@ -32,6 +32,7 @@ export interface DomainEntityV1alpha1 extends Entity {
kind: 'Domain';
spec: {
owner: string;
subdomainOf?: string;
};
}
@@ -11,7 +11,8 @@
"description": "Everything about artists"
},
"spec": {
"owner": "artist-relations-team"
"owner": "artist-relations-team",
"subdomainOf": "audio"
}
}
],
@@ -38,6 +39,12 @@
"description": "An entity reference to the owner of the component.",
"examples": ["artist-relations-team", "user:john.johnson"],
"minLength": 1
},
"subdomainOf": {
"type": "string",
"description": "An entity reference to another domain of which the domain is a part.",
"examples": ["audio"],
"minLength": 1
}
}
}
@@ -418,12 +418,13 @@ describe('BuiltinKindsEntityProcessor', () => {
metadata: { name: 'n' },
spec: {
owner: 'o',
subdomainOf: 'p',
},
};
await processor.postProcessEntity(entity, location, emit);
expect(emit).toHaveBeenCalledTimes(2);
expect(emit).toHaveBeenCalledTimes(4);
expect(emit).toHaveBeenCalledWith({
type: 'relation',
relation: {
@@ -440,6 +441,22 @@ describe('BuiltinKindsEntityProcessor', () => {
target: { kind: 'Group', namespace: 'default', name: 'o' },
},
});
expect(emit).toHaveBeenCalledWith({
type: 'relation',
relation: {
source: { kind: 'Domain', namespace: 'default', name: 'n' },
type: 'partOf',
target: { kind: 'Domain', namespace: 'default', name: 'p' },
},
});
expect(emit).toHaveBeenCalledWith({
type: 'relation',
relation: {
source: { kind: 'Domain', namespace: 'default', name: 'p' },
type: 'hasPart',
target: { kind: 'Domain', namespace: 'default', name: 'n' },
},
});
});
it('generates relations for user entities', async () => {
@@ -298,6 +298,12 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
RELATION_OWNED_BY,
RELATION_OWNER_OF,
);
doEmit(
domain.spec.subdomainOf,
{ defaultKind: 'Domain', defaultNamespace: selfRef.namespace },
RELATION_PART_OF,
RELATION_HAS_PART,
);
}
return entity;