New plugin: "plugin-catalog-backend-module-gerrit"
The "plugin-catalog-backend-module-gerrit" exports an entity provider for discovering catalog entities from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get a list of repositories and will automatically ingest all "catalog-info.yaml" files stored in the root of the matching projects. Also added the "getGerritProjectsApiUrl" function to the Gerrit integration. This returns the url to the "List Projects" API for a given integration. Signed-off-by: Niklas Aronsson <niklasar@axis.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
Gerrit Integration: Added the `getGerritProjectsApiUrl` function
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gerrit': minor
|
||||
---
|
||||
|
||||
Initial version of the `plugin-catalog-backend-module-gerrit` plugin
|
||||
@@ -106,6 +106,7 @@ Firekube
|
||||
Firestore
|
||||
Fiverr
|
||||
Francesco
|
||||
gerrit
|
||||
Gerrit
|
||||
gitbeaker
|
||||
github
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: discovery
|
||||
title: Gerrit Discovery
|
||||
sidebar_label: Discovery
|
||||
# prettier-ignore
|
||||
description: Automatically discovering catalog entities from Gerrit repositories
|
||||
---
|
||||
|
||||
The Gerrit integration has a special entity provider for discovering catalog entities
|
||||
from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get
|
||||
a list of repositories and will automatically ingest all `catalog-info.yaml` files
|
||||
stored in the root of the matching projects.
|
||||
|
||||
## Installation
|
||||
|
||||
As this provider is not one of the default providers, you will first need to install
|
||||
the Gerrit provider plugin:
|
||||
|
||||
```bash
|
||||
# From the Backstage root directory
|
||||
yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-gerrit
|
||||
```
|
||||
|
||||
Then add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.ts`:
|
||||
|
||||
```ts
|
||||
/* packages/backend/src/plugins/catalog.ts */
|
||||
import { GerritEntityProvider } from '@backstage/plugin-catalog-backend-module-gerrit';
|
||||
import { Duration } from 'luxon';
|
||||
const builder = await CatalogBuilder.create(env);
|
||||
/** ... other processors and/or providers ... */
|
||||
builder.addEntityProvider(
|
||||
...GerritEntityProvider.fromConfig(env.config, {
|
||||
logger: env.logger,
|
||||
schedule: env.scheduler.createScheduledTaskRunner({
|
||||
frequency: Duration.fromObject({ minutes: 30 }),
|
||||
timeout: Duration.fromObject({ minutes: 3 }),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
To use the discovery processor, you'll need a Gerrit integration
|
||||
[set up](locations.md). Then you can add any number of providers.
|
||||
|
||||
```yaml
|
||||
# app-config.yaml
|
||||
catalog:
|
||||
providers:
|
||||
gerrit:
|
||||
yourProviderId: # identifies your dataset / provider independent of config changes
|
||||
host: gerrit-your-company.com
|
||||
branch: master # Optional
|
||||
query: 'state=ACTIVE&prefix=webapps'
|
||||
backend:
|
||||
host: gerrit-your-company.com
|
||||
branch: master # Optional
|
||||
query: 'state=ACTIVE&prefix=backend'
|
||||
```
|
||||
|
||||
The provider configuration is composed of three parts:
|
||||
|
||||
- host, the host of the Gerrit integration to use.
|
||||
- branch, the branch where we will look for catalog entities (defaults to "master").
|
||||
- query, this string is directly used as the argument to the "List Project" API.
|
||||
Typically you will want to have some filter here to exclude projects that will
|
||||
never contain any catalog files.
|
||||
@@ -156,7 +156,10 @@
|
||||
{
|
||||
"type": "subcategory",
|
||||
"label": "Gerrit",
|
||||
"ids": ["integrations/gerrit/locations"]
|
||||
"ids": [
|
||||
"integrations/gerrit/locations",
|
||||
"integrations/gerrit/discovery"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "subcategory",
|
||||
|
||||
@@ -101,6 +101,7 @@ nav:
|
||||
- Installation: 'integrations/datadog-rum/installation.md'
|
||||
- Gerrit:
|
||||
- Locations: 'integrations/gerrit/locations.md'
|
||||
- Discovery: 'integrations/gerrit/discovery.md'
|
||||
- GitHub:
|
||||
- Locations: 'integrations/github/locations.md'
|
||||
- Discovery: 'integrations/github/discovery.md'
|
||||
|
||||
@@ -191,6 +191,11 @@ export function getGerritFileContentsApiUrl(
|
||||
url: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function getGerritProjectsApiUrl(
|
||||
config: GerritIntegrationConfig,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function getGerritRequestOptions(config: GerritIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
|
||||
@@ -97,6 +97,7 @@ export function getAuthenticationPrefix(
|
||||
/**
|
||||
* Return the url to fetch the contents of a file using the Gerrit API.
|
||||
*
|
||||
* @param config - A Gerrit provider config.
|
||||
* @param url - An url pointing to a file in git.
|
||||
* @public
|
||||
*/
|
||||
@@ -113,6 +114,16 @@ export function getGerritFileContentsApiUrl(
|
||||
)}/branches/${branch}/files/${encodeURIComponent(filePath)}/content`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the url to query available projects using the Gerrit API.
|
||||
*
|
||||
* @param config - A Gerrit provider config.
|
||||
* @public
|
||||
*/
|
||||
export function getGerritProjectsApiUrl(config: GerritIntegrationConfig) {
|
||||
return `${config.baseUrl}${getAuthenticationPrefix(config)}projects/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return request headers for a Gerrit provider.
|
||||
*
|
||||
|
||||
@@ -20,6 +20,7 @@ export {
|
||||
} from './config';
|
||||
export {
|
||||
getGerritFileContentsApiUrl,
|
||||
getGerritProjectsApiUrl,
|
||||
getGerritRequestOptions,
|
||||
parseGerritJsonResponse,
|
||||
} from './core';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,8 @@
|
||||
# Catalog Backend Module for Gerrit
|
||||
|
||||
This is an extension module to the plugin-catalog-backend plugin, providing extensions targeted at Gerrit integrations.
|
||||
|
||||
## Getting started
|
||||
|
||||
See [Backstage documentation](https://backstage.io/docs/integrations/gerrit/discovery.md)
|
||||
for details on how to install and configure the plugin.
|
||||
@@ -0,0 +1,31 @@
|
||||
## API Report File for "@backstage/plugin-catalog-backend-module-gerrit"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { Config } from '@backstage/config';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-backend';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
|
||||
import { Logger } from 'winston';
|
||||
import { TaskRunner } from '@backstage/backend-tasks';
|
||||
|
||||
// @public (undocumented)
|
||||
export class GerritEntityProvider implements EntityProvider {
|
||||
// (undocumented)
|
||||
connect(connection: EntityProviderConnection): Promise<void>;
|
||||
// (undocumented)
|
||||
static fromConfig(
|
||||
configRoot: Config,
|
||||
options: {
|
||||
logger: Logger;
|
||||
schedule: TaskRunner;
|
||||
},
|
||||
): GerritEntityProvider[];
|
||||
// (undocumented)
|
||||
getProviderName(): string;
|
||||
// (undocumented)
|
||||
refresh(logger: Logger): Promise<void>;
|
||||
}
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
interface GerritConfig {
|
||||
/**
|
||||
* (Required) The host of the Gerrit integration to use.
|
||||
* @visibility backend
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* (Required) The query to use for the "List Projects" API call. Used to limit the
|
||||
* scope of the projects that the provider tries to ingest.
|
||||
* @visibility backend
|
||||
*/
|
||||
query: string;
|
||||
/**
|
||||
* (Optional) Branch.
|
||||
* The branch where the provider will try to find entities. Defaults to "master".
|
||||
* @visibility backend
|
||||
*/
|
||||
branch?: string;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
catalog?: {
|
||||
/**
|
||||
* List of provider-specific options and attributes
|
||||
*/
|
||||
providers?: {
|
||||
/**
|
||||
* GerritEntityProvider configuration
|
||||
*
|
||||
* Maps provider id with configuration.
|
||||
*/
|
||||
gerrit?: Record<string, GerritConfig>;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@backstage/plugin-catalog-backend-module-gerrit",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "backend-plugin-module"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "plugins/catalog-backend-module-gerrit"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.13.2",
|
||||
"@backstage/backend-tasks": "^0.3.0",
|
||||
"@backstage/catalog-model": "^1.0.1",
|
||||
"@backstage/config": "^1.0.0",
|
||||
"@backstage/errors": "^1.0.0",
|
||||
"@backstage/integration": "^1.1.0",
|
||||
"@backstage/plugin-catalog-backend": "^1.1.0",
|
||||
"fs-extra": "10.0.1",
|
||||
"msw": "^0.35.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"uuid": "^8.0.0",
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-test-utils": "^0.1.23",
|
||||
"@backstage/cli": "^0.17.0",
|
||||
"@types/fs-extra": "^9.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"config.d.ts"
|
||||
],
|
||||
"configSchema": "config.d.ts"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { GerritEntityProvider } from './providers/GerritEntityProvider';
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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 { ConfigReader } from '@backstage/config';
|
||||
import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks';
|
||||
import { EntityProviderConnection } from '@backstage/plugin-catalog-backend';
|
||||
import { rest } from 'msw';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { GerritEntityProvider } from './GerritEntityProvider';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
const getJsonFixture = (fileName: string) =>
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve(__dirname, `__fixtures__/${fileName}`),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
|
||||
class PersistingTaskRunner implements TaskRunner {
|
||||
private tasks: TaskInvocationDefinition[] = [];
|
||||
|
||||
getTasks() {
|
||||
return this.tasks;
|
||||
}
|
||||
|
||||
run(task: TaskInvocationDefinition): Promise<void> {
|
||||
this.tasks.push(task);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('GerritEntityProvider', () => {
|
||||
beforeAll(() => server.listen());
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
server.resetHandlers();
|
||||
});
|
||||
afterAll(() => server.close());
|
||||
|
||||
const config = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
gerrit: {
|
||||
'active-training': {
|
||||
host: 'g.com',
|
||||
query: 'state=ACTIVE&prefix=training',
|
||||
branch: 'main',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
gerrit: [
|
||||
{
|
||||
host: 'g.com',
|
||||
baseUrl: 'https://g.com/gerrit',
|
||||
gitilesBaseUrl: 'https:/g.com/gitiles',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const schedule = new PersistingTaskRunner();
|
||||
|
||||
const entityProviderConnection: EntityProviderConnection = {
|
||||
applyMutation: jest.fn(),
|
||||
};
|
||||
|
||||
it('discovers projects from the api.', async () => {
|
||||
const repoBuffer = fs.readFileSync(
|
||||
path.resolve(__dirname, '__fixtures__/listProjectsBody.txt'),
|
||||
);
|
||||
const expected = getJsonFixture('expectedProviderEntities.json');
|
||||
|
||||
server.use(
|
||||
rest.get('https://g.com/gerrit/projects/', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.set('Content-Type', 'application/json'),
|
||||
ctx.body(repoBuffer),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const provider = GerritEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
expect(provider.getProviderName()).toEqual(
|
||||
'gerrit-provider:active-training',
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
expect(taskDef.id).toEqual('gerrit-provider:active-training:refresh');
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
|
||||
expect(entityProviderConnection.applyMutation).toBeCalledWith(expected);
|
||||
});
|
||||
|
||||
it('handles api errors.', async () => {
|
||||
const provider = GerritEntityProvider.fromConfig(config, {
|
||||
logger,
|
||||
schedule,
|
||||
})[0];
|
||||
|
||||
server.use(
|
||||
rest.get('https://g.com/gerrit/projects/', (_, res, ctx) =>
|
||||
res(ctx.status(500, 'Error!.')),
|
||||
),
|
||||
);
|
||||
|
||||
await provider.connect(entityProviderConnection);
|
||||
const taskDef = schedule.getTasks()[0];
|
||||
|
||||
await (taskDef.fn as () => Promise<void>)();
|
||||
expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('can create multiple providers from config.', async () => {
|
||||
const configTwoProviders = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
gerrit: {
|
||||
'active-g1': {
|
||||
host: 'gerrit1.com',
|
||||
query: 'state=ACTIVE',
|
||||
},
|
||||
'active-g2': {
|
||||
host: 'gerrit2.com',
|
||||
query: 'state=ACTIVE',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
gerrit: [
|
||||
{
|
||||
host: 'gerrit1.com',
|
||||
},
|
||||
{
|
||||
host: 'gerrit2.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const providers = GerritEntityProvider.fromConfig(configTwoProviders, {
|
||||
logger,
|
||||
schedule,
|
||||
});
|
||||
expect(providers).toHaveLength(2);
|
||||
expect(providers[0]).toBeInstanceOf(GerritEntityProvider);
|
||||
expect(providers[0].getProviderName()).toEqual('gerrit-provider:active-g1');
|
||||
expect(providers[1]).toBeInstanceOf(GerritEntityProvider);
|
||||
expect(providers[1].getProviderName()).toEqual('gerrit-provider:active-g2');
|
||||
});
|
||||
|
||||
it('throws if integration is missing.', () => {
|
||||
const configMissingIntegration = new ConfigReader({
|
||||
catalog: {
|
||||
providers: {
|
||||
gerrit: {
|
||||
'active-g2': {
|
||||
host: 'gerrit2.com',
|
||||
query: 'state=ACTIVE',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
integrations: {
|
||||
gerrit: [
|
||||
{
|
||||
host: 'gerrit1.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(() =>
|
||||
GerritEntityProvider.fromConfig(configMissingIntegration, {
|
||||
logger,
|
||||
schedule,
|
||||
}),
|
||||
).toThrow(/No gerrit integration/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 { TaskRunner } from '@backstage/backend-tasks';
|
||||
import { Config } from '@backstage/config';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import {
|
||||
EntityProvider,
|
||||
EntityProviderConnection,
|
||||
LocationSpec,
|
||||
locationSpecToLocationEntity,
|
||||
} from '@backstage/plugin-catalog-backend';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import {
|
||||
GerritIntegration,
|
||||
getGerritProjectsApiUrl,
|
||||
getGerritRequestOptions,
|
||||
parseGerritJsonResponse,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import * as uuid from 'uuid';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
import { readGerritConfigs } from './config';
|
||||
import { GerritProjectQueryResult, GerritProviderConfig } from './types';
|
||||
|
||||
/** @public */
|
||||
export class GerritEntityProvider implements EntityProvider {
|
||||
private readonly config: GerritProviderConfig;
|
||||
private readonly integration: GerritIntegration;
|
||||
private readonly logger: Logger;
|
||||
private readonly scheduleFn: () => Promise<void>;
|
||||
private connection?: EntityProviderConnection;
|
||||
|
||||
static fromConfig(
|
||||
configRoot: Config,
|
||||
options: {
|
||||
logger: Logger;
|
||||
schedule: TaskRunner;
|
||||
},
|
||||
): GerritEntityProvider[] {
|
||||
const providerConfigs = readGerritConfigs(configRoot);
|
||||
const integrations = ScmIntegrations.fromConfig(configRoot).gerrit;
|
||||
const providers: GerritEntityProvider[] = [];
|
||||
|
||||
providerConfigs.forEach(providerConfig => {
|
||||
const integration = integrations.byHost(providerConfig.host);
|
||||
if (!integration) {
|
||||
throw new InputError(
|
||||
`No gerrit integration found that matches host ${providerConfig.host}`,
|
||||
);
|
||||
}
|
||||
providers.push(
|
||||
new GerritEntityProvider(
|
||||
providerConfig,
|
||||
integration,
|
||||
options.logger,
|
||||
options.schedule,
|
||||
),
|
||||
);
|
||||
});
|
||||
return providers;
|
||||
}
|
||||
|
||||
private constructor(
|
||||
config: GerritProviderConfig,
|
||||
integration: GerritIntegration,
|
||||
logger: Logger,
|
||||
schedule: TaskRunner,
|
||||
) {
|
||||
this.config = config;
|
||||
this.integration = integration;
|
||||
this.logger = logger.child({
|
||||
target: this.getProviderName(),
|
||||
});
|
||||
this.scheduleFn = this.createScheduleFn(schedule);
|
||||
}
|
||||
|
||||
getProviderName(): string {
|
||||
return `gerrit-provider:${this.config.id}`;
|
||||
}
|
||||
|
||||
async connect(connection: EntityProviderConnection): Promise<void> {
|
||||
this.connection = connection;
|
||||
await this.scheduleFn();
|
||||
}
|
||||
|
||||
private createScheduleFn(schedule: TaskRunner): () => Promise<void> {
|
||||
return async () => {
|
||||
const taskId = `${this.getProviderName()}:refresh`;
|
||||
return schedule.run({
|
||||
id: taskId,
|
||||
fn: async () => {
|
||||
const logger = this.logger.child({
|
||||
class: GerritEntityProvider.prototype.constructor.name,
|
||||
taskId,
|
||||
taskInstanceId: uuid.v4(),
|
||||
});
|
||||
|
||||
try {
|
||||
await this.refresh(logger);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async refresh(logger: Logger): Promise<void> {
|
||||
if (!this.connection) {
|
||||
throw new Error('Gerrit discovery connection not initialized');
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
|
||||
const baseProjectApiUrl = getGerritProjectsApiUrl(this.integration.config);
|
||||
const projectQueryUrl = `${baseProjectApiUrl}?${this.config.query}`;
|
||||
try {
|
||||
response = await fetch(projectQueryUrl, {
|
||||
method: 'GET',
|
||||
...getGerritRequestOptions(this.integration.config),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to list Gerrit projects for query ${this.config.query}, ${e}`,
|
||||
);
|
||||
}
|
||||
const gerritProjectsResponse = (await parseGerritJsonResponse(
|
||||
response as any,
|
||||
)) as GerritProjectQueryResult;
|
||||
const projects = Object.keys(gerritProjectsResponse);
|
||||
|
||||
const locations = projects.map(project => this.createLocationSpec(project));
|
||||
await this.connection.applyMutation({
|
||||
type: 'full',
|
||||
entities: locations.map(location => ({
|
||||
locationKey: this.getProviderName(),
|
||||
entity: locationSpecToLocationEntity({ location }),
|
||||
})),
|
||||
});
|
||||
logger.info(`Found ${locations.length} locations.`);
|
||||
}
|
||||
|
||||
private createLocationSpec(project: string): LocationSpec {
|
||||
return {
|
||||
type: 'url',
|
||||
target: `${this.integration.config.gitilesBaseUrl}/${project}/+/refs/heads/${this.config.branch}/catalog-info.yaml`,
|
||||
presence: 'optional',
|
||||
};
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"entities": [
|
||||
{
|
||||
"entity": {
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "Location",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"backstage.io/managed-by-location": "url:https:/g.com/gitiles/training/gerrit/+/refs/heads/main/catalog-info.yaml",
|
||||
"backstage.io/managed-by-origin-location": "url:https:/g.com/gitiles/training/gerrit/+/refs/heads/main/catalog-info.yaml"
|
||||
},
|
||||
"name": "generated-acbbddb9193be55b61de6a7c1d39b3dcaaf27525"
|
||||
},
|
||||
"spec": {
|
||||
"presence": "optional",
|
||||
"target": "https:/g.com/gitiles/training/gerrit/+/refs/heads/main/catalog-info.yaml",
|
||||
"type": "url"
|
||||
}
|
||||
},
|
||||
"locationKey": "gerrit-provider:active-training"
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "Location",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"backstage.io/managed-by-location": "url:https:/g.com/gitiles/training/sample/+/refs/heads/main/catalog-info.yaml",
|
||||
"backstage.io/managed-by-origin-location": "url:https:/g.com/gitiles/training/sample/+/refs/heads/main/catalog-info.yaml"
|
||||
},
|
||||
"name": "generated-4b8debb1c2ffa8d58c0bdf3d75abcf2252494e65"
|
||||
},
|
||||
"spec": {
|
||||
"presence": "optional",
|
||||
"target": "https:/g.com/gitiles/training/sample/+/refs/heads/main/catalog-info.yaml",
|
||||
"type": "url"
|
||||
}
|
||||
},
|
||||
"locationKey": "gerrit-provider:active-training"
|
||||
}
|
||||
],
|
||||
"type": "full"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
)]}'
|
||||
{"training/gerrit":{"id":"training%2Fgerrit","state":"ACTIVE","web_links":[{"name":"browse","url":"","target":"_blank"}]},"training/sample":{"id":"training%2Fsample","state":"ACTIVE","web_links":[{"name":"browse","url":"","target":"_blank"}]}}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 { ConfigReader } from '@backstage/config';
|
||||
import { readGerritConfigs } from './config';
|
||||
|
||||
describe('readGerritConfigs', () => {
|
||||
it('reads all provider configs', () => {
|
||||
const provider1 = {
|
||||
host: 'gerrit1.com',
|
||||
query: 'state=ACTIVE',
|
||||
branch: 'main',
|
||||
};
|
||||
const provider2 = {
|
||||
host: 'gerrit2.com',
|
||||
query: 'state=ACTIVE',
|
||||
branch: 'main',
|
||||
};
|
||||
const config = {
|
||||
catalog: {
|
||||
providers: {
|
||||
gerrit: {
|
||||
'active-g1': provider1,
|
||||
'active-g2': provider2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actual = readGerritConfigs(new ConfigReader(config));
|
||||
|
||||
expect(actual).toHaveLength(2);
|
||||
expect(actual[0]).toEqual({ ...provider1, id: 'active-g1' });
|
||||
expect(actual[1]).toEqual({ ...provider2, id: 'active-g2' });
|
||||
});
|
||||
it('provides default values', () => {
|
||||
const provider = {
|
||||
host: 'gerrit1.com',
|
||||
query: 'state=ACTIVE',
|
||||
};
|
||||
const config = {
|
||||
catalog: {
|
||||
providers: {
|
||||
gerrit: {
|
||||
'active-g1': provider,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const actual = readGerritConfigs(new ConfigReader(config));
|
||||
expect(actual).toHaveLength(1);
|
||||
expect(actual[0]).toEqual({
|
||||
branch: 'master',
|
||||
id: 'active-g1',
|
||||
...provider,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 { Config } from '@backstage/config';
|
||||
import { GerritProviderConfig } from './types';
|
||||
|
||||
function readGerritConfig(id: string, config: Config): GerritProviderConfig {
|
||||
const branch = config.getOptionalString('branch') ?? 'master';
|
||||
const host = config.getString('host');
|
||||
const query = config.getString('query');
|
||||
|
||||
return {
|
||||
branch,
|
||||
host,
|
||||
id,
|
||||
query,
|
||||
};
|
||||
}
|
||||
|
||||
export function readGerritConfigs(config: Config): GerritProviderConfig[] {
|
||||
const configs: GerritProviderConfig[] = [];
|
||||
|
||||
const providerConfigs = config.getOptionalConfig('catalog.providers.gerrit');
|
||||
|
||||
if (!providerConfigs) {
|
||||
return configs;
|
||||
}
|
||||
|
||||
for (const id of providerConfigs.keys()) {
|
||||
configs.push(readGerritConfig(id, providerConfigs.getConfig(id)));
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type GerritProjectInfo = {
|
||||
id: string;
|
||||
name: string;
|
||||
parent?: string;
|
||||
state?: string;
|
||||
};
|
||||
|
||||
export type GerritProjectQueryResult = Record<string, GerritProjectInfo>;
|
||||
|
||||
export type GerritProviderConfig = {
|
||||
host: string;
|
||||
query: string;
|
||||
id: string;
|
||||
branch?: string;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
export {};
|
||||
Reference in New Issue
Block a user