Merge pull request #15682 from drodil/scaffolder_get_entity
feat: add Scaffolder action to get entity by reference
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Add Scaffolder action `catalog:fetch` to get entity by entity reference from catalog
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user