Merge pull request #19594 from rikez/rikez/parse-entity-ref-filter

feat(scaffolder): allow default kind and namespace args on parseEntityRef filter
This commit is contained in:
Ben Lambert
2023-09-12 11:03:49 +02:00
committed by GitHub
4 changed files with 268 additions and 23 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
Improved the `parseEntityRef` Scaffolder filter by introducing the ability for users to provide default kind and/or namespace values. The filter now takes
2 arguments, similarly to the original [parseEntityRef](https://github.com/backstage/backstage/blob/v1.17.2/packages/catalog-model/src/entity/ref.ts#L77).
@@ -627,3 +627,98 @@ output things. You can grab that output using `steps.$stepId.output.$property`.
You can read more about all the `inputs` and `outputs` defined in the actions in
code part of the `JSONSchema`, or you can read more about our
[built in actions](./builtin-actions.md).
## Built in Filters
Template filters are functions that help you transform data, extract specific information,
and perform various operations in Scaffolder Templates.
This section introduces the built-in filters provided by Backstage and offers examples of
how to use them in the Scaffolder templates. It's important to mention that Backstage also leverages the
native filters from the Nunjucks library. For a complete list of these native filters and their usage,
refer to the [Nunjucks documentation](https://mozilla.github.io/nunjucks/templating.html#builtin-filters).
### parseRepoUrl
The `parseRepoUrl` filter parse a repository URL into
its components, such as `owner`, repository `name`, and more.
**Usage Example:**
```yaml
- id: log
name: Parse Repo URL
action: debug:log
input:
extra: ${{ parameters.repoUrl | parseRepoUrl }}
```
- **Input**: `github.com?repo=backstage&org=backstage`
- **Output**: [RepoSpec](https://github.com/backstage/backstage/blob/v1.17.2/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts#L39)
### parseEntityRef
The `parseEntityRef` filter allows you to extract different parts of
an entity reference, such as the `kind`, `namespace`, and `name`.
**Usage example**
1. Without context
```yaml
- id: log
name: Parse Entity Reference
action: debug:log
input:
extra: ${{ parameters.owner | parseEntityRef }}
```
- **Input**: `group:techdocs`
- **Output**: [CompoundEntityRef](https://github.com/backstage/backstage/blob/v1.17.2/packages/catalog-model/src/types.ts#L23)
2. With context
```yaml
- id: log
name: Parse Entity Reference
action: debug:log
input:
extra: ${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }}
```
- **Input**: `techdocs`
- **Output**: [CompoundEntityRef](https://github.com/backstage/backstage/blob/v1.17.2/packages/catalog-model/src/types.ts#L23)
### pick
This `pick` filter allows you to select specific properties from an object.
**Usage Example**
```yaml
- id: log
name: Pick
action: debug:log
input:
extra: ${{ parameters.owner | parseEntityRef | pick('name') }}
```
- **Input**: `{ kind: 'Group', namespace: 'default', name: 'techdocs' }`
- **Output**: `techdocs`
### projectSlug
The `projectSlug` filter generates a project slug from a repository URL
**Usage Example**
```yaml
- id: log
name: Project Slug
action: debug:log
input:
extra: ${{ parameters.repoUrl | projectSlug }}
```
- **Input**: `github.com?repo=backstage&org=backstage`
- **Output**: `backstage/backstage`
@@ -15,7 +15,7 @@
*/
import { parseEntityRef } from '@backstage/catalog-model';
import { ScmIntegrations } from '@backstage/integration';
import { JsonValue } from '@backstage/types';
import type { JsonObject, JsonValue } from '@backstage/types';
import { TemplateFilter } from '..';
import { parseRepoUrl } from '../../scaffolder/actions/builtin/publish/util';
import get from 'lodash/get';
@@ -27,7 +27,8 @@ export const createDefaultFilters = ({
}): Record<string, TemplateFilter> => {
return {
parseRepoUrl: url => parseRepoUrl(url as string, integrations),
parseEntityRef: ref => parseEntityRef(ref as string),
parseEntityRef: (ref: JsonValue, context?: JsonValue) =>
parseEntityRef(ref as string, context as JsonObject),
pick: (obj: JsonValue, key: JsonValue) => get(obj, key as string),
projectSlug: repoUrl => {
const { owner, repo } = parseRepoUrl(repoUrl as string, integrations);
@@ -808,31 +808,174 @@ describe('DefaultWorkflowRunner', () => {
});
});
it('provides the parseEntityRef filter', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
describe('parseEntityRef', () => {
it('parses entity ref', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: '${{ parameters.entity | parseEntityRef }}',
},
],
output: {
foo: '${{ parameters.entity | parseEntityRef }}',
},
parameters: {
entity: 'component:default/ben',
},
parameters: {
entity: 'component:default/ben',
},
});
const { output } = await runner.execute(task);
expect(output.foo).toEqual({
kind: 'component',
namespace: 'default',
name: 'ben',
});
});
const { output } = await runner.execute(task);
it('provides default kind for parsing entity ref', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: `\${{ parameters.entity | parseEntityRef({ defaultKind:"user" }) }}`,
},
parameters: {
entity: 'ben',
},
});
expect(output.foo).toEqual({
kind: 'component',
namespace: 'default',
name: 'ben',
const { output } = await runner.execute(task);
expect(output.foo).toEqual({
kind: 'user',
namespace: 'default',
name: 'ben',
});
});
it('provides default namespace for parsing entity ref', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: `\${{ parameters.entity | parseEntityRef({ defaultNamespace:"namespace-b" }) }}`,
},
parameters: {
entity: 'user:ben',
},
});
const { output } = await runner.execute(task);
expect(output.foo).toEqual({
kind: 'user',
namespace: 'namespace-b',
name: 'ben',
});
});
it('provides default kind and namespace for parsing entity ref', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: `\${{ parameters.entity | parseEntityRef({ defaultKind:"user", defaultNamespace:"namespace-b" }) }}`,
},
parameters: {
entity: 'ben',
},
});
const { output } = await runner.execute(task);
expect(output.foo).toEqual({
kind: 'user',
namespace: 'namespace-b',
name: 'ben',
});
});
it.each(['undefined', 'null', 'None', 'group', 0, '{}', '[]'])(
'ignores invalid context "%s" for parsing entity refF',
async kind => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: `\${{ parameters.entity | parseEntityRef(${kind}) }}`,
},
parameters: {
entity: 'user:default/ben',
},
});
const { output } = await runner.execute(task);
expect(output.foo).toEqual({
kind: 'user',
namespace: 'default',
name: 'ben',
});
},
);
it('fails when unable to parse entity ref', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
steps: [
{
id: 'test',
name: 'name',
action: 'output-action',
input: {},
},
],
output: {
foo: `\${{ parameters.entity | parseEntityRef({ defaultNamespace:"namespace-b" }) }}`,
},
parameters: {
entity: 'ben',
},
});
const { output } = await runner.execute(task);
expect(output.foo).toEqual(
`\${{ parameters.entity | parseEntityRef({ defaultNamespace:"namespace-b" }) }}`,
);
});
});