feat(catalog/bitbucketCloud): Add option to configure schedule via app-config.yaml

Signed-off-by: Patrick Jungermann <Patrick.Jungermann@gmail.com>
This commit is contained in:
Patrick Jungermann
2022-09-27 02:58:22 +02:00
parent d4fea86ea3
commit f66e696e7b
10 changed files with 180 additions and 21 deletions
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor
---
Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code.
Please find how to configure the schedule at the config at
https://backstage.io/docs/integrations/bitbucketCloud/discovery
+20 -5
View File
@@ -37,6 +37,7 @@ And then add the entity provider to your catalog builder:
+ builder.addEntityProvider(
+ BitbucketCloudEntityProvider.fromConfig(env.config, {
+ logger: env.logger,
+ // optional: alternatively, configure via app-config.yaml
+ schedule: env.scheduler.createScheduledTaskRunner({
+ frequency: { minutes: 30 },
+ timeout: { minutes: 3 },
@@ -67,24 +68,38 @@ catalog:
filters: # optional
projectKey: '^apis-.*$' # optional; RegExp
repoSlug: '^service-.*$' # optional; RegExp
schedule: # optional; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { minutes: 30 }
# supports ISO duration, "human duration" as used in code
timeout: { minutes: 3 }
workspace: workspace-name
```
> **Note:** It is possible but certainly not recommended to skip the provider ID level.
> If you do so, `default` will be used as provider ID.
- **catalogPath** _(optional)_:
- **`catalogPath`** _(optional)_:
Default: `/catalog-info.yaml`.
Path where to look for `catalog-info.yaml` files.
When started with `/`, it is an absolute path from the repo root.
It supports values as allowed by the `path` filter/modifier
[at Bitbucket Cloud's code search](https://confluence.atlassian.com/bitbucket/code-search-in-bitbucket-873876782.html#Search-Pathmodifier).
- **filters** _(optional)_:
- **projectKey** _(optional)_:
- **`filters`** _(optional)_:
- **`projectKey`** _(optional)_:
Regular expression used to filter results based on the project key.
- **repoSlug** _(optional)_:
- **`repoSlug`** _(optional)_:
Regular expression used to filter results based on the repo slug.
- **workspace**:
- **`schedule`** _(optional)_:
- **`frequency`**:
How often you want the task to run. The system does its best to avoid overlapping invocations.
- **`timeout`**:
The maximum amount of time that a single task invocation can take.
- **`initialDelay`** _(optional)_:
The amount of time that should pass before the first invocation happens.
- **`scope`** _(optional)_:
`'global'` or `'local'`. Sets the scope of concurrency control.
- **`workspace`**:
Name of your organization account/workspace.
If you want to add multiple workspaces, you need to add one provider config each.
@@ -7,6 +7,7 @@ import { Config } from '@backstage/config';
import { EntityProvider } from '@backstage/plugin-catalog-backend';
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
import { Logger } from 'winston';
import { PluginTaskScheduler } from '@backstage/backend-tasks';
import { TaskRunner } from '@backstage/backend-tasks';
// @public
@@ -18,7 +19,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
config: Config,
options: {
logger: Logger;
schedule: TaskRunner;
schedule?: TaskRunner;
scheduler?: PluginTaskScheduler;
},
): BitbucketCloudEntityProvider[];
// (undocumented)
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks';
export interface Config {
catalog?: {
/**
@@ -53,6 +55,10 @@ export interface Config {
*/
projectKey?: RegExp;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
}
| Record<
string,
@@ -83,6 +89,10 @@ export interface Config {
*/
projectKey?: RegExp;
};
/**
* (Optional) TaskScheduleDefinition for the discovery.
*/
schedule?: TaskScheduleDefinitionConfig;
}
>;
};
@@ -44,6 +44,7 @@
"@backstage/backend-common": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"luxon": "^3.0.0",
"msw": "^0.47.0"
},
"files": [
@@ -15,7 +15,11 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks';
import {
PluginTaskScheduler,
TaskInvocationDefinition,
TaskRunner,
} from '@backstage/backend-tasks';
import { ConfigReader } from '@backstage/config';
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
@@ -77,6 +81,75 @@ describe('BitbucketCloudEntityProvider', () => {
);
});
it('fail without schedule and scheduler', () => {
const config = new ConfigReader({
catalog: {
providers: {
bitbucketCloud: {
workspace: 'test-ws',
},
},
},
});
expect(() =>
BitbucketCloudEntityProvider.fromConfig(config, {
logger,
}),
).toThrow('Either schedule or scheduler must be provided.');
});
it('fail with scheduler but no schedule config', () => {
const scheduler = jest.fn() as unknown as PluginTaskScheduler;
const config = new ConfigReader({
catalog: {
providers: {
bitbucketCloud: {
workspace: 'test-ws',
},
},
},
});
expect(() =>
BitbucketCloudEntityProvider.fromConfig(config, {
logger,
scheduler,
}),
).toThrow(
'No schedule provided neither via code nor config for bitbucketCloud-provider:default.',
);
});
it('single simple provider config with schedule in config', () => {
const scheduler = {
createScheduledTaskRunner: (_: any) => jest.fn(),
} as unknown as PluginTaskScheduler;
const config = new ConfigReader({
catalog: {
providers: {
bitbucketCloud: {
workspace: 'test-ws',
schedule: {
frequency: 'PT30M',
timeout: 'PT3M',
},
},
},
},
});
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
logger,
scheduler,
});
expect(providers).toHaveLength(1);
expect(providers[0].getProviderName()).toEqual(
'bitbucketCloud-provider:default',
);
});
it('multiple provider configs', () => {
const schedule = new PersistingTaskRunner();
const config = new ConfigReader({
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { TaskRunner } from '@backstage/backend-tasks';
import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
import {
BitbucketCloudIntegration,
@@ -58,7 +58,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
config: Config,
options: {
logger: Logger;
schedule: TaskRunner;
schedule?: TaskRunner;
scheduler?: PluginTaskScheduler;
},
): BitbucketCloudEntityProvider[] {
const integrations = ScmIntegrations.fromConfig(config);
@@ -69,29 +70,42 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
throw new Error('No integration for bitbucket.org available');
}
return readProviderConfigs(config).map(
providerConfig =>
new BitbucketCloudEntityProvider(
providerConfig,
integration,
options.logger,
options.schedule,
),
);
if (!options.schedule && !options.scheduler) {
throw new Error('Either schedule or scheduler must be provided.');
}
return readProviderConfigs(config).map(providerConfig => {
if (!options.schedule && !providerConfig.schedule) {
throw new Error(
`No schedule provided neither via code nor config for bitbucketCloud-provider:${providerConfig.id}.`,
);
}
const taskRunner =
options.schedule ??
options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!);
return new BitbucketCloudEntityProvider(
providerConfig,
integration,
options.logger,
taskRunner,
);
});
}
private constructor(
config: BitbucketCloudEntityProviderConfig,
integration: BitbucketCloudIntegration,
logger: Logger,
schedule: TaskRunner,
taskRunner: TaskRunner,
) {
this.client = BitbucketCloudClient.fromConfig(integration.config);
this.config = config;
this.logger = logger.child({
target: this.getProviderName(),
});
this.scheduleFn = this.createScheduleFn(schedule);
this.scheduleFn = this.createScheduleFn(taskRunner);
}
private createScheduleFn(schedule: TaskRunner): () => Promise<void> {
@@ -15,6 +15,7 @@
*/
import { ConfigReader } from '@backstage/config';
import { Duration } from 'luxon';
import { readProviderConfigs } from './BitbucketCloudEntityProviderConfig';
describe('readProviderConfigs', () => {
@@ -68,13 +69,22 @@ describe('readProviderConfigs', () => {
repoSlug: 'repoSlug.*filter',
},
},
providerWithSchedule: {
workspace: 'test-ws5',
schedule: {
frequency: 'PT30M',
timeout: {
minutes: 3,
},
},
},
},
},
},
});
const providerConfigs = readProviderConfigs(config);
expect(providerConfigs).toHaveLength(4);
expect(providerConfigs).toHaveLength(5);
expect(providerConfigs[0]).toEqual({
id: 'providerWorkspaceOnly',
workspace: 'test-ws1',
@@ -111,5 +121,20 @@ describe('readProviderConfigs', () => {
repoSlug: /^repoSlug.*filter$/,
},
});
expect(providerConfigs[4]).toEqual({
id: 'providerWithSchedule',
workspace: 'test-ws5',
catalogPath: '/catalog-info.yaml',
filters: {
projectKey: undefined,
repoSlug: undefined,
},
schedule: {
frequency: Duration.fromISO('PT30M'),
timeout: {
minutes: 3,
},
},
});
});
});
@@ -14,6 +14,10 @@
* limitations under the License.
*/
import {
readTaskScheduleDefinitionFromConfig,
TaskScheduleDefinition,
} from '@backstage/backend-tasks';
import { Config } from '@backstage/config';
const DEFAULT_CATALOG_PATH = '/catalog-info.yaml';
@@ -27,6 +31,7 @@ export type BitbucketCloudEntityProviderConfig = {
projectKey?: RegExp;
repoSlug?: RegExp;
};
schedule?: TaskScheduleDefinition;
};
export function readProviderConfigs(
@@ -61,6 +66,10 @@ function readProviderConfig(
const projectKeyPattern = config.getOptionalString('filters.projectKey');
const repoSlugPattern = config.getOptionalString('filters.repoSlug');
const schedule = config.has('schedule')
? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule'))
: undefined;
return {
id,
catalogPath,
@@ -71,6 +80,7 @@ function readProviderConfig(
: undefined,
repoSlug: repoSlugPattern ? compileRegExp(repoSlugPattern) : undefined,
},
schedule,
};
}
+1
View File
@@ -4462,6 +4462,7 @@ __metadata:
"@backstage/integration": "workspace:^"
"@backstage/plugin-bitbucket-cloud-common": "workspace:^"
"@backstage/plugin-catalog-backend": "workspace:^"
luxon: ^3.0.0
msw: ^0.47.0
uuid: ^8.0.0
winston: ^3.2.1