chore: added simple get catalog entity

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-06-17 09:43:15 +02:00
parent 19774b2a55
commit f0ea16492f
3 changed files with 185 additions and 0 deletions
@@ -0,0 +1,78 @@
/*
* Copyright 2025 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 { createGetCatalogEntityAction } from './createGetCatalogEntityAction';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha';
describe('createGetCatalogEntityAction', () => {
it('should throw an error if the entity is not found', async () => {
const mockActionsRegistry = actionsRegistryServiceMock();
const mockCatalog = catalogServiceMock();
createGetCatalogEntityAction({
catalog: mockCatalog,
actionsRegistry: mockActionsRegistry,
});
await expect(
mockActionsRegistry.invoke({
id: 'test:get-catalog-entity',
input: { name: 'test' },
}),
).rejects.toThrow(`No entity found with name "test"`);
});
it('should throw an error if theres multiple entities found', async () => {
const mockActionsRegistry = actionsRegistryServiceMock();
const mockCatalog = catalogServiceMock({
entities: [
{
kind: 'Component',
apiVersion: 'backstage.io/v1alpha1',
metadata: {
name: 'test',
namespace: 'default',
},
spec: {
type: 'component',
},
},
{
kind: 'API',
apiVersion: 'backstage.io/v1alpha1',
metadata: {
name: 'test',
namespace: 'default',
},
},
],
});
createGetCatalogEntityAction({
catalog: mockCatalog,
actionsRegistry: mockActionsRegistry,
});
await expect(
mockActionsRegistry.invoke({
id: 'test:get-catalog-entity',
input: { name: 'test' },
}),
).rejects.toThrow(
`Multiple entities found with name "test", please provide more specific filters. Entities found: "component:default/test", "api:default/test"`,
);
});
});
@@ -0,0 +1,95 @@
/*
* Copyright 2025 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 { ActionsRegistryService } from '@backstage/backend-plugin-api/alpha';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { InputError } from '@backstage/errors';
import { CatalogService } from '@backstage/plugin-catalog-node';
export const createGetCatalogEntityAction = ({
catalog,
actionsRegistry,
}: {
catalog: CatalogService;
actionsRegistry: ActionsRegistryService;
}) => {
actionsRegistry.register({
name: 'get-catalog-entity',
title: 'Get Catalog Entity',
description: `
This allows you to get a single entity from the software catalog.
Each entity in the software catalog has a unique name, kind, and namespace. The default namespace is "default".
Each entity is identified by a unique entity reference, which is a string of the form "kind:namespace/name".
`,
schema: {
input: z =>
z.object({
kind: z
.string()
.describe(
'The kind of the entity to query. If the kind is unknown it can be omitted.',
)
.optional(),
namespace: z
.string()
.describe(
'The namespace of the entity to query. If the namespace is unknown it can be omitted.',
)
.optional(),
name: z.string().describe('The name of the entity to query'),
}),
// TODO: is there a better way to do this?
output: z => z.object({}).passthrough(),
},
action: async ({ input, credentials }) => {
const filter: Record<string, string> = { 'metadata.name': input.name };
if (input.kind) {
filter.kind = input.kind;
}
if (input.namespace) {
filter['metadata.namespace'] = input.namespace;
}
const { items } = await catalog.queryEntities(
{ filter },
{
credentials,
},
);
if (items.length === 0) {
throw new InputError(`No entity found with name "${input.name}"`);
}
if (items.length > 1) {
throw new Error(
`Multiple entities found with name "${
input.name
}", please provide more specific filters. Entities found: ${items
.map(item => `"${stringifyEntityRef(item)}"`)
.join(', ')}`,
);
}
const [entity] = items;
return {
output: entity,
};
},
});
};
@@ -22,6 +22,7 @@ import { ForwardedError } from '@backstage/errors';
import {
CatalogProcessor,
CatalogProcessorParser,
catalogServiceRef,
EntityProvider,
LocationAnalyzer,
PlaceholderResolver,
@@ -43,6 +44,8 @@ import { eventsServiceRef } from '@backstage/plugin-events-node';
import { Permission } from '@backstage/plugin-permission-common';
import { merge } from 'lodash';
import { CatalogBuilder } from './CatalogBuilder';
import { actionsRegistryServiceRef } from '@backstage/backend-plugin-api/alpha';
import { createGetCatalogEntityAction } from '../actions/createGetCatalogEntityAction';
class CatalogLocationsExtensionPointImpl
implements CatalogLocationsExtensionPoint
@@ -237,6 +240,8 @@ export const catalogPlugin = createBackendPlugin({
httpAuth: coreServices.httpAuth,
auditor: coreServices.auditor,
events: eventsServiceRef,
catalog: catalogServiceRef,
actionsRegistry: actionsRegistryServiceRef,
},
async init({
logger,
@@ -250,6 +255,8 @@ export const catalogPlugin = createBackendPlugin({
scheduler,
auth,
httpAuth,
catalog,
actionsRegistry,
auditor,
events,
}) {
@@ -313,6 +320,11 @@ export const catalogPlugin = createBackendPlugin({
}
httpRouter.use(router);
createGetCatalogEntityAction({
catalog,
actionsRegistry,
});
},
});
},