Merge pull request #14040 from Bonial-International-GmbH/pjungermann/msgraph/schedule-in-config
feat(catalog/msgraph): schedule via config + backend-plugin-api support
This commit is contained in:
@@ -85,6 +85,13 @@ catalog:
|
||||
# in order to add extra information to your groups that can be used on your custom groupTransformers
|
||||
# See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0
|
||||
select: ['id', 'displayName', 'description']
|
||||
schedule: # optional; same options as in TaskScheduleDefinition
|
||||
# supports cron, ISO duration, "human duration" as used in code
|
||||
frequency: { hours: 1 }
|
||||
# supports ISO duration, "human duration" as used in code
|
||||
timeout: { minutes: 50 }
|
||||
# supports ISO duration, "human duration" as used in code
|
||||
initialDelay: { seconds: 15},
|
||||
```
|
||||
|
||||
`user.filter` and `userGroupMember.filter` are mutually exclusive, only one can be provided. If both are provided, an error will be thrown.
|
||||
@@ -116,13 +123,21 @@ yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-msgraph
|
||||
+ builder.addEntityProvider(
|
||||
+ MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
|
||||
+ logger: env.logger,
|
||||
+ scheduler,
|
||||
+ }),
|
||||
+ );
|
||||
```
|
||||
|
||||
Instead of configuring the refresh schedule inside the config (per provider instance),
|
||||
you can define it in code (for all of them):
|
||||
|
||||
```diff
|
||||
- scheduler,
|
||||
+ schedule: env.scheduler.createScheduledTaskRunner({
|
||||
+ frequency: { hours: 1 },
|
||||
+ timeout: { minutes: 50 },
|
||||
+ initialDelay: { seconds: 15}
|
||||
+ initialDelay: { seconds: 15},
|
||||
+ }),
|
||||
+ }),
|
||||
+ );
|
||||
```
|
||||
|
||||
## Customize the Processor or Entity Provider
|
||||
@@ -161,10 +176,7 @@ export async function myGroupTransformer(
|
||||
builder.addEntityProvider(
|
||||
MicrosoftGraphOrgEntityProvider.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: { minutes: 5 },
|
||||
timeout: { minutes: 3 },
|
||||
}),
|
||||
scheduler,
|
||||
+ groupTransformer: myGroupTransformer,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CatalogProcessor } from '@backstage/plugin-catalog-backend';
|
||||
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend';
|
||||
import { Config } from '@backstage/config';
|
||||
@@ -12,8 +13,10 @@ import { GroupEntity } from '@backstage/catalog-model';
|
||||
import { LocationSpec } from '@backstage/plugin-catalog-backend';
|
||||
import { Logger } from 'winston';
|
||||
import * as MicrosoftGraph from '@microsoft/microsoft-graph-types';
|
||||
import { PluginTaskScheduler } from '@backstage/backend-tasks';
|
||||
import { Response as Response_2 } from 'node-fetch';
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { TaskScheduleDefinition } from '@backstage/backend-tasks';
|
||||
import { TokenCredential } from '@azure/identity';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
|
||||
@@ -131,6 +134,20 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
read(options?: { logger?: Logger }): Promise<void>;
|
||||
}
|
||||
|
||||
// @alpha
|
||||
export const microsoftGraphOrgEntityProviderCatalogModule: (
|
||||
options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions | undefined,
|
||||
) => BackendFeature;
|
||||
|
||||
// @alpha
|
||||
export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions {
|
||||
groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;
|
||||
organizationTransformer?:
|
||||
| OrganizationTransformer
|
||||
| Record<string, OrganizationTransformer>;
|
||||
userTransformer?: UserTransformer | Record<string, UserTransformer>;
|
||||
}
|
||||
|
||||
// @public @deprecated
|
||||
export interface MicrosoftGraphOrgEntityProviderLegacyOptions {
|
||||
groupTransformer?: GroupTransformer;
|
||||
@@ -147,7 +164,8 @@ export type MicrosoftGraphOrgEntityProviderOptions =
|
||||
| MicrosoftGraphOrgEntityProviderLegacyOptions
|
||||
| {
|
||||
logger: Logger;
|
||||
schedule: 'manual' | TaskRunner;
|
||||
schedule?: 'manual' | TaskRunner;
|
||||
scheduler?: PluginTaskScheduler;
|
||||
userTransformer?: UserTransformer | Record<string, UserTransformer>;
|
||||
groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;
|
||||
organizationTransformer?:
|
||||
@@ -202,6 +220,7 @@ export type MicrosoftGraphProviderConfig = {
|
||||
groupSearch?: string;
|
||||
groupSelect?: string[];
|
||||
queryMode?: 'basic' | 'advanced';
|
||||
schedule?: TaskScheduleDefinition;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
+13
-9
@@ -14,14 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks';
|
||||
|
||||
export interface Config {
|
||||
/**
|
||||
* Configuration options for the catalog plugin.
|
||||
*/
|
||||
catalog?: {
|
||||
/**
|
||||
* List of processor-specific options and attributes
|
||||
*/
|
||||
processors?: {
|
||||
/**
|
||||
* MicrosoftGraphOrgReaderProcessor configuration
|
||||
@@ -109,9 +105,7 @@ export interface Config {
|
||||
}>;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* List of provider-specific options and attributes
|
||||
*/
|
||||
|
||||
providers?: {
|
||||
/**
|
||||
* MicrosoftGraphOrgEntityProvider configuration.
|
||||
@@ -209,6 +203,11 @@ export interface Config {
|
||||
*/
|
||||
search?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* (Optional) TaskScheduleDefinition for the refresh.
|
||||
*/
|
||||
schedule?: TaskScheduleDefinitionConfig;
|
||||
}
|
||||
| Record<
|
||||
string,
|
||||
@@ -292,6 +291,11 @@ export interface Config {
|
||||
*/
|
||||
search?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* (Optional) TaskScheduleDefinition for the refresh.
|
||||
*/
|
||||
schedule?: TaskScheduleDefinitionConfig;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"alphaTypes": "dist/index.alpha.d.ts",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
@@ -23,20 +24,22 @@
|
||||
"backstage"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "backstage-cli package build",
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build --experimental-type-build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack",
|
||||
"clean": "backstage-cli package clean",
|
||||
"start": "backstage-cli package start"
|
||||
"clean": "backstage-cli package clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/identity": "^2.1.0",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/backend-tasks": "workspace:^",
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-catalog-backend": "workspace:^",
|
||||
"@backstage/plugin-catalog-node": "workspace:^",
|
||||
"@microsoft/microsoft-graph-types": "^2.6.0",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -51,11 +54,13 @@
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"luxon": "^3.0.0",
|
||||
"msw": "^0.47.0"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
"alpha",
|
||||
"config.d.ts",
|
||||
"dist"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
|
||||
@@ -20,5 +20,7 @@
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export * from './processors';
|
||||
export * from './microsoftGraph';
|
||||
export * from './processors';
|
||||
export { microsoftGraphOrgEntityProviderCatalogModule } from './service/MicrosoftGraphOrgEntityProviderCatalogModule';
|
||||
export type { MicrosoftGraphOrgEntityProviderCatalogModuleOptions } from './service/MicrosoftGraphOrgEntityProviderCatalogModule';
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { Duration } from 'luxon';
|
||||
import { readMicrosoftGraphConfig, readProviderConfigs } from './config';
|
||||
|
||||
describe('readMicrosoftGraphConfig', () => {
|
||||
@@ -172,6 +173,12 @@ describe('readProviderConfigs', () => {
|
||||
filter: 'securityEnabled eq false',
|
||||
select: ['id', 'displayName', 'description'],
|
||||
},
|
||||
schedule: {
|
||||
frequency: 'PT30M',
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -192,6 +199,12 @@ describe('readProviderConfigs', () => {
|
||||
groupExpand: 'member',
|
||||
groupSelect: ['id', 'displayName', 'description'],
|
||||
groupFilter: 'securityEnabled eq false',
|
||||
schedule: {
|
||||
frequency: Duration.fromISO('PT30M'),
|
||||
timeout: {
|
||||
minutes: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
expect(actual).toEqual(expected);
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
readTaskScheduleDefinitionFromConfig,
|
||||
TaskScheduleDefinition,
|
||||
} from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import { trimEnd } from 'lodash';
|
||||
|
||||
@@ -121,6 +125,11 @@ export type MicrosoftGraphProviderConfig = {
|
||||
* Some features like `$expand` are not available for advanced queries, though.
|
||||
*/
|
||||
queryMode?: 'basic' | 'advanced';
|
||||
|
||||
/**
|
||||
* Schedule configuration for refresh tasks.
|
||||
*/
|
||||
schedule?: TaskScheduleDefinition;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -296,6 +305,10 @@ export function readProviderConfig(
|
||||
throw new Error(`clientId must be provided when clientSecret is defined.`);
|
||||
}
|
||||
|
||||
const schedule = config.has('schedule')
|
||||
? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id,
|
||||
target,
|
||||
@@ -312,5 +325,6 @@ export function readProviderConfig(
|
||||
queryMode,
|
||||
userGroupMemberFilter,
|
||||
userGroupMemberSearch,
|
||||
schedule,
|
||||
};
|
||||
}
|
||||
|
||||
+207
-58
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskInvocationDefinition,
|
||||
TaskRunner,
|
||||
} from '@backstage/backend-tasks';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
@@ -43,10 +48,21 @@ const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock<
|
||||
Promise<{ users: UserEntity[]; groups: GroupEntity[] }>
|
||||
>;
|
||||
|
||||
describe('MicrosoftGraphOrgEntityProvider', () => {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
class PersistingTaskRunner implements TaskRunner {
|
||||
private tasks: TaskInvocationDefinition[] = [];
|
||||
|
||||
it('should apply mutation', async () => {
|
||||
getTasks() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
run(task: TaskInvocationDefinition): Promise<void> {
|
||||
this.tasks.push(task);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
describe('MicrosoftGraphOrgEntityProvider', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(MicrosoftGraphClient, 'create')
|
||||
.mockReturnValue({} as unknown as MicrosoftGraphClient);
|
||||
@@ -78,8 +94,65 @@ describe('MicrosoftGraphOrgEntityProvider', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
const config = {
|
||||
afterEach(() => jest.resetAllMocks());
|
||||
|
||||
const logger = getVoidLogger();
|
||||
const taskRunner = new PersistingTaskRunner();
|
||||
const scheduler = {
|
||||
createScheduledTaskRunner: (_: any) => taskRunner,
|
||||
} as unknown as PluginTaskScheduler;
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
|
||||
const expectedMutation = {
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': 'msgraph:customProviderId/u1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'msgraph:customProviderId/u1',
|
||||
},
|
||||
name: 'u1',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
},
|
||||
},
|
||||
locationKey: 'msgraph-org-provider:customProviderId',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location': 'msgraph:customProviderId/g1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'msgraph:customProviderId/g1',
|
||||
},
|
||||
name: 'g1',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
type: 'team',
|
||||
},
|
||||
},
|
||||
locationKey: 'msgraph-org-provider:customProviderId',
|
||||
},
|
||||
],
|
||||
type: 'full',
|
||||
};
|
||||
|
||||
it('should apply mutation - manual', async () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
@@ -92,67 +165,143 @@ describe('MicrosoftGraphOrgEntityProvider', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
refresh: jest.fn(),
|
||||
};
|
||||
const provider = MicrosoftGraphOrgEntityProvider.fromConfig(
|
||||
new ConfigReader(config),
|
||||
{
|
||||
logger: getVoidLogger(),
|
||||
schedule: 'manual',
|
||||
},
|
||||
)[0];
|
||||
|
||||
provider.connect(entityProviderConnection);
|
||||
});
|
||||
const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule: 'manual',
|
||||
})[0];
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
await provider.read();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
|
||||
entities: [
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'User',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'msgraph:customProviderId/u1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'msgraph:customProviderId/u1',
|
||||
},
|
||||
name: 'u1',
|
||||
},
|
||||
spec: {
|
||||
memberOf: [],
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith(
|
||||
expectedMutation,
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply mutation - schedule', async () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
customProviderId: {
|
||||
target: 'target',
|
||||
tenantId: 'tenantId',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
},
|
||||
},
|
||||
locationKey: 'msgraph-org-provider:customProviderId',
|
||||
},
|
||||
{
|
||||
entity: {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Group',
|
||||
metadata: {
|
||||
annotations: {
|
||||
'backstage.io/managed-by-location':
|
||||
'msgraph:customProviderId/g1',
|
||||
'backstage.io/managed-by-origin-location':
|
||||
'msgraph:customProviderId/g1',
|
||||
},
|
||||
name: 'g1',
|
||||
},
|
||||
spec: {
|
||||
children: [],
|
||||
type: 'team',
|
||||
},
|
||||
},
|
||||
locationKey: 'msgraph-org-provider:customProviderId',
|
||||
},
|
||||
],
|
||||
type: 'full',
|
||||
},
|
||||
});
|
||||
const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule: taskRunner,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = taskRunner.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith(
|
||||
expectedMutation,
|
||||
);
|
||||
});
|
||||
|
||||
it('should apply mutation - scheduler', async () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
customProviderId: {
|
||||
target: 'target',
|
||||
tenantId: 'tenantId',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
schedule: {
|
||||
frequency: 'PT30M',
|
||||
timeout: 'PT3M',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
scheduler,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = taskRunner.getTasks()[0];
|
||||
expect(taskDef.id).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId:refresh',
|
||||
);
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith(
|
||||
expectedMutation,
|
||||
);
|
||||
});
|
||||
|
||||
it('fail without schedule and scheduler', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
customProviderId: {
|
||||
target: 'target',
|
||||
tenantId: 'tenantId',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
}),
|
||||
).toThrow('Either schedule or scheduler must be provided');
|
||||
});
|
||||
|
||||
it('fail with scheduler but no schedule config', () => {
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
customProviderId: {
|
||||
target: 'target',
|
||||
tenantId: 'tenantId',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
scheduler,
|
||||
}),
|
||||
).toThrow(
|
||||
'No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:customProviderId',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+31
-12
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';
|
||||
import {
|
||||
ANNOTATION_LOCATION,
|
||||
ANNOTATION_ORIGIN_LOCATION,
|
||||
@@ -67,7 +67,13 @@ export type MicrosoftGraphOrgEntityProviderOptions =
|
||||
* {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner}
|
||||
* to enable automatic scheduling of tasks.
|
||||
*/
|
||||
schedule: 'manual' | TaskRunner;
|
||||
schedule?: 'manual' | TaskRunner;
|
||||
|
||||
/**
|
||||
* Scheduler used to schedule refreshes based on
|
||||
* the schedule config.
|
||||
*/
|
||||
scheduler?: PluginTaskScheduler;
|
||||
|
||||
/**
|
||||
* The function that transforms a user entry in msgraph to an entity.
|
||||
@@ -168,6 +174,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
];
|
||||
}
|
||||
|
||||
if (!options.schedule && !options.scheduler) {
|
||||
throw new Error('Either schedule or scheduler must be provided.');
|
||||
}
|
||||
|
||||
function getTransformer<T extends Function>(
|
||||
id: string,
|
||||
transformers?: T | Record<string, T>,
|
||||
@@ -180,6 +190,16 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
}
|
||||
|
||||
return readProviderConfigs(configRoot).map(providerConfig => {
|
||||
if (!options.schedule && !providerConfig.schedule) {
|
||||
throw new Error(
|
||||
`No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:${providerConfig.id}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const taskRunner =
|
||||
options.schedule ??
|
||||
options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
|
||||
|
||||
const provider = new MicrosoftGraphOrgEntityProvider({
|
||||
id: providerConfig.id,
|
||||
provider: providerConfig,
|
||||
@@ -197,7 +217,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
options.organizationTransformer,
|
||||
),
|
||||
});
|
||||
provider.schedule(options.schedule);
|
||||
|
||||
if (taskRunner !== 'manual') {
|
||||
provider.schedule(taskRunner);
|
||||
}
|
||||
|
||||
return provider;
|
||||
});
|
||||
@@ -238,7 +261,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
provider,
|
||||
});
|
||||
|
||||
result.schedule(options.schedule);
|
||||
if (options.schedule !== 'manual') {
|
||||
result.schedule(options.schedule);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -311,16 +336,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
|
||||
markCommitComplete();
|
||||
}
|
||||
|
||||
private schedule(
|
||||
schedule: MicrosoftGraphOrgEntityProviderOptions['schedule'],
|
||||
) {
|
||||
if (schedule === 'manual') {
|
||||
return;
|
||||
}
|
||||
|
||||
private schedule(taskRunner: TaskRunner) {
|
||||
this.scheduleFn = async () => {
|
||||
const id = `${this.getProviderName()}:refresh`;
|
||||
await schedule.run({
|
||||
await taskRunner.run({
|
||||
id,
|
||||
fn: async () => {
|
||||
const logger = this.options.logger.child({
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2022 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
configServiceRef,
|
||||
loggerServiceRef,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
PluginTaskScheduler,
|
||||
TaskScheduleDefinition,
|
||||
} from '@backstage/backend-tasks';
|
||||
import { startTestBackend } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import { Duration } from 'luxon';
|
||||
import { microsoftGraphOrgEntityProviderCatalogModule } from './MicrosoftGraphOrgEntityProviderCatalogModule';
|
||||
import { MicrosoftGraphOrgEntityProvider } from '../processors';
|
||||
|
||||
describe('awsS3EntityProviderCatalogModule', () => {
|
||||
it('should register provider at the catalog extension point', async () => {
|
||||
let addedProviders: Array<MicrosoftGraphOrgEntityProvider> | undefined;
|
||||
let usedSchedule: TaskScheduleDefinition | undefined;
|
||||
|
||||
const extensionPoint = {
|
||||
addEntityProvider: (providers: any) => {
|
||||
addedProviders = providers;
|
||||
},
|
||||
};
|
||||
const runner = jest.fn();
|
||||
const scheduler = {
|
||||
createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => {
|
||||
usedSchedule = schedule;
|
||||
return runner;
|
||||
},
|
||||
} as unknown as PluginTaskScheduler;
|
||||
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
microsoftGraphOrg: {
|
||||
customProviderId: {
|
||||
target: 'target',
|
||||
tenantId: 'tenantId',
|
||||
clientId: 'clientId',
|
||||
clientSecret: 'clientSecret',
|
||||
schedule: {
|
||||
frequency: 'PT30M',
|
||||
timeout: 'PT3M',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await startTestBackend({
|
||||
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
|
||||
services: [
|
||||
[configServiceRef, config],
|
||||
[loggerServiceRef, getVoidLogger()],
|
||||
[schedulerServiceRef, scheduler],
|
||||
],
|
||||
features: [microsoftGraphOrgEntityProviderCatalogModule()],
|
||||
});
|
||||
|
||||
expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M'));
|
||||
expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M'));
|
||||
expect(addedProviders?.length).toEqual(1);
|
||||
expect(addedProviders?.pop()?.getProviderName()).toEqual(
|
||||
'MicrosoftGraphOrgEntityProvider:customProviderId',
|
||||
);
|
||||
expect(runner).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
configServiceRef,
|
||||
createBackendModule,
|
||||
loggerServiceRef,
|
||||
loggerToWinstonLogger,
|
||||
schedulerServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node';
|
||||
import {
|
||||
GroupTransformer,
|
||||
OrganizationTransformer,
|
||||
UserTransformer,
|
||||
} from '../microsoftGraph';
|
||||
import { MicrosoftGraphOrgEntityProvider } from '../processors';
|
||||
|
||||
/**
|
||||
* Options for {@link microsoftGraphOrgEntityProviderCatalogModule}.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions {
|
||||
/**
|
||||
* The function that transforms a user entry in msgraph to an entity.
|
||||
* Optionally, you can pass separate transformers per provider ID.
|
||||
*/
|
||||
userTransformer?: UserTransformer | Record<string, UserTransformer>;
|
||||
|
||||
/**
|
||||
* The function that transforms a group entry in msgraph to an entity.
|
||||
* Optionally, you can pass separate transformers per provider ID.
|
||||
*/
|
||||
groupTransformer?: GroupTransformer | Record<string, GroupTransformer>;
|
||||
|
||||
/**
|
||||
* The function that transforms an organization entry in msgraph to an entity.
|
||||
* Optionally, you can pass separate transformers per provider ID.
|
||||
*/
|
||||
organizationTransformer?:
|
||||
| OrganizationTransformer
|
||||
| Record<string, OrganizationTransformer>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the MicrosoftGraphOrgEntityProvider with the catalog processing extension point.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const microsoftGraphOrgEntityProviderCatalogModule = createBackendModule(
|
||||
{
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'microsoftGraphOrgEntityProvider',
|
||||
register(
|
||||
env,
|
||||
options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions,
|
||||
) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
catalog: catalogProcessingExtensionPoint,
|
||||
config: configServiceRef,
|
||||
logger: loggerServiceRef,
|
||||
scheduler: schedulerServiceRef,
|
||||
},
|
||||
async init({ catalog, config, logger, scheduler }) {
|
||||
catalog.addEntityProvider(
|
||||
MicrosoftGraphOrgEntityProvider.fromConfig(config, {
|
||||
groupTransformer: options?.groupTransformer,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
organizationTransformer: options?.organizationTransformer,
|
||||
scheduler,
|
||||
userTransformer: options?.userTransformer,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user