Adds gitea to supported set of SCM integrations
Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> GiteaUrlReader readUrl implementation. Currently relies on content being base64 encoded Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Prettify changed files Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Changeset for gitea integration Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Include gitea in Vale vocabulary, run Lint / Prettier Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Update api-reports Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Lint packages/integration/* Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Fix getGiteaFileContentsUrl, split changesets The previous file contents url code was missing a segment, (/branch), and was causing the loader to 404. (The intended functionality is to use the same URL you would use to view the file in gitea) Changesets for the integration and backend-common packages were split This commit also adds documentation relevant to the gitea integration Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com> Signed-off-by: Pedro Cardona <1724279+atoko@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': minor
|
||||
---
|
||||
|
||||
This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api
|
||||
@@ -121,6 +121,8 @@ github
|
||||
Gitiles
|
||||
gitlab
|
||||
GitLab
|
||||
gitea
|
||||
Gitea
|
||||
Gource
|
||||
Grafana
|
||||
graphql
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: locations
|
||||
title: Gitea Locations
|
||||
sidebar_label: Locations
|
||||
description: Integrating source code stored in Gitea into the Backstage catalog
|
||||
---
|
||||
|
||||
The Gitea integration supports loading catalog entities from a hosted repository. Entities can be added to
|
||||
[static catalog configuration](../../features/software-catalog/configuration.md),
|
||||
registered with the
|
||||
[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import)
|
||||
plugin.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use this integration, add configuration to your root `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
integrations:
|
||||
gitea:
|
||||
- host: gitea.example.com
|
||||
password: ${GITEA_TOKEN}
|
||||
- host: gitea.example.com
|
||||
username: ${GITEA_USERNAME}
|
||||
password: ${GITEA_PASSWORD}
|
||||
```
|
||||
|
||||
Directly under the `gitea` key is a list of provider configurations, where you
|
||||
can list the Gitea instances you want to be able to fetch
|
||||
data from. Each entry is a structure with up to four elements:
|
||||
|
||||
- `host`: The host of the gitea instance that you want to match on.
|
||||
- `baseUrl` (optional): Needed if the Gitea instance is not reachable at
|
||||
the base of the `host` option (e.g. `https://git.company.com/gitea`). This is the address that you would open in a browser.
|
||||
- `username` (optional): The gitea username to use in API requests.
|
||||
- `password` (optional): The password or api token to authenticate with.
|
||||
|
||||
You may supply only the `password` field, if authenticating via API access tokens (generated in Settings > Applications).
|
||||
@@ -21,6 +21,7 @@ import { Duration } from 'luxon';
|
||||
import { ErrorRequestHandler } from 'express';
|
||||
import express from 'express';
|
||||
import { GerritIntegration } from '@backstage/integration';
|
||||
import { GiteaIntegration } from '@backstage/integration';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
import { GithubIntegration } from '@backstage/integration';
|
||||
import { GitLabIntegration } from '@backstage/integration';
|
||||
@@ -406,6 +407,23 @@ export class Git {
|
||||
resolveRef(options: { dir: string; ref: string }): Promise<string>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class GiteaUrlReader implements UrlReader {
|
||||
constructor(integration: GiteaIntegration);
|
||||
// (undocumented)
|
||||
static factory: ReaderFactory;
|
||||
// (undocumented)
|
||||
read(url: string): Promise<Buffer>;
|
||||
// (undocumented)
|
||||
readTree(): Promise<ReadTreeResponse>;
|
||||
// (undocumented)
|
||||
readUrl(url: string, options?: ReadUrlOptions): Promise<ReadUrlResponse>;
|
||||
// (undocumented)
|
||||
search(): Promise<SearchResponse>;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class GithubUrlReader implements UrlReader {
|
||||
constructor(
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { GiteaIntegration, readGiteaConfig } from '@backstage/integration';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { getVoidLogger } from '../logging';
|
||||
import { UrlReaderPredicateTuple } from './types';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import getRawBody from 'raw-body';
|
||||
import { GiteaUrlReader } from './GiteaUrlReader';
|
||||
import { NotFoundError } from '@backstage/errors';
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
jest.mock('../scm', () => ({
|
||||
Git: {
|
||||
fromAuth: () => ({
|
||||
clone: jest.fn(() => Promise.resolve({})),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
const giteaProcessor = new GiteaUrlReader(
|
||||
new GiteaIntegration(
|
||||
readGiteaConfig(
|
||||
new ConfigReader({
|
||||
host: 'gitea.com',
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
|
||||
return GiteaUrlReader.factory({
|
||||
config: new ConfigReader(config),
|
||||
logger: getVoidLogger(),
|
||||
treeResponseFactory,
|
||||
});
|
||||
};
|
||||
|
||||
describe('GiteaUrlReader', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('reader factory', () => {
|
||||
it('creates a reader.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
gitea: [{ host: 'gitea.com' }],
|
||||
},
|
||||
});
|
||||
expect(readers).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not create a default entry.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {},
|
||||
});
|
||||
expect(readers).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('predicates', () => {
|
||||
it('returns true for the configured host', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
gitea: [{ host: 'gitea.com' }],
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
|
||||
expect(predicate(new URL('https://gitea.com/path'))).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for a different host.', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
gitea: [{ host: 'gitea.com' }],
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
|
||||
expect(predicate(new URL('https://github.com/path'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('readUrl', () => {
|
||||
const responseBuffer = Buffer.from('Apache License');
|
||||
const giteaApiResponse = (content: any) => {
|
||||
return JSON.stringify({
|
||||
encoding: 'base64',
|
||||
content: Buffer.from(content).toString('base64'),
|
||||
});
|
||||
};
|
||||
|
||||
it('should be able to read file contents as buffer', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE',
|
||||
(req, res, ctx) => {
|
||||
// Test utils prefers matching URL directly but it is part of Gitea's API
|
||||
if (req.url.searchParams.get('ref') === 'branch2') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(giteaApiResponse(responseBuffer.toString())),
|
||||
);
|
||||
}
|
||||
|
||||
return res(ctx.status(500));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await giteaProcessor.readUrl(
|
||||
'https://gitea.com/owner/project/src/branch/branch2/LICENSE',
|
||||
);
|
||||
const buffer = await result.buffer();
|
||||
expect(buffer.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
|
||||
it('should be able to read file contents as stream', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE',
|
||||
(req, res, ctx) => {
|
||||
if (req.url.searchParams.get('ref') === 'branch2') {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.body(giteaApiResponse(responseBuffer.toString())),
|
||||
);
|
||||
}
|
||||
|
||||
return res(ctx.status(500));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const result = await giteaProcessor.readUrl(
|
||||
'https://gitea.com/owner/project/src/branch/branch2/LICENSE',
|
||||
);
|
||||
const fromStream = await getRawBody(result.stream!());
|
||||
expect(fromStream.toString()).toBe(responseBuffer.toString());
|
||||
});
|
||||
|
||||
it('should raise NotFoundError on 404.', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE',
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(404, 'File not found.'));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
giteaProcessor.readUrl(
|
||||
'https://gitea.com/owner/project/src/branch/branch2/LICENSE',
|
||||
),
|
||||
).rejects.toThrow(NotFoundError);
|
||||
});
|
||||
|
||||
it('should throw an error on non 404 errors.', async () => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE',
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(500, 'Error!!!'));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
await expect(
|
||||
giteaProcessor.readUrl(
|
||||
'https://gitea.com/owner/project/src/branch/branch2/LICENSE',
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'https://gitea.com/owner/project/src/branch/branch2/LICENSE could not be read as https://gitea.com/api/v1/repos/owner/project/contents/LICENSE?ref=branch2, 500 Error!!!',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 {
|
||||
getGiteaRequestOptions,
|
||||
getGiteaFileContentsUrl,
|
||||
GiteaIntegration,
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { ReadUrlOptions, ReadUrlResponse } from './types';
|
||||
import {
|
||||
ReaderFactory,
|
||||
ReadTreeResponse,
|
||||
SearchResponse,
|
||||
UrlReader,
|
||||
} from './types';
|
||||
import fetch, { Response } from 'node-fetch';
|
||||
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
import {
|
||||
AuthenticationError,
|
||||
NotFoundError,
|
||||
NotModifiedError,
|
||||
} from '@backstage/errors';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
/**
|
||||
* Implements a {@link UrlReader} for the Gitea v1 api.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GiteaUrlReader implements UrlReader {
|
||||
static factory: ReaderFactory = ({ config }) => {
|
||||
return ScmIntegrations.fromConfig(config)
|
||||
.gitea.list()
|
||||
.map(integration => {
|
||||
const reader = new GiteaUrlReader(integration);
|
||||
const predicate = (url: URL) => {
|
||||
return url.host === integration.config.host;
|
||||
};
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
constructor(private readonly integration: GiteaIntegration) {}
|
||||
|
||||
async read(url: string): Promise<Buffer> {
|
||||
const response = await this.readUrl(url);
|
||||
return response.buffer();
|
||||
}
|
||||
|
||||
async readUrl(
|
||||
url: string,
|
||||
options?: ReadUrlOptions,
|
||||
): Promise<ReadUrlResponse> {
|
||||
let response: Response;
|
||||
const blobUrl = getGiteaFileContentsUrl(this.integration.config, url);
|
||||
|
||||
try {
|
||||
response = await fetch(blobUrl, {
|
||||
method: 'GET',
|
||||
...getGiteaRequestOptions(this.integration.config),
|
||||
signal: options?.signal as any,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to read ${blobUrl}, ${e}`);
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
// Gitea returns an object with the file contents encoded, not the file itself
|
||||
const { encoding, content } = await response.json();
|
||||
|
||||
if (encoding === 'base64') {
|
||||
return ReadUrlResponseFactory.fromReadable(
|
||||
Readable.from(Buffer.from(content, 'base64')),
|
||||
{
|
||||
etag: response.headers.get('ETag') ?? undefined,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unknown encoding: ${encoding}`);
|
||||
}
|
||||
|
||||
const message = `${url} could not be read as ${blobUrl}, ${response.status} ${response.statusText}`;
|
||||
if (response.status === 404) {
|
||||
throw new NotFoundError(message);
|
||||
}
|
||||
|
||||
if (response.status === 304) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
readTree(): Promise<ReadTreeResponse> {
|
||||
throw new Error('GiteaUrlReader readTree not implemented.');
|
||||
}
|
||||
search(): Promise<SearchResponse> {
|
||||
throw new Error('GiteaUrlReader search not implemented.');
|
||||
}
|
||||
|
||||
toString() {
|
||||
const { host } = this.integration.config;
|
||||
return `gitea{host=${host},authed=${Boolean(
|
||||
this.integration.config.password,
|
||||
)}}`;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { DefaultReadTreeResponseFactory } from './tree';
|
||||
import { FetchUrlReader } from './FetchUrlReader';
|
||||
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
|
||||
import { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
import { GiteaUrlReader } from './GiteaUrlReader';
|
||||
|
||||
/**
|
||||
* Creation options for {@link UrlReaders}.
|
||||
@@ -89,6 +90,7 @@ export class UrlReaders {
|
||||
BitbucketUrlReader.factory,
|
||||
GerritUrlReader.factory,
|
||||
GithubUrlReader.factory,
|
||||
GiteaUrlReader.factory,
|
||||
GitlabUrlReader.factory,
|
||||
GoogleGcsUrlReader.factory,
|
||||
AwsS3UrlReader.factory,
|
||||
|
||||
@@ -21,6 +21,7 @@ export { BitbucketServerUrlReader } from './BitbucketServerUrlReader';
|
||||
export { GerritUrlReader } from './GerritUrlReader';
|
||||
export { GithubUrlReader } from './GithubUrlReader';
|
||||
export { GitlabUrlReader } from './GitlabUrlReader';
|
||||
export { GiteaUrlReader } from './GiteaUrlReader';
|
||||
export { AwsS3UrlReader } from './AwsS3UrlReader';
|
||||
export { FetchUrlReader } from './FetchUrlReader';
|
||||
export { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
|
||||
|
||||
@@ -323,6 +323,17 @@ export function getGerritRequestOptions(config: GerritIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export function getGiteaFileContentsUrl(
|
||||
config: GiteaIntegrationConfig,
|
||||
url: string,
|
||||
): string;
|
||||
|
||||
// @public
|
||||
export function getGiteaRequestOptions(config: GiteaIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const getGitHubFileFetchUrl: typeof getGithubFileFetchUrl;
|
||||
|
||||
@@ -357,6 +368,35 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class GiteaIntegration implements ScmIntegration {
|
||||
constructor(config: GiteaIntegrationConfig);
|
||||
// (undocumented)
|
||||
readonly config: GiteaIntegrationConfig;
|
||||
// (undocumented)
|
||||
static factory: ScmIntegrationsFactory<GiteaIntegration>;
|
||||
// (undocumented)
|
||||
resolveEditUrl(url: string): string;
|
||||
// (undocumented)
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
lineNumber?: number | undefined;
|
||||
}): string;
|
||||
// (undocumented)
|
||||
get title(): string;
|
||||
// (undocumented)
|
||||
get type(): string;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type GiteaIntegrationConfig = {
|
||||
host: string;
|
||||
baseUrl?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type GithubAppConfig = {
|
||||
appId: number;
|
||||
@@ -488,6 +528,8 @@ export interface IntegrationsByType {
|
||||
// (undocumented)
|
||||
gerrit: ScmIntegrationsGroup<GerritIntegration>;
|
||||
// (undocumented)
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
// (undocumented)
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
// (undocumented)
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
@@ -566,6 +608,9 @@ export function readGerritIntegrationConfigs(
|
||||
configs: Config[],
|
||||
): GerritIntegrationConfig[];
|
||||
|
||||
// @public
|
||||
export function readGiteaConfig(config: Config): GiteaIntegrationConfig;
|
||||
|
||||
// @public @deprecated (undocumented)
|
||||
export const readGitHubIntegrationConfig: typeof readGithubIntegrationConfig;
|
||||
|
||||
@@ -640,6 +685,8 @@ export interface ScmIntegrationRegistry
|
||||
// (undocumented)
|
||||
gerrit: ScmIntegrationsGroup<GerritIntegration>;
|
||||
// (undocumented)
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
// (undocumented)
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
// (undocumented)
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
@@ -673,6 +720,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
// (undocumented)
|
||||
get gerrit(): ScmIntegrationsGroup<GerritIntegration>;
|
||||
// (undocumented)
|
||||
get gitea(): ScmIntegrationsGroup<GiteaIntegration>;
|
||||
// (undocumented)
|
||||
get github(): ScmIntegrationsGroup<GithubIntegration>;
|
||||
// (undocumented)
|
||||
get gitlab(): ScmIntegrationsGroup<GitLabIntegration>;
|
||||
|
||||
Vendored
+26
@@ -281,5 +281,31 @@ export interface Config {
|
||||
*/
|
||||
externalId?: string;
|
||||
}>;
|
||||
|
||||
/** Integration configuration for Gitea */
|
||||
gitea?: Array<{
|
||||
/**
|
||||
* The hostname of the given Gitea instance
|
||||
* @visibility frontend
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* The base url for the Gitea instance.
|
||||
* @visibility frontend
|
||||
*/
|
||||
baseUrl?: string;
|
||||
|
||||
/**
|
||||
* The username to use for authenticated requests.
|
||||
* @visibility secret
|
||||
*/
|
||||
username?: string;
|
||||
/**
|
||||
* Gitea password used to authenticate requests. This can be either a password
|
||||
* or a generated access token.
|
||||
* @visibility secret
|
||||
*/
|
||||
password?: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import { GitLabIntegrationConfig } from './gitlab';
|
||||
import { GitLabIntegration } from './gitlab/GitLabIntegration';
|
||||
import { basicIntegrations } from './helpers';
|
||||
import { ScmIntegrations } from './ScmIntegrations';
|
||||
import { GiteaIntegration, GiteaIntegrationConfig } from './gitea';
|
||||
|
||||
describe('ScmIntegrations', () => {
|
||||
const awsS3 = new AwsS3Integration({
|
||||
@@ -69,6 +70,10 @@ describe('ScmIntegrations', () => {
|
||||
host: 'gitlab.local',
|
||||
} as GitLabIntegrationConfig);
|
||||
|
||||
const gitea = new GiteaIntegration({
|
||||
host: 'gitea.local',
|
||||
} as GiteaIntegrationConfig);
|
||||
|
||||
const i = new ScmIntegrations({
|
||||
awsS3: basicIntegrations([awsS3], item => item.config.host),
|
||||
azure: basicIntegrations([azure], item => item.config.host),
|
||||
@@ -81,6 +86,7 @@ describe('ScmIntegrations', () => {
|
||||
gerrit: basicIntegrations([gerrit], item => item.config.host),
|
||||
github: basicIntegrations([github], item => item.config.host),
|
||||
gitlab: basicIntegrations([gitlab], item => item.config.host),
|
||||
gitea: basicIntegrations([gitea], item => item.config.host),
|
||||
});
|
||||
|
||||
it('can get the specifics', () => {
|
||||
@@ -96,6 +102,7 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.gerrit.byUrl('https://gerrit.local')).toBe(gerrit);
|
||||
expect(i.github.byUrl('https://github.local')).toBe(github);
|
||||
expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab);
|
||||
expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea);
|
||||
});
|
||||
|
||||
it('can list', () => {
|
||||
@@ -109,6 +116,7 @@ describe('ScmIntegrations', () => {
|
||||
gerrit,
|
||||
github,
|
||||
gitlab,
|
||||
gitea,
|
||||
]),
|
||||
);
|
||||
});
|
||||
@@ -122,6 +130,7 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.byUrl('https://gerrit.local')).toBe(gerrit);
|
||||
expect(i.byUrl('https://github.local')).toBe(github);
|
||||
expect(i.byUrl('https://gitlab.local')).toBe(gitlab);
|
||||
expect(i.byUrl('https://gitea.local')).toBe(gitea);
|
||||
|
||||
expect(i.byHost('awss3.local')).toBe(awsS3);
|
||||
expect(i.byHost('azure.local')).toBe(azure);
|
||||
@@ -131,6 +140,7 @@ describe('ScmIntegrations', () => {
|
||||
expect(i.byHost('gerrit.local')).toBe(gerrit);
|
||||
expect(i.byHost('github.local')).toBe(github);
|
||||
expect(i.byHost('gitlab.local')).toBe(gitlab);
|
||||
expect(i.byHost('gitea.local')).toBe(gitea);
|
||||
});
|
||||
|
||||
it('can resolveUrl using fallback', () => {
|
||||
|
||||
@@ -26,6 +26,7 @@ import { GitLabIntegration } from './gitlab/GitLabIntegration';
|
||||
import { defaultScmResolveUrl } from './helpers';
|
||||
import { ScmIntegration, ScmIntegrationsGroup } from './types';
|
||||
import { ScmIntegrationRegistry } from './registry';
|
||||
import { GiteaIntegration } from './gitea';
|
||||
|
||||
/**
|
||||
* The set of supported integrations.
|
||||
@@ -44,6 +45,7 @@ export interface IntegrationsByType {
|
||||
gerrit: ScmIntegrationsGroup<GerritIntegration>;
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +66,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
gerrit: GerritIntegration.factory({ config }),
|
||||
github: GithubIntegration.factory({ config }),
|
||||
gitlab: GitLabIntegration.factory({ config }),
|
||||
gitea: GiteaIntegration.factory({ config }),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -106,6 +109,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
|
||||
return this.byType.gitlab;
|
||||
}
|
||||
|
||||
get gitea(): ScmIntegrationsGroup<GiteaIntegration> {
|
||||
return this.byType.gitea;
|
||||
}
|
||||
|
||||
list(): ScmIntegration[] {
|
||||
return Object.values(this.byType).flatMap(
|
||||
i => i.list() as ScmIntegration[],
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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 { GiteaIntegration } from './GiteaIntegration';
|
||||
|
||||
describe('GiteaIntegration', () => {
|
||||
it('has a working factory', () => {
|
||||
const integrations = GiteaIntegration.factory({
|
||||
config: new ConfigReader({
|
||||
integrations: {
|
||||
gitea: [
|
||||
{
|
||||
host: 'gitea.example.com',
|
||||
username: 'git',
|
||||
baseUrl: 'https://gitea.example.com/route',
|
||||
password: '1234',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
});
|
||||
expect(integrations.list().length).toBe(1);
|
||||
expect(integrations.list()[0].config.host).toBe('gitea.example.com');
|
||||
expect(integrations.list()[0].config.baseUrl).toBe(
|
||||
'https://gitea.example.com/route',
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the basics', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
expect(integration.type).toBe('gitea');
|
||||
expect(integration.title).toBe('gitea.example.com');
|
||||
});
|
||||
|
||||
describe('resolveUrl', () => {
|
||||
it('works for valid urls, ignoring line number', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: 'https://gitea.example.com/catalog-info.yaml',
|
||||
base: 'https://gitea.example.com/catalog-info.yaml',
|
||||
lineNumber: 9,
|
||||
}),
|
||||
).toBe('https://gitea.example.com/catalog-info.yaml');
|
||||
});
|
||||
|
||||
it('handles line numbers', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '',
|
||||
base: 'https://gitea.example.com/catalog-info.yaml#4',
|
||||
lineNumber: 9,
|
||||
}),
|
||||
).toBe('https://gitea.example.com/catalog-info.yaml#L9');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with a relative url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: './skeleton',
|
||||
base: 'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/template.yaml',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/skeleton',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolves with an absolute url', () => {
|
||||
it('works for valid urls', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveUrl({
|
||||
url: '/catalog-info.yaml',
|
||||
base: 'https://gitea.example.com/git/repo/+/refs/heads/master/',
|
||||
}),
|
||||
).toBe(
|
||||
'https://gitea.example.com/git/repo/+/refs/heads/master/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolve edit URL', () => {
|
||||
const integration = new GiteaIntegration({
|
||||
host: 'gitea.example.com',
|
||||
});
|
||||
|
||||
expect(
|
||||
integration.resolveEditUrl(
|
||||
'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml',
|
||||
),
|
||||
).toBe(
|
||||
'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 { basicIntegrations, defaultScmResolveUrl } from '../helpers';
|
||||
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
|
||||
import { GiteaIntegrationConfig, readGiteaConfig } from './config';
|
||||
import { getGiteaEditContentsUrl } from './core';
|
||||
|
||||
/**
|
||||
* A Gitea based integration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class GiteaIntegration implements ScmIntegration {
|
||||
static factory: ScmIntegrationsFactory<GiteaIntegration> = ({ config }) => {
|
||||
const configs = config.getOptionalConfigArray('integrations.gitea') ?? [];
|
||||
const giteaConfigs = configs.map(c => readGiteaConfig(c));
|
||||
|
||||
return basicIntegrations(
|
||||
giteaConfigs.map(c => new GiteaIntegration(c)),
|
||||
(gitea: GiteaIntegration) => gitea.config.host,
|
||||
);
|
||||
};
|
||||
|
||||
constructor(readonly config: GiteaIntegrationConfig) {}
|
||||
|
||||
get type(): string {
|
||||
return 'gitea';
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this.config.host;
|
||||
}
|
||||
|
||||
resolveUrl(options: {
|
||||
url: string;
|
||||
base: string;
|
||||
lineNumber?: number | undefined;
|
||||
}): string {
|
||||
return defaultScmResolveUrl(options);
|
||||
}
|
||||
|
||||
resolveEditUrl(url: string): string {
|
||||
return getGiteaEditContentsUrl(this.config, url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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, ConfigReader } from '@backstage/config';
|
||||
import { loadConfigSchema } from '@backstage/config-loader';
|
||||
import { GiteaIntegrationConfig, readGiteaConfig } from './config';
|
||||
|
||||
describe('readGiteaConfig', () => {
|
||||
function buildConfig(data: Partial<GiteaIntegrationConfig>): Config {
|
||||
return new ConfigReader(data);
|
||||
}
|
||||
|
||||
async function buildFrontendConfig(
|
||||
data: Partial<GiteaIntegrationConfig>,
|
||||
): Promise<Config> {
|
||||
const fullSchema = await loadConfigSchema({
|
||||
dependencies: ['@backstage/integration'],
|
||||
});
|
||||
const serializedSchema = fullSchema.serialize() as {
|
||||
schemas: { value: { properties?: { integrations?: object } } }[];
|
||||
};
|
||||
const schema = await loadConfigSchema({
|
||||
serialized: {
|
||||
...serializedSchema, // only include schemas that apply to integrations
|
||||
schemas: serializedSchema.schemas.filter(
|
||||
s => s.value?.properties?.integrations,
|
||||
),
|
||||
},
|
||||
});
|
||||
const processed = schema.process(
|
||||
[{ data: { integrations: { gitea: [data] } }, context: 'app' }],
|
||||
{ visibility: ['frontend'] },
|
||||
);
|
||||
return new ConfigReader((processed[0].data as any).integrations.gitea[0]);
|
||||
}
|
||||
|
||||
it('reads all values', () => {
|
||||
const output = readGiteaConfig(
|
||||
buildConfig({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/route/api',
|
||||
username: 'u',
|
||||
password: 'p',
|
||||
}),
|
||||
);
|
||||
expect(output).toEqual({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/route/api',
|
||||
username: 'u',
|
||||
password: 'p',
|
||||
});
|
||||
});
|
||||
|
||||
it('can create a default value if the API base URL is missing', () => {
|
||||
const output = readGiteaConfig(
|
||||
buildConfig({
|
||||
host: 'a.com',
|
||||
}),
|
||||
);
|
||||
expect(output).toEqual({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com',
|
||||
username: undefined,
|
||||
password: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects funky configs', () => {
|
||||
const valid: any = {
|
||||
host: 'a.com',
|
||||
};
|
||||
expect(() => readGiteaConfig(buildConfig({ ...valid, host: 2 }))).toThrow(
|
||||
/host/,
|
||||
);
|
||||
expect(() =>
|
||||
readGiteaConfig(buildConfig({ ...valid, baseUrl: 2 })),
|
||||
).toThrow(/baseUrl/);
|
||||
});
|
||||
|
||||
it('works on the frontend', async () => {
|
||||
expect(
|
||||
readGiteaConfig(
|
||||
await buildFrontendConfig({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/route',
|
||||
username: 'u',
|
||||
password: 'p',
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
host: 'a.com',
|
||||
baseUrl: 'https://a.com/route',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 { trimEnd } from 'lodash';
|
||||
import { isValidHost } from '../helpers';
|
||||
|
||||
/**
|
||||
* The configuration for a single Gitea integration.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type GiteaIntegrationConfig = {
|
||||
/**
|
||||
* The host of the target that this matches on, e.g. "gitea.website.com"
|
||||
*/
|
||||
host: string;
|
||||
/**
|
||||
* The optional base URL of the Gitea instance. It is assumed that https
|
||||
* is used and that the base path is "/" on the host. If that is not the
|
||||
* case set the complete base url to the gitea instance, e.g.
|
||||
* "https://gitea.website.com/". This is the url that you would open
|
||||
* in a browser.
|
||||
*/
|
||||
baseUrl?: string;
|
||||
/**
|
||||
* The username to use for requests to gitea.
|
||||
*/
|
||||
username?: string;
|
||||
|
||||
/**
|
||||
* The password or http token to use for authentication.
|
||||
*/
|
||||
password?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a location config block for use in GiteaIntegration
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function readGiteaConfig(config: Config): GiteaIntegrationConfig {
|
||||
const host = config.getString('host');
|
||||
let baseUrl = config.getOptionalString('baseUrl');
|
||||
const username = config.getOptionalString('username');
|
||||
const password = config.getOptionalString('password');
|
||||
|
||||
if (!isValidHost(host)) {
|
||||
throw new Error(
|
||||
`Invalid Gitea integration config, '${host}' is not a valid host`,
|
||||
);
|
||||
}
|
||||
|
||||
if (baseUrl) {
|
||||
baseUrl = trimEnd(baseUrl, '/');
|
||||
} else {
|
||||
baseUrl = `https://${host}`;
|
||||
}
|
||||
|
||||
return {
|
||||
host,
|
||||
baseUrl,
|
||||
username,
|
||||
password,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 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 { setupServer } from 'msw/node';
|
||||
import { setupRequestMockHandlers } from '@backstage/test-utils';
|
||||
import { GiteaIntegrationConfig } from './config';
|
||||
import {
|
||||
getGiteaEditContentsUrl,
|
||||
getGiteaFileContentsUrl,
|
||||
getGiteaRequestOptions,
|
||||
} from './core';
|
||||
|
||||
describe('gitea core', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
describe('getGiteaFileContentsUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: GiteaIntegrationConfig = {
|
||||
host: 'gitea.com',
|
||||
};
|
||||
expect(
|
||||
getGiteaFileContentsUrl(
|
||||
config,
|
||||
'https://gitea.com/a/b/src/branch/branch_name/path/to/c.yaml',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branch_name',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGiteaEditContentsUrl', () => {
|
||||
it('can create an url from arguments', () => {
|
||||
const config: GiteaIntegrationConfig = {
|
||||
host: 'gitea.example.com',
|
||||
};
|
||||
expect(
|
||||
getGiteaEditContentsUrl(
|
||||
config,
|
||||
'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml',
|
||||
),
|
||||
).toEqual(
|
||||
'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGerritRequestOptions', () => {
|
||||
it('adds token header when only a password is specified', () => {
|
||||
const authRequest: GiteaIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
password: 'P',
|
||||
};
|
||||
const anonymousRequest: GiteaIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
};
|
||||
expect(
|
||||
(getGiteaRequestOptions(authRequest).headers as any).Authorization,
|
||||
).toEqual('token P');
|
||||
expect(getGiteaRequestOptions(anonymousRequest).headers).toBeUndefined();
|
||||
});
|
||||
|
||||
it('adds basic auth when username and password are specified', () => {
|
||||
const authRequest: GiteaIntegrationConfig = {
|
||||
host: 'gerrit.com',
|
||||
username: 'username',
|
||||
password: 'P',
|
||||
};
|
||||
|
||||
const basicAuthentication = `basic ${Buffer.from(
|
||||
`${authRequest.username}:${authRequest.password}`,
|
||||
).toString('base64')}`;
|
||||
|
||||
expect(
|
||||
(getGiteaRequestOptions(authRequest).headers as any).Authorization,
|
||||
).toEqual(basicAuthentication);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 { GiteaIntegrationConfig } from './config';
|
||||
|
||||
/**
|
||||
* Given a URL pointing to a file, returns a URL
|
||||
* for editing the contents of the data.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Converts
|
||||
* from: https://gitea.com/a/b/src/branchname/path/to/c.yaml
|
||||
* or: https://gitea.com/a/b/_edit/branchname/path/to/c.yaml
|
||||
*
|
||||
* @param url - A URL pointing to a file
|
||||
* @param config - The relevant provider config
|
||||
* @public
|
||||
*/
|
||||
export function getGiteaEditContentsUrl(
|
||||
config: GiteaIntegrationConfig,
|
||||
url: string,
|
||||
) {
|
||||
try {
|
||||
const baseUrl = config.baseUrl ?? `https://${config.host}`;
|
||||
const [_blank, owner, name, _src, _branch, ref, ...path] = url
|
||||
.replace(baseUrl, '')
|
||||
.split('/');
|
||||
const pathWithoutSlash = path.join('/').replace(/^\//, '');
|
||||
return `${baseUrl}/${owner}/${name}/_edit/${ref}/${pathWithoutSlash}`;
|
||||
} catch (e) {
|
||||
throw new Error(`Incorrect URL: ${url}, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a URL pointing to a file, returns an api URL
|
||||
* for fetching the contents of the data.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Converts
|
||||
* from: https://gitea.com/a/b/src/branch/branchname/path/to/c.yaml
|
||||
* to: https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branchname
|
||||
*
|
||||
* @param url - A URL pointing to a file
|
||||
* @param config - The relevant provider config
|
||||
* @public
|
||||
*/
|
||||
export function getGiteaFileContentsUrl(
|
||||
config: GiteaIntegrationConfig,
|
||||
url: string,
|
||||
) {
|
||||
try {
|
||||
const baseUrl = config.baseUrl ?? `https://${config.host}`;
|
||||
const [_blank, owner, name, _src, _branch, ref, ...path] = url
|
||||
.replace(baseUrl, '')
|
||||
.split('/');
|
||||
const pathWithoutSlash = path.join('/').replace(/^\//, '');
|
||||
|
||||
return `${baseUrl}/api/v1/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`;
|
||||
} catch (e) {
|
||||
throw new Error(`Incorrect URL: ${url}, ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return request headers for a Gitea provider.
|
||||
*
|
||||
* @param config - A Gitea provider config
|
||||
* @public
|
||||
*/
|
||||
export function getGiteaRequestOptions(config: GiteaIntegrationConfig): {
|
||||
headers?: Record<string, string>;
|
||||
} {
|
||||
const headers: Record<string, string> = {};
|
||||
const { username, password } = config;
|
||||
|
||||
if (!password) {
|
||||
return headers;
|
||||
}
|
||||
|
||||
if (username) {
|
||||
headers.Authorization = `basic ${Buffer.from(
|
||||
`${username}:${password}`,
|
||||
).toString('base64')}`;
|
||||
} else {
|
||||
headers.Authorization = `token ${password}`;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 { GiteaIntegration } from './GiteaIntegration';
|
||||
export { getGiteaRequestOptions, getGiteaFileContentsUrl } from './core';
|
||||
export { readGiteaConfig } from './config';
|
||||
export type { GiteaIntegrationConfig } from './config';
|
||||
@@ -26,6 +26,7 @@ export * from './bitbucket';
|
||||
export * from './bitbucketCloud';
|
||||
export * from './bitbucketServer';
|
||||
export * from './gerrit';
|
||||
export * from './gitea';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export * from './googleGcs';
|
||||
|
||||
@@ -23,6 +23,7 @@ import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerInt
|
||||
import { GerritIntegration } from './gerrit/GerritIntegration';
|
||||
import { GithubIntegration } from './github/GithubIntegration';
|
||||
import { GitLabIntegration } from './gitlab/GitLabIntegration';
|
||||
import { GiteaIntegration } from './gitea/GiteaIntegration';
|
||||
|
||||
/**
|
||||
* Holds all registered SCM integrations, of all types.
|
||||
@@ -42,7 +43,7 @@ export interface ScmIntegrationRegistry
|
||||
gerrit: ScmIntegrationsGroup<GerritIntegration>;
|
||||
github: ScmIntegrationsGroup<GithubIntegration>;
|
||||
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
|
||||
|
||||
gitea: ScmIntegrationsGroup<GiteaIntegration>;
|
||||
/**
|
||||
* Resolves an absolute or relative URL in relation to a base URL.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user