spec.owner added into template schema

Signed-off-by: Victor Perera <v-vperera@expediagroup.com>
This commit is contained in:
Victor Perera
2021-05-05 14:46:13 -05:00
parent 4c7aaf36f3
commit a23850d4ad
10 changed files with 109 additions and 6 deletions
@@ -657,6 +657,25 @@ You can find out more about the `parameters` key
You can find out more about the `steps` key
[here](../software-templates/writing-templates.md)
### `spec.owner` [optional]
An [entity reference](#string-references) to the owner of the component, e.g.
`artist-relations-team`. This field is required.
In Backstage, the owner of an Template is the singular entity (commonly a team)
that bears ultimate responsibility for the Template, and has the authority and
capability to develop and maintain it. They will be the point of contact if
something goes wrong, or if features are to be requested. The main purpose of
this field is for display purposes in Backstage, so that people looking at
catalog items can get an understanding of to whom this Template belongs. It is
not to be used by automated processes to for example assign authorization in
runtime systems. There may be others that also develop or otherwise touch the
Template, but there will always be one ultimate owner.
| [`kind`](#apiversion-and-kind-required) | Default [`namespace`](#namespace-optional) | Generated [relation](well-known-relations.md) type |
| ------------------------------------------------------ | ------------------------------------------ | ------------------------------------------------------------------------------- |
| [`Group`](#kind-group) (default), [`User`](#kind-user) | Same as this entity, typically `default` | [`ownerOf`, and reverse `ownedBy`](well-known-relations.md#ownedby-and-ownerof) |
## Kind: API
Describes the following entity kind:
@@ -91,4 +91,8 @@ describe('templateEntityV1alpha1Validator', () => {
(entity as any).spec.templater = '';
await expect(validator.check(entity)).rejects.toThrow(/templater/);
});
it('accepts missing owner', async () => {
delete (entity as any).spec.owner;
await expect(validator.check(entity)).resolves.toBe(true);
});
});
@@ -33,7 +33,7 @@ export interface TemplateEntityV1alpha1 extends Entity {
templater: string;
path?: string;
schema: JSONSchema;
owner: string;
owner?: string;
};
}
@@ -59,6 +59,7 @@ describe('templateEntityV1beta2Validator', () => {
output: {
fetchUrl: '{{ steps.fetch.output.targetUrl }}',
},
owner: 'example@email.com',
},
};
});
@@ -121,4 +122,8 @@ describe('templateEntityV1beta2Validator', () => {
delete (entity as any).spec.steps[0].action;
await expect(validator.check(entity)).rejects.toThrow(/action/);
});
it('accepts missing owner', async () => {
delete (entity as any).spec.owner;
await expect(validator.check(entity)).resolves.toBe(true);
});
});
@@ -41,6 +41,7 @@ export interface TemplateEntityV1beta2 extends Entity {
parameters?: JsonObject;
}>;
output?: { [name: string]: string };
owner?: string;
};
}
@@ -85,6 +85,10 @@
"schema": {
"type": "object",
"description": "The JSONSchema describing the inputs for the template."
},
"owner": {
"type": "string",
"description": "The user (or group) owner of the template"
}
}
}
@@ -168,6 +168,10 @@
"additionalProperties": {
"type": "string"
}
},
"owner": {
"type": "string",
"description": "The user (or group) owner of the template"
}
}
}
@@ -21,6 +21,7 @@ import {
GroupEntity,
ResourceEntity,
SystemEntity,
TemplateEntity,
UserEntity,
} from '@backstage/catalog-model';
import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
@@ -520,5 +521,47 @@ describe('BuiltinKindsEntityProcessor', () => {
},
});
});
it('generates relations for template entities', async () => {
const entity: TemplateEntity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Template',
metadata: { name: 'n' },
spec: {
schema: {
properties: {
description: {
title: 'd',
type: 'string',
description: 'des',
},
},
},
templater: 'cookiecutter',
path: '.',
type: 'service',
owner: 'o',
},
};
await processor.postProcessEntity(entity, location, emit);
expect(emit).toBeCalledTimes(2);
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'Group', namespace: 'default', name: 'o' },
type: 'ownerOf',
target: { kind: 'Template', namespace: 'default', name: 'n' },
},
});
expect(emit).toBeCalledWith({
type: 'relation',
relation: {
source: { kind: 'Template', namespace: 'default', name: 'n' },
type: 'ownedBy',
target: { kind: 'Group', namespace: 'default', name: 'o' },
},
});
});
});
});
@@ -46,6 +46,7 @@ import {
resourceEntityV1alpha1Validator,
SystemEntity,
systemEntityV1alpha1Validator,
TemplateEntity,
templateEntityV1alpha1Validator,
templateEntityV1beta2Validator,
UserEntity,
@@ -131,6 +132,19 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor {
}
}
/*
* Emit relations for the Template kind
*/
if (entity.kind === 'Template') {
const template = entity as TemplateEntity;
doEmit(
template.spec.owner,
{ defaultKind: 'Group', defaultNamespace: selfRef.namespace },
RELATION_OWNED_BY,
RELATION_OWNER_OF,
);
}
/*
* Emit relations for the Component kind
*/
@@ -29,8 +29,16 @@ import {
import React from 'react';
import { generatePath } from 'react-router';
import { rootRouteRef } from '../../routes';
import { TemplateEntityV1alpha1 } from '@backstage/catalog-model';
import {
TemplateEntityV1alpha1,
Entity,
RELATION_OWNED_BY,
} from '@backstage/catalog-model';
import { FavouriteTemplate } from '../FavouriteTemplate/FavouriteTemplate';
import {
getEntityRelations,
EntityRefLinks,
} from '@backstage/plugin-catalog-react';
const useStyles = makeStyles(theme => ({
cardHeader: {
@@ -69,7 +77,6 @@ type TemplateProps = {
title: string;
type: string;
name: string;
owner: string;
};
const getTemplateCardProps = (
@@ -81,7 +88,6 @@ const getTemplateCardProps = (
title: `${(template.metadata.title || template.metadata.name) ?? ''}`,
type: template.spec.type ?? '',
description: template.metadata.description ?? '-',
owner: template.spec.owner ?? '-',
tags: (template.metadata?.tags as string[]) ?? [],
};
};
@@ -90,7 +96,10 @@ export const TemplateCard = ({ template }: TemplateCardProps) => {
const backstageTheme = useTheme<BackstageTheme>();
const rootLink = useRouteRef(rootRouteRef);
const templateProps = getTemplateCardProps(template);
const ownedByRelations = getEntityRelations(
template as Entity,
RELATION_OWNED_BY,
);
const themeId = pageTheme[templateProps.type] ? templateProps.type : 'other';
const theme = backstageTheme.getPageTheme({ themeId });
const classes = useStyles({ backgroundImage: theme.backgroundImage });
@@ -119,7 +128,7 @@ export const TemplateCard = ({ template }: TemplateCardProps) => {
<Typography variant="body2" className={classes.label}>
Owner
</Typography>
<Typography variant="inherit">{templateProps.owner}</Typography>
<EntityRefLinks entityRefs={ownedByRelations} defaultKind="Group" />
</Box>
<Box>
<Typography variant="body2" className={classes.label}>