Merge pull request #15682 from drodil/scaffolder_get_entity

feat: add Scaffolder action to get entity by reference
This commit is contained in:
Patrik Oldsberg
2023-01-17 10:52:47 +01:00
committed by GitHub
6 changed files with 227 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
Add Scaffolder action `catalog:fetch` to get entity by entity reference from catalog
+8
View File
@@ -96,6 +96,14 @@ export function createDebugLogAction(): TemplateAction<{
listWorkspace?: boolean | undefined;
}>;
// @public
export function createFetchCatalogEntityAction(options: {
catalogClient: CatalogApi;
}): TemplateAction<{
entityRef: string;
optional?: boolean | undefined;
}>;
// @public
export function createFetchPlainAction(options: {
reader: UrlReader;
@@ -0,0 +1,112 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { PassThrough } from 'stream';
import os from 'os';
import { getVoidLogger } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { createFetchCatalogEntityAction } from './fetch';
describe('catalog:fetch', () => {
const getEntityByRef = jest.fn();
const catalogClient = {
getEntityByRef: getEntityByRef,
};
const action = createFetchCatalogEntityAction({
catalogClient: catalogClient as unknown as CatalogApi,
});
const mockContext = {
workspacePath: os.tmpdir(),
logger: getVoidLogger(),
logStream: new PassThrough(),
output: jest.fn(),
createTemporaryDirectory: jest.fn(),
secrets: { backstageToken: 'secret' },
};
beforeEach(() => {
jest.resetAllMocks();
});
it('should return entity from catalog', async () => {
getEntityByRef.mockReturnValueOnce({
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
} as Entity);
await action.handler({
...mockContext,
input: {
entityRef: 'component:default/test',
},
});
expect(getEntityByRef).toHaveBeenCalledWith('component:default/test', {
token: 'secret',
});
expect(mockContext.output).toHaveBeenCalledWith('entity', {
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
});
});
it('should return null if entity not in catalog and optional is true', async () => {
getEntityByRef.mockImplementationOnce(() => {
throw new Error('Not found');
});
await action.handler({
...mockContext,
input: {
entityRef: 'component:default/test',
optional: true,
},
});
expect(getEntityByRef).toHaveBeenCalledWith('component:default/test', {
token: 'secret',
});
expect(mockContext.output).toHaveBeenCalledWith('entity', null);
});
it('should throw error if entity not in catalog and optional is false', async () => {
getEntityByRef.mockImplementationOnce(() => {
throw new Error('Not found');
});
await expect(
action.handler({
...mockContext,
input: {
entityRef: 'component:default/test',
},
}),
).rejects.toThrow('Not found');
expect(getEntityByRef).toHaveBeenCalledWith('component:default/test', {
token: 'secret',
});
expect(mockContext.output).not.toHaveBeenCalled();
});
});
@@ -0,0 +1,99 @@
/*
* Copyright 2021 The Backstage Authors
*
* 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 { CatalogApi } from '@backstage/catalog-client';
import { createTemplateAction } from '../../createTemplateAction';
import yaml from 'yaml';
const id = 'catalog:fetch';
const examples = [
{
description: 'Fetch entity by reference',
example: yaml.stringify({
steps: [
{
action: id,
id: 'fetch',
name: 'Fetch catalog entity',
input: {
entityRef: 'component:default/name',
},
},
],
}),
},
];
/**
* Returns entity from the catalog by entity reference.
* @public
*/
export function createFetchCatalogEntityAction(options: {
catalogClient: CatalogApi;
}) {
const { catalogClient } = options;
return createTemplateAction<{ entityRef: string; optional?: boolean }>({
id,
description: 'Returns entity from the catalog by entity reference',
examples,
schema: {
input: {
required: ['entityRef'],
type: 'object',
properties: {
entityRef: {
type: 'string',
title: 'Entity reference',
description: 'Entity reference of the entity to get',
},
optional: {
title: 'Optional',
description:
'Permit the entity to optionally exist. Default: false',
type: 'boolean',
},
},
},
output: {
type: 'object',
properties: {
entity: {
title: 'Entity found by the entity reference',
type: 'object',
description:
'Object containing same values used in the Entity schema.',
},
},
},
},
async handler(ctx) {
const { entityRef, optional } = ctx.input;
let entity;
try {
entity = await catalogClient.getEntityByRef(entityRef, {
token: ctx.secrets?.backstageToken,
});
} catch (e) {
if (!optional) {
throw e;
}
}
ctx.output('entity', entity ?? null);
},
});
}
@@ -16,3 +16,4 @@
export { createCatalogRegisterAction } from './register';
export { createCatalogWriteAction } from './write';
export { createFetchCatalogEntityAction } from './fetch';
@@ -26,6 +26,7 @@ import { JsonObject } from '@backstage/types';
import {
createCatalogRegisterAction,
createCatalogWriteAction,
createFetchCatalogEntityAction,
} from './catalog';
import { TemplateFilter, TemplateGlobal } from '../../../lib';
@@ -160,6 +161,7 @@ export const createBuiltinActions = (
}),
createDebugLogAction(),
createCatalogRegisterAction({ catalogClient, integrations }),
createFetchCatalogEntityAction({ catalogClient }),
createCatalogWriteAction(),
createFilesystemDeleteAction(),
createFilesystemRenameAction(),