feat: migrating things to using catalogService and removing older exports

Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
benjdlambert
2025-06-04 15:49:52 +02:00
parent d72021a5ec
commit 04c0b15da7
24 changed files with 179 additions and 589 deletions
@@ -43,11 +43,11 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-catalog-node": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@backstage/types": "workspace:^",
"@octokit/webhooks": "^10.9.2",
@@ -3,9 +3,8 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogService } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
import { createPullRequest } from 'octokit-plugin-create-pull-request';
import { GithubCredentialsProvider } from '@backstage/integration';
@@ -111,8 +110,7 @@ export function createGithubDeployKeyAction(options: {
// @public
export function createGithubEnvironmentAction(options: {
integrations: ScmIntegrationRegistry;
catalogClient?: CatalogApi;
auth?: AuthService;
catalogService: CatalogService;
}): TemplateAction<
{
repoUrl: string;
@@ -16,11 +16,11 @@
import { createGithubEnvironmentAction } from './githubEnvironment';
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import yaml from 'yaml';
import { examples } from './gitHubEnvironment.examples';
import { CatalogApi } from '@backstage/catalog-client';
const mockOctokit = {
rest: {
@@ -42,9 +42,7 @@ const mockOctokit = {
},
},
};
const mockCatalogClient: Partial<CatalogApi> = {
getEntitiesByRefs: jest.fn(),
};
jest.mock('octokit', () => ({
Octokit: class {
constructor() {
@@ -52,9 +50,6 @@ jest.mock('octokit', () => ({
}
},
}));
jest.mock('@backstage/catalog-client', () => ({
CatalogClient: mockCatalogClient,
}));
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
@@ -72,6 +67,7 @@ describe('github:environment:create examples', () => {
let action: TemplateAction<any, any, any>;
const mockContext = createMockActionContext();
const mockCatalogService = catalogServiceMock.mock();
beforeEach(() => {
mockOctokit.rest.actions.getEnvironmentPublicKey.mockResolvedValue({
@@ -95,15 +91,17 @@ describe('github:environment:create examples', () => {
id: 2,
},
});
(mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({
mockCatalogService.getEntitiesByRefs.mockResolvedValue({
items: [
{
apiVersion: 'v1',
kind: 'User',
metadata: {
name: 'johndoe',
},
},
{
apiVersion: 'v1',
kind: 'Group',
metadata: {
name: 'team-a',
@@ -114,7 +112,7 @@ describe('github:environment:create examples', () => {
action = createGithubEnvironmentAction({
integrations,
catalogClient: mockCatalogClient as CatalogApi,
catalogService: mockCatalogService,
});
});
@@ -19,10 +19,10 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import { CatalogApi } from '@backstage/catalog-client';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { mockCredentials } from '@backstage/backend-test-utils';
import { Octokit } from 'octokit';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
const octokitMock = Octokit as unknown as jest.Mock;
@@ -47,18 +47,10 @@ const mockOctokit = {
},
};
const mockCatalogClient: Partial<CatalogApi> = {
getEntitiesByRefs: jest.fn(),
};
jest.mock('octokit', () => ({
Octokit: jest.fn(),
}));
jest.mock('@backstage/catalog-client', () => ({
CatalogClient: mockCatalogClient,
}));
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
describe('github:environment:create', () => {
@@ -72,14 +64,10 @@ describe('github:environment:create', () => {
});
const integrations = ScmIntegrations.fromConfig(config);
const mockCatalogService = catalogServiceMock.mock();
const credentials = mockCredentials.user();
const token = mockCredentials.service.token({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
let action: TemplateAction<any, any, any>;
const mockContext = createMockActionContext({
@@ -87,7 +75,6 @@ describe('github:environment:create', () => {
repoUrl: 'github.com?repo=repository&owner=owner',
name: 'envname',
},
secrets: { backstageToken: token },
});
beforeEach(() => {
@@ -114,15 +101,18 @@ describe('github:environment:create', () => {
id: 2,
},
});
(mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({
mockCatalogService.getEntitiesByRefs.mockResolvedValue({
items: [
{
apiVersion: '1',
kind: 'User',
metadata: {
name: 'johndoe',
},
},
{
apiVersion: '1',
kind: 'Group',
metadata: {
name: 'team-a',
@@ -133,8 +123,7 @@ describe('github:environment:create', () => {
action = createGithubEnvironmentAction({
integrations,
catalogClient: mockCatalogClient as CatalogApi,
auth: mockServices.auth(),
catalogService: mockCatalogService,
});
});
@@ -496,11 +485,11 @@ describe('github:environment:create', () => {
},
});
expect(mockCatalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
expect(mockCatalogService.getEntitiesByRefs).toHaveBeenCalledWith(
{
entityRefs: ['group:default/team-a', 'user:default/johndoe'],
},
{ token },
{ credentials },
);
expect(
@@ -24,9 +24,8 @@ import { getOctokitOptions } from '../util';
import { Octokit } from 'octokit';
import Sodium from 'libsodium-wrappers';
import { examples } from './gitHubEnvironment.examples';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { AuthService } from '@backstage/backend-plugin-api';
import { CatalogService } from '@backstage/plugin-catalog-node';
/**
* Creates an `github:environment:create` Scaffolder action that creates a Github Environment.
@@ -35,10 +34,9 @@ import { AuthService } from '@backstage/backend-plugin-api';
*/
export function createGithubEnvironmentAction(options: {
integrations: ScmIntegrationRegistry;
catalogClient?: CatalogApi;
auth?: AuthService;
catalogService: CatalogService;
}) {
const { integrations, catalogClient, auth } = options;
const { integrations, catalogService } = options;
// For more information on how to define custom actions, see
// https://backstage.io/docs/features/software-templates/writing-custom-actions
return createTemplateAction({
@@ -147,11 +145,6 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin
reviewers,
} = ctx.input;
const { token } = (await auth?.getPluginRequestToken({
onBehalfOf: await ctx.getInitiatorCredentials(),
targetPluginId: 'catalog',
})) ?? { token: ctx.secrets?.backstageToken };
// When environment creation step is executed right after a repo publish step, the repository might not be available immediately.
// Add a 2-second delay before initiating the steps in this action.
await new Promise(resolve => setTimeout(resolve, 2000));
@@ -190,12 +183,12 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin
if (reviewers) {
let reviewersEntityRefs: Array<Entity | undefined> = [];
// Fetch reviewers from Catalog
const catalogResponse = await catalogClient?.getEntitiesByRefs(
const catalogResponse = await catalogService?.getEntitiesByRefs(
{
entityRefs: reviewers,
},
{
token,
credentials: await ctx.getInitiatorCredentials(),
},
);
if (catalogResponse?.items?.length) {
@@ -39,8 +39,8 @@ import {
DefaultGithubCredentialsProvider,
ScmIntegrations,
} from '@backstage/integration';
import { CatalogClient } from '@backstage/catalog-client';
import { createHandleAutocompleteRequest } from './autocomplete/autocomplete';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
/**
* @public
@@ -54,17 +54,13 @@ export const githubModule = createBackendModule({
deps: {
scaffolder: scaffolderActionsExtensionPoint,
config: coreServices.rootConfig,
discovery: coreServices.discovery,
auth: coreServices.auth,
catalogService: catalogServiceRef,
autocomplete: scaffolderAutocompleteExtensionPoint,
},
async init({ scaffolder, config, discovery, auth, autocomplete }) {
async init({ scaffolder, config, autocomplete, catalogService }) {
const integrations = ScmIntegrations.fromConfig(config);
const githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
const catalogClient = new CatalogClient({
discoveryApi: discovery,
});
scaffolder.addActions(
createGithubActionsDispatchAction({
@@ -80,8 +76,7 @@ export const githubModule = createBackendModule({
}),
createGithubEnvironmentAction({
integrations,
catalogClient,
auth,
catalogService,
}),
createGithubIssuesLabelAction({
integrations,
-1
View File
@@ -64,7 +64,6 @@
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
+6 -25
View File
@@ -7,7 +7,7 @@ import { AuditorService } from '@backstage/backend-plugin-api';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
import { BackstageCredentials } from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import { CatalogService } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { Duration } from 'luxon';
@@ -16,7 +16,7 @@ import { HumanDuration } from '@backstage/types';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Knex } from 'knex';
import { Logger } from 'winston';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
import { PermissionRule } from '@backstage/plugin-permission-node';
import { PermissionRuleParams } from '@backstage/plugin-permission-common';
@@ -51,28 +51,10 @@ export type ActionPermissionRuleInput<
TParams
>;
// @public
export const createBuiltinActions: (
options: CreateBuiltInActionsOptions,
) => TemplateAction[];
// @public
export interface CreateBuiltInActionsOptions {
additionalTemplateFilters?: Record<string, TemplateFilter>;
// (undocumented)
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
auth?: AuthService;
catalogClient: CatalogApi;
config: Config;
integrations: ScmIntegrations;
reader: UrlReaderService;
}
// @public
export function createCatalogRegisterAction(options: {
catalogClient: CatalogApi;
catalogService: CatalogService;
integrations: ScmIntegrations;
auth?: AuthService;
}): TemplateAction<
| {
catalogInfoUrl: string;
@@ -124,8 +106,7 @@ export function createDebugLogAction(): TemplateAction<
// @public
export function createFetchCatalogEntityAction(options: {
catalogClient: CatalogApi;
auth?: AuthService;
catalogService: CatalogService;
}): TemplateAction<
{
entityRef?: string | undefined;
@@ -290,7 +271,7 @@ export type CreateWorkerOptions = {
actionRegistry: TemplateActionRegistry;
integrations: ScmIntegrations;
workingDirectory: string;
logger: Logger;
logger: LoggerService;
auditor?: AuditorService;
additionalTemplateFilters?: Record<string, TemplateFilter>;
concurrentTasksLimit?: number;
@@ -427,7 +408,7 @@ export class TaskManager implements TaskContext {
task: CurrentClaimedTask,
storage: TaskStore,
abortSignal: AbortSignal,
logger: Logger,
logger: LoggerService,
auth?: AuthService,
config?: Config,
additionalWorkspaceProviders?: Record<string, WorkspaceProvider>,
@@ -19,7 +19,7 @@ import {
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { ScmIntegrations } from '@backstage/integration';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';
import { catalogServiceRef } from '@backstage/plugin-catalog-node';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { TaskBroker, TemplateAction } from '@backstage/plugin-scaffolder-node';
import {
@@ -137,7 +137,7 @@ export const scaffolderPlugin = createBackendPlugin({
httpRouter: coreServices.httpRouter,
httpAuth: coreServices.httpAuth,
auditor: coreServices.auditor,
catalogClient: catalogServiceRef,
catalogService: catalogServiceRef,
events: eventsServiceRef,
},
async init({
@@ -149,7 +149,7 @@ export const scaffolderPlugin = createBackendPlugin({
auth,
httpRouter,
httpAuth,
catalogClient,
catalogService,
permissions,
events,
auditor,
@@ -191,8 +191,8 @@ export const scaffolderPlugin = createBackendPlugin({
createDebugLogAction(),
createWaitAction(),
// todo(blam): maybe these should be a -catalog module?
createCatalogRegisterAction({ catalogClient, integrations, auth }),
createFetchCatalogEntityAction({ catalogClient, auth }),
createCatalogRegisterAction({ catalogService, integrations }),
createFetchCatalogEntityAction({ catalogService }),
createCatalogWriteAction(),
createFilesystemDeleteAction(),
createFilesystemRenameAction(),
@@ -206,11 +206,10 @@ export const scaffolderPlugin = createBackendPlugin({
);
const router = await createRouter({
logger: log,
logger,
config,
database,
catalogClient,
reader,
catalogService,
lifecycle,
actions,
taskBroker,
@@ -19,7 +19,7 @@ import { Entity } from '@backstage/catalog-model';
import { createFetchCatalogEntityAction } from './fetch';
import { examples } from './fetch.examples';
import yaml from 'yaml';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { mockCredentials } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:fetch examples', () => {
@@ -31,27 +31,19 @@ describe('catalog:fetch examples', () => {
},
} as Entity;
const catalogClient = catalogServiceMock({ entities: [entity] });
const catalogService = catalogServiceMock({ entities: [entity] });
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
const credentials = mockCredentials.user();
const token = mockCredentials.service.token({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const mockContext = createMockActionContext({
secrets: { backstageToken: token },
});
const mockContext = createMockActionContext();
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(catalogClient, 'getEntityByRef');
jest.spyOn(catalogService, 'getEntityByRef');
});
describe('fetch single entity', () => {
@@ -61,9 +53,9 @@ describe('catalog:fetch examples', () => {
input: yaml.parse(examples[0].example).steps[0].input,
});
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'component:default/name',
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', entity);
});
@@ -17,7 +17,7 @@
import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils';
import { Entity } from '@backstage/catalog-model';
import { createFetchCatalogEntityAction } from './fetch';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { mockCredentials } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:fetch', () => {
@@ -31,14 +31,7 @@ describe('catalog:fetch', () => {
const credentials = mockCredentials.user();
const token = mockCredentials.service.token({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const mockContext = createMockActionContext({
secrets: { backstageToken: token },
});
const mockContext = createMockActionContext();
beforeEach(() => {
jest.resetAllMocks();
@@ -46,11 +39,10 @@ describe('catalog:fetch', () => {
describe('fetch single entity', () => {
it('should return entity from catalog', async () => {
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntityByRef');
const catalogService = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogService, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await action.handler({
@@ -60,21 +52,20 @@ describe('catalog:fetch', () => {
},
});
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', component);
});
it('should throw error if entity fetch fails from catalog and optional is false', async () => {
const catalogClient = catalogServiceMock.mock({
const catalogService = catalogServiceMock.mock({
getEntityByRef: () => Promise.reject(new Error('Not found')),
});
jest.spyOn(catalogClient, 'getEntityByRef');
jest.spyOn(catalogService, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await expect(
@@ -86,19 +77,18 @@ describe('catalog:fetch', () => {
}),
).rejects.toThrow('Not found');
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{ token },
{ credentials },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
it('should throw error if entity not in catalog and optional is false', async () => {
const catalogClient = catalogServiceMock({ entities: [] });
jest.spyOn(catalogClient, 'getEntityByRef');
const catalogService = catalogServiceMock({ entities: [] });
jest.spyOn(catalogService, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await expect(
@@ -110,9 +100,9 @@ describe('catalog:fetch', () => {
}),
).rejects.toThrow('Entity component:default/test not found');
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{ token },
{ credentials },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
@@ -125,11 +115,10 @@ describe('catalog:fetch', () => {
namespace: 'ns',
},
} as Entity;
const catalogClient = catalogServiceMock({ entities: [entity] });
jest.spyOn(catalogClient, 'getEntityByRef');
const catalogService = catalogServiceMock({ entities: [entity] });
jest.spyOn(catalogService, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await action.handler({
@@ -141,9 +130,9 @@ describe('catalog:fetch', () => {
},
});
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'group:ns/test',
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', entity);
});
@@ -151,11 +140,10 @@ describe('catalog:fetch', () => {
describe('fetch multiple entities', () => {
it('should return entities from catalog', async () => {
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const catalogService = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogService, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await action.handler({
@@ -165,19 +153,18 @@ describe('catalog:fetch', () => {
},
});
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['component:default/test'] },
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [component]);
});
it('should throw error if undefined is returned for some entity', async () => {
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const catalogService = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogService, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await expect(
@@ -190,21 +177,20 @@ describe('catalog:fetch', () => {
}),
).rejects.toThrow('Entity component:default/test2 not found');
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith(
{
entityRefs: ['component:default/test', 'component:default/test2'],
},
{ token },
{ credentials },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
it('should return null in case some of the entities not found and optional is true', async () => {
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const catalogService = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogService, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await action.handler({
@@ -215,9 +201,9 @@ describe('catalog:fetch', () => {
},
});
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['component:default/test', 'component:default/test2'] },
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [
component,
@@ -240,13 +226,12 @@ describe('catalog:fetch', () => {
},
kind: 'User',
} as Entity;
const catalogClient = catalogServiceMock({
const catalogService = catalogServiceMock({
entities: [entity1, entity2],
});
jest.spyOn(catalogClient, 'getEntitiesByRefs');
jest.spyOn(catalogService, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
catalogService,
});
await action.handler({
@@ -258,9 +243,9 @@ describe('catalog:fetch', () => {
},
});
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['group:ns/test', 'user:default/test'] },
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [
@@ -14,11 +14,10 @@
* limitations under the License.
*/
import { CatalogApi } from '@backstage/catalog-client';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import { examples } from './fetch.examples';
import { AuthService } from '@backstage/backend-plugin-api';
import { CatalogService } from '@backstage/plugin-catalog-node';
const id = 'catalog:fetch';
@@ -28,10 +27,9 @@ const id = 'catalog:fetch';
* @public
*/
export function createFetchCatalogEntityAction(options: {
catalogClient: CatalogApi;
auth?: AuthService;
catalogService: CatalogService;
}) {
const { catalogClient, auth } = options;
const { catalogService } = options;
return createTemplateAction({
id,
@@ -95,18 +93,13 @@ export function createFetchCatalogEntityAction(options: {
throw new Error('Missing entity reference or references');
}
const { token } = (await auth?.getPluginRequestToken({
onBehalfOf: await ctx.getInitiatorCredentials(),
targetPluginId: 'catalog',
})) ?? { token: ctx.secrets?.backstageToken };
if (entityRef) {
const entity = await catalogClient.getEntityByRef(
const entity = await catalogService.getEntityByRef(
stringifyEntityRef(
parseEntityRef(entityRef, { defaultKind, defaultNamespace }),
),
{
token,
credentials: await ctx.getInitiatorCredentials(),
},
);
@@ -117,7 +110,7 @@ export function createFetchCatalogEntityAction(options: {
}
if (entityRefs) {
const entities = await catalogClient.getEntitiesByRefs(
const entities = await catalogService.getEntitiesByRefs(
{
entityRefs: entityRefs.map(ref =>
stringifyEntityRef(
@@ -125,9 +118,7 @@ export function createFetchCatalogEntityAction(options: {
),
),
},
{
token,
},
{ credentials: await ctx.getInitiatorCredentials() },
);
const finalEntities = entities.items.map((e, i) => {
@@ -21,7 +21,7 @@ import { createCatalogRegisterAction } from './register';
import { Entity } from '@backstage/catalog-model';
import { examples } from './register.examples';
import yaml from 'yaml';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { mockCredentials } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:register', () => {
@@ -33,28 +33,22 @@ describe('catalog:register', () => {
}),
);
const catalogClient = catalogServiceMock.mock();
const catalogService = catalogServiceMock.mock();
const action = createCatalogRegisterAction({
integrations,
catalogClient,
auth: mockServices.auth(),
catalogService,
});
const credentials = mockCredentials.user();
const token = mockCredentials.service.token({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const mockContext = createMockActionContext();
beforeEach(() => {
jest.resetAllMocks();
});
it('should register location in catalog', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -76,16 +70,16 @@ describe('catalog:register', () => {
input: yaml.parse(examples[0].example).steps[0].input,
});
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
1,
{
type: 'url',
target:
'http://github.com/backstage/backstage/blob/master/catalog-info.yaml',
},
{ token },
{ credentials },
);
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
2,
{
dryRun: true,
@@ -93,7 +87,7 @@ describe('catalog:register', () => {
target:
'http://github.com/backstage/backstage/blob/master/catalog-info.yaml',
},
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith(
@@ -19,7 +19,7 @@ import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import { createCatalogRegisterAction } from './register';
import { Entity } from '@backstage/catalog-model';
import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { mockCredentials } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:register', () => {
@@ -31,21 +31,15 @@ describe('catalog:register', () => {
}),
);
const catalogClient = catalogServiceMock.mock();
const catalogService = catalogServiceMock.mock();
const action = createCatalogRegisterAction({
integrations,
catalogClient,
auth: mockServices.auth(),
catalogService,
});
const credentials = mockCredentials.user();
const token = mockCredentials.service.token({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const mockContext = createMockActionContext();
beforeEach(() => {
@@ -66,7 +60,7 @@ describe('catalog:register', () => {
});
it('should register location in catalog', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -90,22 +84,22 @@ describe('catalog:register', () => {
},
});
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
1,
{
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
2,
{
dryRun: true,
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith(
@@ -119,7 +113,7 @@ describe('catalog:register', () => {
});
it('should return entityRef with the Component entity and not the generated location', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -170,7 +164,7 @@ describe('catalog:register', () => {
});
it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -214,7 +208,7 @@ describe('catalog:register', () => {
});
it('should return entityRef with the first entity if no non-generated entities can be found', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -251,7 +245,7 @@ describe('catalog:register', () => {
});
it('should not return entityRef if there are no entities', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockResolvedValueOnce({
location: null as any,
entities: [],
@@ -273,7 +267,7 @@ describe('catalog:register', () => {
});
it('should ignore failures when dry running the location in the catalog if `optional` is set', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockRejectedValueOnce(new Error('Not found'))
.mockRejectedValueOnce(new Error('Not found'));
await action.handler({
@@ -284,22 +278,22 @@ describe('catalog:register', () => {
},
});
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
1,
{
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
2,
{
dryRun: true,
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith(
@@ -309,7 +303,7 @@ describe('catalog:register', () => {
});
it('should fetch entities when adding location in the catalog fails and `optional` is set', async () => {
catalogClient.addLocation
catalogService.addLocation
.mockRejectedValueOnce(new Error('Already registered'))
.mockResolvedValueOnce({
location: null as any,
@@ -331,22 +325,22 @@ describe('catalog:register', () => {
},
});
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
1,
{
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(catalogClient.addLocation).toHaveBeenNthCalledWith(
expect(catalogService.addLocation).toHaveBeenNthCalledWith(
2,
{
dryRun: true,
type: 'url',
target: 'http://foo/var',
},
{ token },
{ credentials },
);
expect(mockContext.output).toHaveBeenCalledWith(
@@ -16,11 +16,10 @@
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import { CatalogApi } from '@backstage/catalog-client';
import { stringifyEntityRef, Entity } from '@backstage/catalog-model';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { examples } from './register.examples';
import { AuthService } from '@backstage/backend-plugin-api';
import { CatalogService } from '@backstage/plugin-catalog-node';
const id = 'catalog:register';
@@ -29,11 +28,10 @@ const id = 'catalog:register';
* @public
*/
export function createCatalogRegisterAction(options: {
catalogClient: CatalogApi;
catalogService: CatalogService;
integrations: ScmIntegrations;
auth?: AuthService;
}) {
const { catalogClient, integrations, auth } = options;
const { catalogService, integrations } = options;
return createTemplateAction({
id,
@@ -99,19 +97,14 @@ export function createCatalogRegisterAction(options: {
ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
const { token } = (await auth?.getPluginRequestToken({
onBehalfOf: await ctx.getInitiatorCredentials(),
targetPluginId: 'catalog',
})) ?? { token: ctx.secrets?.backstageToken };
try {
// 1st try to register the location, this will throw an error if the location already exists (see catch)
await catalogClient.addLocation(
await catalogService.addLocation(
{
type: 'url',
target: catalogInfoUrl,
},
token ? { token } : {},
{ credentials: await ctx.getInitiatorCredentials() },
);
} catch (e) {
if (!input.optional) {
@@ -122,13 +115,13 @@ export function createCatalogRegisterAction(options: {
try {
// 2nd retry the registration as a dry run, this will not throw an error if the location already exists
const result = await catalogClient.addLocation(
const result = await catalogService.addLocation(
{
dryRun: true,
type: 'url',
target: catalogInfoUrl,
},
token ? { token } : {},
{ credentials: await ctx.getInitiatorCredentials() },
);
if (result.entities.length) {
@@ -1,268 +0,0 @@
/*
* 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 { Config } from '@backstage/config';
import {
DefaultGithubCredentialsProvider,
GithubCredentialsProvider,
ScmIntegrations,
} from '@backstage/integration';
import {
TemplateAction,
TemplateFilter,
TemplateGlobal,
} from '@backstage/plugin-scaffolder-node';
import {
createCatalogRegisterAction,
createCatalogWriteAction,
createFetchCatalogEntityAction,
} from './catalog';
import { createDebugLogAction, createWaitAction } from './debug';
import {
createFetchPlainAction,
createFetchPlainFileAction,
createFetchTemplateAction,
createFetchTemplateFileAction,
} from './fetch';
import {
createFilesystemDeleteAction,
createFilesystemReadDirAction,
createFilesystemRenameAction,
} from './filesystem';
import {
createGithubActionsDispatchAction,
createGithubAutolinksAction,
createGithubDeployKeyAction,
createGithubEnvironmentAction,
createGithubIssuesLabelAction,
createGithubRepoCreateAction,
createGithubRepoPushAction,
createGithubWebhookAction,
createPublishGithubAction,
createPublishGithubPullRequestAction,
} from '@backstage/plugin-scaffolder-backend-module-github';
import { createPublishAzureAction } from '@backstage/plugin-scaffolder-backend-module-azure';
import { createPublishBitbucketAction } from '@backstage/plugin-scaffolder-backend-module-bitbucket';
import {
createPublishBitbucketCloudAction,
createBitbucketPipelinesRunAction,
createPublishBitbucketCloudPullRequestAction,
} from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud';
import {
createPublishBitbucketServerAction,
createPublishBitbucketServerPullRequestAction,
} from '@backstage/plugin-scaffolder-backend-module-bitbucket-server';
import {
createPublishGerritAction,
createPublishGerritReviewAction,
} from '@backstage/plugin-scaffolder-backend-module-gerrit';
import {
createPublishGitlabAction,
createGitlabRepoPushAction,
createPublishGitlabMergeRequestAction,
} from '@backstage/plugin-scaffolder-backend-module-gitlab';
import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea';
import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api';
/**
* The options passed to {@link createBuiltinActions}
* @public
*/
export interface CreateBuiltInActionsOptions {
/**
* The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions.
*/
reader: UrlReaderService;
/**
* The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions.
*/
integrations: ScmIntegrations;
/**
* The {@link @backstage/catalog-client#CatalogApi} that will be used in the default actions.
*/
catalogClient: CatalogApi;
/**
* The {@link @backstage/backend-plugin-api#AuthService} that will be used in the default actions.
*/
auth?: AuthService;
/**
* The {@link @backstage/config#Config} that will be used in the default actions.
*/
config: Config;
/**
* Additional custom filters that will be passed to the nunjucks template engine for use in
* Template Manifests and also template skeleton files when using `fetch:template`.
*/
additionalTemplateFilters?: Record<string, TemplateFilter>;
additionalTemplateGlobals?: Record<string, TemplateGlobal>;
}
/**
* A function to generate create a list of default actions that the scaffolder provides.
* Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones
*
* TODO(blam): version 2 of the scaffolder shouldn't ship with the additional modules. We should ship the basics, and let people install
* modules for the providers they want to use.
* @public
* @returns A list of actions that can be used in the scaffolder
*
*/
export const createBuiltinActions = (
options: CreateBuiltInActionsOptions,
): TemplateAction[] => {
const {
reader,
integrations,
catalogClient,
auth,
config,
additionalTemplateFilters,
additionalTemplateGlobals,
} = options;
const githubCredentialsProvider: GithubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
const actions = [
createFetchPlainAction({
reader,
integrations,
}),
createFetchPlainFileAction({
reader,
integrations,
}),
createFetchTemplateAction({
integrations,
reader,
additionalTemplateFilters,
additionalTemplateGlobals,
}),
createFetchTemplateFileAction({
integrations,
reader,
additionalTemplateFilters,
additionalTemplateGlobals,
}),
createPublishGerritAction({
integrations,
config,
}),
createPublishGerritReviewAction({
integrations,
config,
}),
createPublishGiteaAction({
integrations,
config,
}),
createPublishGithubAction({
integrations,
config,
githubCredentialsProvider,
}),
createPublishGithubPullRequestAction({
integrations,
githubCredentialsProvider,
config,
}),
createPublishGitlabAction({
integrations,
config,
}),
createPublishGitlabMergeRequestAction({
integrations,
}),
createGitlabRepoPushAction({
integrations,
}),
createPublishBitbucketAction({
integrations,
config,
}),
createPublishBitbucketCloudAction({
integrations,
config,
}),
createPublishBitbucketCloudPullRequestAction({ integrations, config }),
createPublishBitbucketServerAction({
integrations,
config,
}),
createPublishBitbucketServerPullRequestAction({
integrations,
config,
}),
createPublishAzureAction({
integrations,
config,
}),
createDebugLogAction(),
createWaitAction(),
createCatalogRegisterAction({ catalogClient, integrations, auth }),
createFetchCatalogEntityAction({ catalogClient, auth }),
createCatalogWriteAction(),
createFilesystemDeleteAction(),
createFilesystemReadDirAction(),
createFilesystemRenameAction(),
createGithubActionsDispatchAction({
integrations,
githubCredentialsProvider,
}),
createGithubWebhookAction({
integrations,
githubCredentialsProvider,
}),
createGithubIssuesLabelAction({
integrations,
githubCredentialsProvider,
}),
createGithubRepoCreateAction({
integrations,
githubCredentialsProvider,
}),
createGithubRepoPushAction({
integrations,
config,
githubCredentialsProvider,
}),
createGithubEnvironmentAction({
integrations,
catalogClient,
}),
createGithubDeployKeyAction({
integrations,
}),
createGithubAutolinksAction({
integrations,
githubCredentialsProvider,
}),
createBitbucketPipelinesRunAction({
integrations,
}),
];
return actions as TemplateAction[];
};
@@ -15,7 +15,6 @@
*/
export * from './catalog';
export * from './createBuiltinActions';
export * from './debug';
export * from './fetch';
export * from './filesystem';
@@ -17,6 +17,7 @@
import {
AuditorService,
BackstageCredentials,
LoggerService,
} from '@backstage/backend-plugin-api';
import type { UserEntity } from '@backstage/catalog-model';
import { ScmIntegrations } from '@backstage/integration';
@@ -36,7 +37,6 @@ import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import { v4 as uuid } from 'uuid';
import { Logger } from 'winston';
import { TemplateActionRegistry } from '../actions';
import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner';
import { DecoratedActionsRegistry } from './DecoratedActionsRegistry';
@@ -61,7 +61,7 @@ interface DryRunResult {
/** @internal */
export type TemplateTesterCreateOptions = {
logger: Logger;
logger: LoggerService;
auditor?: AuditorService;
integrations: ScmIntegrations;
actionRegistry: TemplateActionRegistry;
@@ -18,6 +18,7 @@ import {
AuditorService,
AuthService,
BackstageCredentials,
LoggerService,
} from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
@@ -38,7 +39,6 @@ import {
Observable,
createDeferred,
} from '@backstage/types';
import { Logger } from 'winston';
import ObservableImpl from 'zen-observable';
import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService';
import { readDuration } from './helper';
@@ -71,7 +71,7 @@ export class TaskManager implements TaskContext {
task: CurrentClaimedTask,
storage: TaskStore,
abortSignal: AbortSignal,
logger: Logger,
logger: LoggerService,
auth?: AuthService,
config?: Config,
additionalWorkspaceProviders?: Record<string, WorkspaceProvider>,
@@ -100,7 +100,7 @@ export class TaskManager implements TaskContext {
private readonly task: CurrentClaimedTask,
private readonly storage: TaskStore,
private readonly signal: AbortSignal,
private readonly logger: Logger,
private readonly logger: LoggerService,
private readonly workspaceService: WorkspaceService,
private readonly auth?: AuthService,
) {}
@@ -269,7 +269,7 @@ export interface CurrentClaimedTask {
export class StorageTaskBroker implements TaskBroker {
constructor(
private readonly storage: TaskStore,
private readonly logger: Logger,
private readonly logger: LoggerService,
private readonly config?: Config,
private readonly auth?: AuthService,
private readonly additionalWorkspaceProviders?: Record<
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { AuditorService } from '@backstage/backend-plugin-api';
import { AuditorService, LoggerService } from '@backstage/backend-plugin-api';
import { assertError, stringifyError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import { PermissionEvaluator } from '@backstage/plugin-permission-common';
@@ -25,7 +25,6 @@ import {
TemplateGlobal,
} from '@backstage/plugin-scaffolder-node';
import PQueue from 'p-queue';
import { Logger } from 'winston';
import { TemplateActionRegistry } from '../actions';
import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner';
import { WorkflowRunner } from './types';
@@ -43,7 +42,7 @@ export type TaskWorkerOptions = {
};
concurrentTasksLimit: number;
permissions?: PermissionEvaluator;
logger?: Logger;
logger?: LoggerService;
auditor?: AuditorService;
gracefulShutdown?: boolean;
};
@@ -58,7 +57,7 @@ export type CreateWorkerOptions = {
actionRegistry: TemplateActionRegistry;
integrations: ScmIntegrations;
workingDirectory: string;
logger: Logger;
logger: LoggerService;
auditor?: AuditorService;
additionalTemplateFilters?: Record<string, TemplateFilter>;
/**
@@ -86,7 +85,7 @@ export type CreateWorkerOptions = {
*/
export class TaskWorker {
private taskQueue: PQueue;
private logger: Logger | undefined;
private logger: LoggerService | undefined;
private auditor: AuditorService | undefined;
private stopWorkers: boolean;
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { CatalogApi } from '@backstage/catalog-client';
import {
BackstageCredentials,
LoggerService,
} from '@backstage/backend-plugin-api';
import {
ANNOTATION_LOCATION,
ANNOTATION_SOURCE_LOCATION,
@@ -25,14 +28,14 @@ import {
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { assertError, InputError, NotFoundError } from '@backstage/errors';
import { CatalogService } from '@backstage/plugin-catalog-node';
import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common';
import fs from 'fs-extra';
import os from 'os';
import { Logger } from 'winston';
export async function getWorkingDirectory(
config: Config,
logger: Logger,
logger: LoggerService,
): Promise<string> {
if (!config.has('backend.workingDirectory')) {
return os.tmpdir();
@@ -89,16 +92,18 @@ export function getEntityBaseUrl(entity: Entity): string | undefined {
*/
export async function findTemplate(options: {
entityRef: CompoundEntityRef;
token?: string;
catalogApi: CatalogApi;
catalogService: CatalogService;
credentials: BackstageCredentials;
}): Promise<TemplateEntityV1beta3> {
const { entityRef, token, catalogApi } = options;
const { entityRef, catalogService, credentials } = options;
if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') {
throw new InputError(`Invalid kind, only 'Template' kind is supported`);
}
const template = await catalogApi.getEntityByRef(entityRef, { token });
const template = await catalogService.getEntityByRef(entityRef, {
credentials,
});
if (!template) {
throw new NotFoundError(
`Template ${stringifyEntityRef(entityRef)} not found`,
@@ -53,7 +53,6 @@ import {
createTemplateGlobalFunction,
createTemplateGlobalValue,
} from '@backstage/plugin-scaffolder-node/alpha';
import { UrlReaders } from '@backstage/backend-defaults/urlReader';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
import { EventsService } from '@backstage/plugin-events-node';
import { DatabaseService } from '@backstage/backend-plugin-api';
@@ -96,11 +95,6 @@ function createDatabase(): DatabaseService {
).forPlugin('scaffolder');
}
const mockUrlReader = UrlReaders.default({
logger: mockServices.logger.mock(),
config: new ConfigReader({}),
});
const config = new ConfigReader({});
describe.each([
@@ -188,7 +182,7 @@ describe.each([
let app: express.Express;
let loggerSpy: jest.SpyInstance;
let taskBroker: TaskBroker;
const catalogClient = catalogServiceMock.mock();
const catalogService = catalogServiceMock.mock();
const permissionApi = {
authorize: jest.fn(),
authorizeConditional: jest.fn(),
@@ -303,8 +297,7 @@ describe.each([
logger: logger,
config: new ConfigReader({}),
database: createDatabase(),
catalogClient,
reader: mockUrlReader,
catalogService,
taskBroker,
permissions: permissionApi,
auth,
@@ -315,7 +308,7 @@ describe.each([
});
app = express().use(router);
catalogClient.getEntityByRef.mockImplementation(async ref => {
catalogService.getEntityByRef.mockImplementation(async ref => {
const { kind } = parseEntityRef(ref);
if (kind.toLocaleLowerCase() === 'template') {
@@ -851,9 +844,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
directoryContents: [],
});
expect(catalogClient.getEntityByRef).toHaveBeenCalledTimes(1);
expect(catalogService.getEntityByRef).toHaveBeenCalledTimes(1);
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
expect(catalogService.getEntityByRef).toHaveBeenCalledWith(
'user:default/mock',
expect.anything(),
);
@@ -879,8 +872,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
logger: logger,
config: new ConfigReader({}),
database: createDatabase(),
catalogClient,
reader: mockUrlReader,
catalogService,
taskBroker,
permissions: permissionApi,
auth,
@@ -888,7 +880,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
});
app = express().use(router);
catalogClient.getEntityByRef.mockImplementation(async ref => {
catalogService.getEntityByRef.mockImplementation(async ref => {
const { kind } = parseEntityRef(ref);
if (kind.toLocaleLowerCase() === 'template') {
@@ -1649,8 +1641,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{
logger: loggerToWinstonLogger(mockServices.logger.mock()),
config: new ConfigReader({}),
database: createDatabase(),
catalogClient,
reader: mockUrlReader,
catalogService,
taskBroker,
permissions: permissionApi,
auth,
@@ -21,12 +21,11 @@ import {
DatabaseService,
HttpAuthService,
LifecycleService,
LoggerService,
PermissionsService,
resolveSafeChildPath,
SchedulerService,
UrlReaderService,
} from '@backstage/backend-plugin-api';
import { CatalogApi } from '@backstage/catalog-client';
import {
CompoundEntityRef,
Entity,
@@ -81,10 +80,8 @@ import { validate } from 'jsonschema';
import { Duration } from 'luxon';
import { pathToFileURL } from 'url';
import { v4 as uuid } from 'uuid';
import { Logger } from 'winston';
import { z } from 'zod';
import {
createBuiltinActions,
DatabaseTaskStore,
TaskWorker,
TemplateActionRegistry,
@@ -115,17 +112,17 @@ import {
isTemplatePermissionRuleInput,
TemplatePermissionRuleInput,
} from './permissions';
import { CatalogService } from '@backstage/plugin-catalog-node';
/**
* RouterOptions
*/
export interface RouterOptions {
logger: Logger;
logger: LoggerService;
config: Config;
reader: UrlReaderService;
lifecycle?: LifecycleService;
database: DatabaseService;
catalogClient: CatalogApi;
catalogService: CatalogService;
scheduler?: SchedulerService;
actions?: TemplateAction<any, any, any>[];
/**
@@ -180,9 +177,8 @@ export async function createRouter(
const {
logger: parentLogger,
config,
reader,
database,
catalogClient,
catalogService,
actions,
scheduler,
additionalTemplateFilters,
@@ -285,18 +281,7 @@ export async function createRouter(
workers.push(worker);
}
const actionsToRegister = Array.isArray(actions)
? actions
: createBuiltinActions({
integrations,
catalogClient,
reader,
config,
auth,
...templateExtensions,
});
actionsToRegister.forEach(action => actionRegistry.register(action));
actions?.forEach(action => actionRegistry.register(action));
const launchWorkers = () => workers.forEach(worker => worker.start());
@@ -370,16 +355,7 @@ export async function createRouter(
try {
const credentials = await httpAuth.credentials(req);
const { token } = await auth.getPluginRequestToken({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const template = await authorizeTemplate(
req.params,
token,
credentials,
);
const template = await authorizeTemplate(req.params, credentials);
const parameters = [template.spec.parameters ?? []].flat();
@@ -458,17 +434,12 @@ export async function createRouter(
permissionService: permissions,
});
const { token } = await auth.getPluginRequestToken({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const userEntityRef = auth.isPrincipal(credentials, 'user')
? credentials.principal.userEntityRef
: undefined;
const userEntity = userEntityRef
? await catalogClient.getEntityByRef(userEntityRef, { token })
? await catalogService.getEntityByRef(userEntityRef, { credentials })
: undefined;
let auditLog = `Scaffolding task for ${templateRef}`;
@@ -481,7 +452,6 @@ export async function createRouter(
const template = await authorizeTemplate(
{ kind, namespace, name },
token,
credentials,
);
@@ -529,7 +499,7 @@ export async function createRouter(
const secrets: InternalTaskSecrets = {
...req.body.secrets,
backstageToken: token,
backstageToken: (credentials as any).token,
__initiatorCredentials: JSON.stringify({
...credentials,
// credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly
@@ -887,17 +857,12 @@ export async function createRouter(
throw new InputError('Input template is not a template');
}
const { token } = await auth.getPluginRequestToken({
onBehalfOf: credentials,
targetPluginId: 'catalog',
});
const userEntityRef = auth.isPrincipal(credentials, 'user')
? credentials.principal.userEntityRef
: undefined;
const userEntity = userEntityRef
? await catalogClient.getEntityByRef(userEntityRef, { token })
? await catalogService.getEntityByRef(userEntityRef, { credentials })
: undefined;
const templateRef: string = `${template.kind}:${
@@ -963,7 +928,7 @@ export async function createRouter(
})),
secrets: {
...body.secrets,
...(token && { backstageToken: token }),
backstageToken: (credentials as any).token,
},
credentials,
});
@@ -1026,13 +991,12 @@ export async function createRouter(
async function authorizeTemplate(
entityRef: CompoundEntityRef,
token: string | undefined,
credentials: BackstageCredentials,
) {
const template = await findTemplate({
catalogApi: catalogClient,
catalogService,
entityRef,
token,
credentials,
});
if (!isSupportedTemplate(template)) {
+1 -2
View File
@@ -7601,12 +7601,12 @@ __metadata:
dependencies:
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/catalog-client": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/errors": "workspace:^"
"@backstage/integration": "workspace:^"
"@backstage/plugin-catalog-node": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/plugin-scaffolder-node-test-utils": "workspace:^"
"@backstage/types": "workspace:^"
@@ -7721,7 +7721,6 @@ __metadata:
"@backstage/backend-defaults": "workspace:^"
"@backstage/backend-plugin-api": "workspace:^"
"@backstage/backend-test-utils": "workspace:^"
"@backstage/catalog-client": "workspace:^"
"@backstage/catalog-model": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"