integrations: move common integration concerns to a separate package (#3295)

This commit is contained in:
Fredrik Adelöw
2020-11-16 16:15:09 +01:00
committed by GitHub
parent 04c0698df8
commit 7b37e68348
25 changed files with 1086 additions and 403 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-common': patch
'@backstage/integration': patch
---
Added the integration package
+1
View File
@@ -32,6 +32,7 @@
"@backstage/cli-common": "^0.1.1",
"@backstage/config": "^0.1.1",
"@backstage/config-loader": "^0.2.0",
"@backstage/integration": "^0.1.0",
"@backstage/test-utils": "^0.1.2",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6",
@@ -14,49 +14,27 @@
* limitations under the License.
*/
import {
AzureIntegrationConfig,
readAzureIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import { Config } from '@backstage/config';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
type Options = {
// TODO: added here for future support, but we only allow dev.azure.com for now
host: string;
token?: string;
};
function readConfig(config: Config): Options[] {
const optionsArr = Array<Options>();
const providerConfigs =
config.getOptionalConfigArray('integrations.azure') ?? [];
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'dev.azure.com';
const token = providerConfig.getOptionalString('token');
optionsArr.push({ host, token });
}
// As a convenience we always make sure there's at least an unauthenticated
// reader for public azure repos.
if (!optionsArr.some(p => p.host === 'dev.azure.com')) {
optionsArr.push({ host: 'dev.azure.com' });
}
return optionsArr;
}
export class AzureUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(options => {
const configs = readAzureIntegrationConfigs(
config.getOptionalConfigArray('integrations.azure') ?? [],
);
return configs.map(options => {
const reader = new AzureUrlReader(options);
const predicate = (url: URL) => url.host === options.host;
return { reader, predicate };
});
};
constructor(private readonly options: Options) {
constructor(private readonly options: AzureIntegrationConfig) {
if (options.host !== 'dev.azure.com') {
throw Error(
`Azure integration currently only supports 'dev.azure.com', tried to use host '${options.host}'`,
@@ -14,24 +14,22 @@
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import { BitbucketIntegrationConfig } from '@backstage/integration';
import {
BitbucketUrlReader,
getApiRequestOptions,
getApiUrl,
ProviderConfig,
readConfig,
} from './BitbucketUrlReader';
describe('BitbucketUrlReader', () => {
describe('getApiRequestOptions', () => {
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -44,13 +42,13 @@ describe('BitbucketUrlReader', () => {
});
it('insert basic auth when needed', () => {
const withUsernameAndPassword: ProviderConfig = {
const withUsernameAndPassword: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
username: 'some-user',
appPassword: 'my-secret',
};
const withoutUsernameAndPassword: ProviderConfig = {
const withoutUsernameAndPassword: BitbucketIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -67,11 +65,11 @@ describe('BitbucketUrlReader', () => {
describe('getApiUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: BitbucketIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for Bitbucket Cloud', () => {
const config: ProviderConfig = {
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
};
@@ -87,7 +85,7 @@ describe('BitbucketUrlReader', () => {
);
});
it('happy path for Bitbucket Server', () => {
const config: ProviderConfig = {
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.mycompany.net',
apiBaseUrl: 'https://bitbucket.mycompany.net/rest/api/1.0',
};
@@ -104,66 +102,6 @@ describe('BitbucketUrlReader', () => {
});
});
describe('readConfig', () => {
function config(
providers: {
host: string;
apiBaseUrl?: string;
token?: string;
username?: string;
password?: string;
}[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
integrations: { bitbucket: providers },
},
},
]);
}
it('adds a default Bitbucket Cloud entry when missing', () => {
const output = readConfig(config([]));
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
it('injects the correct Bitbucket Cloud API base URL when missing', () => {
const output = readConfig(config([{ host: 'bitbucket.org' }]));
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
it('rejects custom targets with no base URLs', () => {
expect(() =>
readConfig(config([{ host: 'bitbucket.mycompany.net' }])),
).toThrow(
"Bitbucket integration for 'bitbucket.mycompany.net' must configure an explicit apiBaseUrl",
);
});
it('rejects funky configs', () => {
expect(() => readConfig(config([{ host: 7 } as any]))).toThrow(/host/);
expect(() => readConfig(config([{ token: 7 } as any]))).toThrow(/token/);
expect(() =>
readConfig(config([{ host: 'bitbucket.org', apiBaseUrl: 7 } as any])),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(config([{ host: 'bitbucket.org', token: 7 } as any])),
).toThrow(/token/);
});
});
describe('implementation', () => {
it('rejects unknown targets', async () => {
const processor = new BitbucketUrlReader({
@@ -14,57 +14,18 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import {
BitbucketIntegrationConfig,
readBitbucketIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUri from 'git-url-parse';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
const DEFAULT_BASE_URL = 'https://api.bitbucket.org/2.0';
/**
* The configuration parameters for a single Bitbucket API provider.
*/
export type ProviderConfig = {
/**
* The host of the target that this matches on, e.g. "bitbucket.com"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.bitbucket.org/2.0",
* with no trailing slash.
*
* May be omitted specifically for Bitbucket Cloud; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The authorization token to use for requests to a Bitbucket Server provider.
*
* See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html
*
* If no token is specified, anonymous access is used.
*/
token?: string;
/**
* The username to use for requests to Bitbucket Cloud (bitbucket.org).
*/
username?: string;
/**
* Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords.
*
* See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/
*/
appPassword?: string;
};
export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
export function getApiRequestOptions(
provider: BitbucketIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {};
if (provider.token) {
@@ -84,7 +45,10 @@ export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
// Converts for example
// from: https://bitbucket.org/orgname/reponame/src/master/file.yaml
// to: https://api.bitbucket.org/2.0/repositories/orgname/reponame/src/master/file.yaml
export function getApiUrl(target: string, provider: ProviderConfig): URL {
export function getApiUrl(
target: string,
provider: BitbucketIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
if (
@@ -115,74 +79,39 @@ export function getApiUrl(target: string, provider: ProviderConfig): URL {
}
}
export function readConfig(config: Config): ProviderConfig[] {
const providers: ProviderConfig[] = [];
const providerConfigs =
config.getOptionalConfigArray('integrations.bitbucket') ?? [];
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'bitbucket.org';
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
const token = providerConfig.getOptionalString('token');
const username = providerConfig.getOptionalString('username');
const appPassword = providerConfig.getOptionalString('appPassword');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === 'bitbucket.org') {
apiBaseUrl = DEFAULT_BASE_URL;
}
if (!apiBaseUrl) {
throw new Error(
`Bitbucket integration for '${host}' must configure an explicit apiBaseUrl`,
);
}
if (!token && username && !appPassword) {
throw new Error(
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
);
}
providers.push({
host,
apiBaseUrl,
token,
username,
appPassword,
});
}
// If no explicit bitbucket.org provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.host === 'bitbucket.org')) {
providers.push({
host: 'bitbucket.org',
apiBaseUrl: DEFAULT_BASE_URL,
});
}
return providers;
}
/**
* A processor that adds the ability to read files from Bitbucket v1 and v2 APIs, such as
* the one exposed by Bitbucket Cloud itself.
*/
export class BitbucketUrlReader implements UrlReader {
private config: ProviderConfig;
private readonly config: BitbucketIntegrationConfig;
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(provider => {
const configs = readBitbucketIntegrationConfigs(
config.getOptionalConfigArray('integrations.bitbucket') ?? [],
);
return configs.map(provider => {
const reader = new BitbucketUrlReader(provider);
const predicate = (url: URL) => url.host === provider.host;
return { reader, predicate };
});
};
constructor(config: ProviderConfig) {
constructor(config: BitbucketIntegrationConfig) {
const { host, apiBaseUrl, token, username, appPassword } = config;
if (!apiBaseUrl) {
throw new Error(
`Bitbucket integration for '${host}' must configure an explicit apiBaseUrl`,
);
}
if (!token && username && !appPassword) {
throw new Error(
`Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.`,
);
}
this.config = config;
}
@@ -15,20 +15,19 @@
*/
import { ConfigReader } from '@backstage/config';
import { setupServer } from 'msw/node';
import { GitHubIntegrationConfig } from '@backstage/integration';
import { msw } from '@backstage/test-utils';
import fs from 'fs';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
import path from 'path';
import {
getApiRequestOptions,
getApiUrl,
getRawRequestOptions,
getRawUrl,
GithubUrlReader,
ProviderConfig,
readConfig,
} from './GithubUrlReader';
import fs from 'fs';
import path from 'path';
import { ReadTreeResponseFactory } from './tree';
const treeResponseFactory = ReadTreeResponseFactory.create({
@@ -38,19 +37,19 @@ const treeResponseFactory = ReadTreeResponseFactory.create({
describe('GithubUrlReader', () => {
describe('getApiRequestOptions', () => {
it('sets the correct API version', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect((getApiRequestOptions(config).headers as any).Accept).toEqual(
'application/vnd.github.v3.raw',
);
});
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: GitHubIntegrationConfig = {
host: '',
apiBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: GitHubIntegrationConfig = {
host: '',
apiBaseUrl: '',
};
@@ -65,12 +64,12 @@ describe('GithubUrlReader', () => {
describe('getRawRequestOptions', () => {
it('inserts a token when needed', () => {
const withToken: ProviderConfig = {
const withToken: GitHubIntegrationConfig = {
host: '',
rawBaseUrl: '',
token: 'A',
};
const withoutToken: ProviderConfig = {
const withoutToken: GitHubIntegrationConfig = {
host: '',
rawBaseUrl: '',
};
@@ -85,12 +84,12 @@ describe('GithubUrlReader', () => {
describe('getApiUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getApiUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
};
@@ -117,7 +116,7 @@ describe('GithubUrlReader', () => {
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'ghe.mycompany.net',
apiBaseUrl: 'https://ghe.mycompany.net/api/v3',
};
@@ -136,12 +135,12 @@ describe('GithubUrlReader', () => {
describe('getRawUrl', () => {
it('rejects targets that do not look like URLs', () => {
const config: ProviderConfig = { host: '', apiBaseUrl: '' };
const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' };
expect(() => getRawUrl('a/b', config)).toThrow(/Incorrect URL: a\/b/);
});
it('happy path for github', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
};
@@ -158,7 +157,7 @@ describe('GithubUrlReader', () => {
});
it('happy path for ghe', () => {
const config: ProviderConfig = {
const config: GitHubIntegrationConfig = {
host: 'ghe.mycompany.net',
rawBaseUrl: 'https://ghe.mycompany.net/raw',
};
@@ -173,60 +172,6 @@ describe('GithubUrlReader', () => {
});
});
describe('readConfig', () => {
function config(
providers: { host: string; apiBaseUrl?: string; token?: string }[],
) {
return ConfigReader.fromConfigs([
{
context: '',
data: {
integrations: { github: providers },
},
},
]);
}
it('adds a default GitHub entry when missing', () => {
const output = readConfig(config([]));
expect(output).toEqual([
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('injects the correct GitHub API base URL when missing', () => {
const output = readConfig(config([{ host: 'github.com' }]));
expect(output).toEqual([
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
it('rejects custom targets with no base URLs', () => {
expect(() => readConfig(config([{ host: 'ghe.company.com' }]))).toThrow(
"GitHub integration for 'ghe.company.com' must configure an explicit apiBaseUrl and rawBaseUrl",
);
});
it('rejects funky configs', () => {
expect(() => readConfig(config([{ host: 7 } as any]))).toThrow(/host/);
expect(() => readConfig(config([{ token: 7 } as any]))).toThrow(/token/);
expect(() =>
readConfig(config([{ host: 'github.com', apiBaseUrl: 7 } as any])),
).toThrow(/apiBaseUrl/);
expect(() =>
readConfig(config([{ host: 'github.com', token: 7 } as any])),
).toThrow(/token/);
});
});
describe('implementation', () => {
it('rejects unknown targets', async () => {
const processor = new GithubUrlReader(
@@ -271,6 +216,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -293,6 +239,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -308,6 +255,7 @@ describe('GithubUrlReader', () => {
const processor = new GithubUrlReader(
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
},
{ treeResponseFactory },
);
@@ -14,59 +14,25 @@
* limitations under the License.
*/
import { Config } from '@backstage/config';
import parseGitUri from 'git-url-parse';
import {
GitHubIntegrationConfig,
readGitHubIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import parseGitUri from 'git-url-parse';
import { Readable } from 'stream';
import { InputError, NotFoundError } from '../errors';
import { ReadTreeResponseFactory } from './tree';
import {
ReaderFactory,
ReadTreeOptions,
ReadTreeResponse,
UrlReader,
ReadTreeOptions,
} from './types';
import { ReadTreeResponseFactory } from './tree';
/**
* The configuration parameters for a single GitHub API provider.
*/
export type ProviderConfig = {
/**
* The host of the target that this matches on, e.g. "github.com"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.github.com",
* with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The base URL of the raw fetch endpoint of this provider, e.g.
* "https://raw.githubusercontent.com", with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
rawBaseUrl?: string;
/**
* The authorization token to use for requests to this provider.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
export function getApiRequestOptions(
provider: GitHubIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {
Accept: 'application/vnd.github.v3.raw',
};
@@ -80,7 +46,9 @@ export function getApiRequestOptions(provider: ProviderConfig): RequestInit {
};
}
export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
export function getRawRequestOptions(
provider: GitHubIntegrationConfig,
): RequestInit {
const headers: HeadersInit = {};
if (provider.token) {
@@ -95,7 +63,10 @@ export function getRawRequestOptions(provider: ProviderConfig): RequestInit {
// Converts for example
// from: https://github.com/a/b/blob/branchname/path/to/c.yaml
// to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname
export function getApiUrl(target: string, provider: ProviderConfig): URL {
export function getApiUrl(
target: string,
provider: GitHubIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
@@ -120,7 +91,10 @@ export function getApiUrl(target: string, provider: ProviderConfig): URL {
// Converts for example
// from: https://github.com/a/b/blob/branchname/c.yaml
// to: https://raw.githubusercontent.com/a/b/branchname/c.yaml
export function getRawUrl(target: string, provider: ProviderConfig): URL {
export function getRawUrl(
target: string,
provider: GitHubIntegrationConfig,
): URL {
try {
const { owner, name, ref, filepathtype, filepath } = parseGitUri(target);
@@ -142,60 +116,16 @@ export function getRawUrl(target: string, provider: ProviderConfig): URL {
}
}
export function readConfig(config: Config): ProviderConfig[] {
const providers: ProviderConfig[] = [];
const providerConfigs =
config.getOptionalConfigArray('integrations.github') ?? [];
// First read all the explicit providers
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'github.com';
let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl');
let rawBaseUrl = providerConfig.getOptionalString('rawBaseUrl');
const token = providerConfig.getOptionalString('token');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === 'github.com') {
apiBaseUrl = 'https://api.github.com';
}
if (rawBaseUrl) {
rawBaseUrl = rawBaseUrl.replace(/\/+$/, '');
} else if (host === 'github.com') {
rawBaseUrl = 'https://raw.githubusercontent.com';
}
if (!apiBaseUrl && !rawBaseUrl) {
throw new Error(
`GitHub integration for '${host}' must configure an explicit apiBaseUrl and rawBaseUrl`,
);
}
providers.push({ host, apiBaseUrl, rawBaseUrl, token });
}
// If no explicit github.com provider was added, put one in the list as
// a convenience
if (!providers.some(p => p.host === 'github.com')) {
providers.push({
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
});
}
return providers;
}
/**
* A processor that adds the ability to read files from GitHub v3 APIs, such as
* the one exposed by GitHub itself.
*/
export class GithubUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config, treeResponseFactory }) => {
return readConfig(config).map(provider => {
const configs = readGitHubIntegrationConfigs(
config.getOptionalConfigArray('integrations.github') ?? [],
);
return configs.map(provider => {
const reader = new GithubUrlReader(provider, { treeResponseFactory });
const predicate = (url: URL) => url.host === provider.host;
return { reader, predicate };
@@ -203,9 +133,15 @@ export class GithubUrlReader implements UrlReader {
};
constructor(
private readonly config: ProviderConfig,
private readonly config: GitHubIntegrationConfig,
private readonly deps: { treeResponseFactory: ReadTreeResponseFactory },
) {}
) {
if (!config.apiBaseUrl && !config.rawBaseUrl) {
throw new Error(
`GitHub integration for '${config.host}' must configure an explicit apiBaseUrl and rawBaseUrl`,
);
}
}
async read(url: string): Promise<Buffer> {
const useApi =
@@ -14,48 +14,27 @@
* limitations under the License.
*/
import {
GitLabIntegrationConfig,
readGitLabIntegrationConfigs,
} from '@backstage/integration';
import fetch from 'cross-fetch';
import { Config } from '@backstage/config';
import { NotFoundError } from '../errors';
import { ReaderFactory, ReadTreeResponse, UrlReader } from './types';
type Options = {
host: string;
token?: string;
};
function readConfig(config: Config): Options[] {
const optionsArr = Array<Options>();
const providerConfigs =
config.getOptionalConfigArray('integrations.gitlab') ?? [];
for (const providerConfig of providerConfigs) {
const host = providerConfig.getOptionalString('host') ?? 'gitlab.com';
const token = providerConfig.getOptionalString('token');
optionsArr.push({ host, token });
}
// As a convenience we always make sure there's at least an unauthenticated
// reader for public gitlab repos.
if (!optionsArr.some(p => p.host === 'gitlab.com')) {
optionsArr.push({ host: 'gitlab.com' });
}
return optionsArr;
}
export class GitlabUrlReader implements UrlReader {
static factory: ReaderFactory = ({ config }) => {
return readConfig(config).map(options => {
const configs = readGitLabIntegrationConfigs(
config.getOptionalConfigArray('integrations.gitlab') ?? [],
);
return configs.map(options => {
const reader = new GitlabUrlReader(options);
const predicate = (url: URL) => url.host === options.host;
return { reader, predicate };
});
};
constructor(private readonly options: Options) {}
constructor(private readonly options: GitLabIntegrationConfig) {}
async read(url: string): Promise<Buffer> {
// TODO(Rugvip): merged the old GitlabReaderProcessor in here and used
@@ -133,9 +112,9 @@ export class GitlabUrlReader implements UrlReader {
try {
const url = new URL(target);
const branchAndfilePath = url.pathname.split('/-/blob/')[1];
const branchAndFilePath = url.pathname.split('/-/blob/')[1];
const [branch, ...filePath] = branchAndfilePath.split('/');
const [branch, ...filePath] = branchAndFilePath.split('/');
url.pathname = [
'/api/v4/projects',
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
extends: [require.resolve('@backstage/cli/config/eslint')],
};
+9
View File
@@ -0,0 +1,9 @@
# Integrations common functionality
Contains some common functionality of integrations.
This package will be imported both by the frontend and backend.
## Links
- [The Backstage homepage](https://backstage.io)
+33
View File
@@ -0,0 +1,33 @@
{
"name": "@backstage/integration",
"version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli build",
"lint": "backstage-cli lint",
"test": "backstage-cli test",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1",
"git-url-parse": "^11.4.0"
},
"devDependencies": {
"@backstage/cli": "^0.2.0",
"@types/jest": "^26.0.7"
},
"files": [
"dist"
]
}
@@ -0,0 +1,91 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
AzureIntegrationConfig,
readAzureIntegrationConfig,
readAzureIntegrationConfigs,
} from './config';
describe('readAzureIntegrationConfig', () => {
function buildConfig(data: Partial<AzureIntegrationConfig>): Config {
return ConfigReader.fromConfigs([{ context: '', data }]);
}
it('reads all values', () => {
const output = readAzureIntegrationConfig(
buildConfig({
host: 'a.com',
token: 't',
}),
);
expect(output).toEqual({
host: 'a.com',
token: 't',
});
});
it('inserts the defaults if missing', () => {
const output = readAzureIntegrationConfig(buildConfig({}));
expect(output).toEqual({ host: 'dev.azure.com' });
});
it('rejects funky configs', () => {
const valid: any = {
host: 'a.com',
token: 't',
};
expect(() =>
readAzureIntegrationConfig(buildConfig({ ...valid, host: 7 })),
).toThrow(/host/);
expect(() =>
readAzureIntegrationConfig(buildConfig({ ...valid, token: 7 })),
).toThrow(/token/);
});
});
describe('readAzureIntegrationConfigs', () => {
function buildConfig(data: Partial<AzureIntegrationConfig>[]): Config[] {
return data.map(item =>
ConfigReader.fromConfigs([{ context: '', data: item }]),
);
}
it('reads all values', () => {
const output = readAzureIntegrationConfigs(
buildConfig([
{
host: 'a.com',
token: 't',
},
]),
);
expect(output).toContainEqual({
host: 'a.com',
token: 't',
});
});
it('adds a default entry when missing', () => {
const output = readAzureIntegrationConfigs(buildConfig([]));
expect(output).toEqual([
{
host: 'dev.azure.com',
},
]);
});
});
+72
View File
@@ -0,0 +1,72 @@
/*
* Copyright 2020 Spotify AB
*
* 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';
const AZURE_HOST = 'dev.azure.com';
/**
* The configuration parameters for a single Azure provider.
*/
export type AzureIntegrationConfig = {
/**
* The host of the target that this matches on, e.g. "dev.azure.com".
*
* Currently only "dev.azure.com" is supported.
*/
host: string;
/**
* The authorization token to use for requests.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
/**
* Reads a single Azure integration config.
*
* @param config The config object of a single integration
*/
export function readAzureIntegrationConfig(
config: Config,
): AzureIntegrationConfig {
const host = config.getOptionalString('host') ?? AZURE_HOST;
const token = config.getOptionalString('token');
return { host, token };
}
/**
* Reads a set of Azure integration configs, and inserts some defaults for
* public Azure if not specified.
*
* @param configs All of the integration config objects
*/
export function readAzureIntegrationConfigs(
configs: Config[],
): AzureIntegrationConfig[] {
// First read all the explicit integrations
const result = configs.map(readAzureIntegrationConfig);
// If no explicit dev.azure.com integration was added, put one in the list as
// a convenience
if (!result.some(c => c.host === AZURE_HOST)) {
result.push({ host: AZURE_HOST });
}
return result;
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
readAzureIntegrationConfig,
readAzureIntegrationConfigs,
} from './config';
export type { AzureIntegrationConfig } from './config';
@@ -0,0 +1,133 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
BitbucketIntegrationConfig,
readBitbucketIntegrationConfig,
readBitbucketIntegrationConfigs,
} from './config';
describe('readBitbucketIntegrationConfig', () => {
function buildConfig(data: Partial<BitbucketIntegrationConfig>): Config {
return ConfigReader.fromConfigs([{ context: '', data }]);
}
it('reads all values', () => {
const output = readBitbucketIntegrationConfig(
buildConfig({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
token: 't',
username: 'u',
appPassword: 'p',
}),
);
expect(output).toEqual({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
token: 't',
username: 'u',
appPassword: 'p',
});
});
it('inserts the defaults if missing', () => {
const output = readBitbucketIntegrationConfig(buildConfig({}));
expect(output).toEqual(
expect.objectContaining({
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
}),
);
});
it('rejects funky configs', () => {
const valid: any = {
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
token: 't',
username: 'u',
appPassword: 'p',
};
expect(() =>
readBitbucketIntegrationConfig(buildConfig({ ...valid, host: 7 })),
).toThrow(/host/);
expect(() =>
readBitbucketIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })),
).toThrow(/apiBaseUrl/);
expect(() =>
readBitbucketIntegrationConfig(buildConfig({ ...valid, token: 7 })),
).toThrow(/token/);
expect(() =>
readBitbucketIntegrationConfig(buildConfig({ ...valid, username: 7 })),
).toThrow(/username/);
expect(() =>
readBitbucketIntegrationConfig(buildConfig({ ...valid, appPassword: 7 })),
).toThrow(/appPassword/);
});
});
describe('readBitbucketIntegrationConfigs', () => {
function buildConfig(data: Partial<BitbucketIntegrationConfig>[]): Config[] {
return data.map(item =>
ConfigReader.fromConfigs([{ context: '', data: item }]),
);
}
it('reads all values', () => {
const output = readBitbucketIntegrationConfigs(
buildConfig([
{
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
token: 't',
username: 'u',
appPassword: 'p',
},
]),
);
expect(output).toContainEqual({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
token: 't',
username: 'u',
appPassword: 'p',
});
});
it('adds a default Bitbucket Cloud entry when missing', () => {
const output = readBitbucketIntegrationConfigs(buildConfig([]));
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
it('injects the correct Bitbucket Cloud API base URL when missing', () => {
const output = readBitbucketIntegrationConfigs(
buildConfig([{ host: 'bitbucket.org' }]),
);
expect(output).toEqual([
{
host: 'bitbucket.org',
apiBaseUrl: 'https://api.bitbucket.org/2.0',
},
]);
});
});
@@ -0,0 +1,115 @@
/*
* Copyright 2020 Spotify AB
*
* 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';
const BITBUCKET_HOST = 'bitbucket.org';
const BITBUCKET_API_BASE_URL = 'https://api.bitbucket.org/2.0';
/**
* The configuration parameters for a single Bitbucket API provider.
*/
export type BitbucketIntegrationConfig = {
/**
* The host of the target that this matches on, e.g. "bitbucket.org"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.bitbucket.org/2.0",
* with no trailing slash.
*
* May be omitted specifically for Bitbucket Cloud; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The authorization token to use for requests to a Bitbucket Server provider.
*
* See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html
*
* If no token is specified, anonymous access is used.
*/
token?: string;
/**
* The username to use for requests to Bitbucket Cloud (bitbucket.org).
*/
username?: string;
/**
* Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords.
*
* See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/
*/
appPassword?: string;
};
/**
* Reads a single Bitbucket integration config.
*
* @param config The config object of a single integration
*/
export function readBitbucketIntegrationConfig(
config: Config,
): BitbucketIntegrationConfig {
const host = config.getOptionalString('host') ?? BITBUCKET_HOST;
let apiBaseUrl = config.getOptionalString('apiBaseUrl');
const token = config.getOptionalString('token');
const username = config.getOptionalString('username');
const appPassword = config.getOptionalString('appPassword');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === BITBUCKET_HOST) {
apiBaseUrl = BITBUCKET_API_BASE_URL;
}
return {
host,
apiBaseUrl,
token,
username,
appPassword,
};
}
/**
* Reads a set of Bitbucket integration configs, and inserts some defaults for
* public Bitbucket if not specified.
*
* @param configs All of the integration config objects
*/
export function readBitbucketIntegrationConfigs(
configs: Config[],
): BitbucketIntegrationConfig[] {
// First read all the explicit integrations
const result = configs.map(readBitbucketIntegrationConfig);
// If no explicit bitbucket.org integration was added, put one in the list as
// a convenience
if (!result.some(c => c.host === BITBUCKET_HOST)) {
result.push({
host: BITBUCKET_HOST,
apiBaseUrl: BITBUCKET_API_BASE_URL,
});
}
return result;
}
@@ -0,0 +1,21 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
readBitbucketIntegrationConfig,
readBitbucketIntegrationConfigs,
} from './config';
export type { BitbucketIntegrationConfig } from './config';
@@ -0,0 +1,117 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
GitHubIntegrationConfig,
readGitHubIntegrationConfig,
readGitHubIntegrationConfigs,
} from './config';
describe('readGitHubIntegrationConfig', () => {
function buildConfig(provider: Partial<GitHubIntegrationConfig>) {
return ConfigReader.fromConfigs([{ context: '', data: provider }]);
}
it('reads all values', () => {
const output = readGitHubIntegrationConfig(
buildConfig({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
rawBaseUrl: 'https://a.com/raw',
token: 't',
}),
);
expect(output).toEqual({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
rawBaseUrl: 'https://a.com/raw',
token: 't',
});
});
it('injects the correct GitHub API base URL when missing', () => {
const output = readGitHubIntegrationConfig(
buildConfig({ host: 'github.com' }),
);
expect(output).toEqual({
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
});
});
it('rejects funky configs', () => {
const valid: any = {
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
rawBaseUrl: 'https://a.com/raw',
token: 't',
};
expect(() =>
readGitHubIntegrationConfig(buildConfig({ ...valid, host: 7 })),
).toThrow(/host/);
expect(() =>
readGitHubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })),
).toThrow(/apiBaseUrl/);
expect(() =>
readGitHubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })),
).toThrow(/rawBaseUrl/);
expect(() =>
readGitHubIntegrationConfig(buildConfig({ ...valid, token: 7 })),
).toThrow(/token/);
});
});
describe('readGitHubIntegrationConfigs', () => {
function buildConfig(
providers: Partial<GitHubIntegrationConfig>[],
): Config[] {
return providers.map(provider =>
ConfigReader.fromConfigs([{ context: '', data: provider }]),
);
}
it('reads all values', () => {
const output = readGitHubIntegrationConfigs(
buildConfig([
{
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
rawBaseUrl: 'https://a.com/raw',
token: 't',
},
]),
);
expect(output).toContainEqual({
host: 'a.com',
apiBaseUrl: 'https://a.com/api',
rawBaseUrl: 'https://a.com/raw',
token: 't',
});
});
it('adds a default GitHub entry when missing', () => {
const output = readGitHubIntegrationConfigs(buildConfig([]));
expect(output).toEqual([
{
host: 'github.com',
apiBaseUrl: 'https://api.github.com',
rawBaseUrl: 'https://raw.githubusercontent.com',
},
]);
});
});
+113
View File
@@ -0,0 +1,113 @@
/*
* Copyright 2020 Spotify AB
*
* 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';
const GITHUB_HOST = 'github.com';
const GITHUB_API_BASE_URL = 'https://api.github.com';
const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com';
/**
* The configuration parameters for a single GitHub integration.
*/
export type GitHubIntegrationConfig = {
/**
* The host of the target that this matches on, e.g. "github.com"
*/
host: string;
/**
* The base URL of the API of this provider, e.g. "https://api.github.com",
* with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
apiBaseUrl?: string;
/**
* The base URL of the raw fetch endpoint of this provider, e.g.
* "https://raw.githubusercontent.com", with no trailing slash.
*
* May be omitted specifically for GitHub; then it will be deduced.
*
* The API will always be preferred if both its base URL and a token are
* present.
*/
rawBaseUrl?: string;
/**
* The authorization token to use for requests to this provider.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
/**
* Reads a single GitHub integration config.
*
* @param config The config object of a single integration
*/
export function readGitHubIntegrationConfig(
config: Config,
): GitHubIntegrationConfig {
const host = config.getOptionalString('host') ?? GITHUB_HOST;
let apiBaseUrl = config.getOptionalString('apiBaseUrl');
let rawBaseUrl = config.getOptionalString('rawBaseUrl');
const token = config.getOptionalString('token');
if (apiBaseUrl) {
apiBaseUrl = apiBaseUrl.replace(/\/+$/, '');
} else if (host === GITHUB_HOST) {
apiBaseUrl = GITHUB_API_BASE_URL;
}
if (rawBaseUrl) {
rawBaseUrl = rawBaseUrl.replace(/\/+$/, '');
} else if (host === GITHUB_HOST) {
rawBaseUrl = GITHUB_RAW_BASE_URL;
}
return { host, apiBaseUrl, rawBaseUrl, token };
}
/**
* Reads a set of GitHub integration configs, and inserts some defaults for
* public GitHub if not specified.
*
* @param configs All of the integration config objects
*/
export function readGitHubIntegrationConfigs(
configs: Config[],
): GitHubIntegrationConfig[] {
// First read all the explicit integrations
const result = configs.map(readGitHubIntegrationConfig);
// If no explicit github.com integration was added, put one in the list as
// a convenience
if (!result.some(c => c.host === GITHUB_HOST)) {
result.push({
host: GITHUB_HOST,
apiBaseUrl: GITHUB_API_BASE_URL,
rawBaseUrl: GITHUB_RAW_BASE_URL,
});
}
return result;
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
readGitHubIntegrationConfig,
readGitHubIntegrationConfigs,
} from './config';
export type { GitHubIntegrationConfig } from './config';
@@ -0,0 +1,91 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
GitLabIntegrationConfig,
readGitLabIntegrationConfig,
readGitLabIntegrationConfigs,
} from './config';
describe('readGitLabIntegrationConfig', () => {
function buildConfig(data: Partial<GitLabIntegrationConfig>): Config {
return ConfigReader.fromConfigs([{ context: '', data }]);
}
it('reads all values', () => {
const output = readGitLabIntegrationConfig(
buildConfig({
host: 'a.com',
token: 't',
}),
);
expect(output).toEqual({
host: 'a.com',
token: 't',
});
});
it('inserts the defaults if missing', () => {
const output = readGitLabIntegrationConfig(buildConfig({}));
expect(output).toEqual({ host: 'gitlab.com' });
});
it('rejects funky configs', () => {
const valid: any = {
host: 'a.com',
token: 't',
};
expect(() =>
readGitLabIntegrationConfig(buildConfig({ ...valid, host: 7 })),
).toThrow(/host/);
expect(() =>
readGitLabIntegrationConfig(buildConfig({ ...valid, token: 7 })),
).toThrow(/token/);
});
});
describe('readGitLabIntegrationConfigs', () => {
function buildConfig(data: Partial<GitLabIntegrationConfig>[]): Config[] {
return data.map(item =>
ConfigReader.fromConfigs([{ context: '', data: item }]),
);
}
it('reads all values', () => {
const output = readGitLabIntegrationConfigs(
buildConfig([
{
host: 'a.com',
token: 't',
},
]),
);
expect(output).toContainEqual({
host: 'a.com',
token: 't',
});
});
it('adds a default entry when missing', () => {
const output = readGitLabIntegrationConfigs(buildConfig([]));
expect(output).toEqual([
{
host: 'gitlab.com',
},
]);
});
});
+70
View File
@@ -0,0 +1,70 @@
/*
* Copyright 2020 Spotify AB
*
* 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';
const GITLAB_HOST = 'gitlab.com';
/**
* The configuration parameters for a single GitLab integration.
*/
export type GitLabIntegrationConfig = {
/**
* The host of the target that this matches on, e.g. "gitlab.com"
*/
host: string;
/**
* The authorization token to use for requests this provider.
*
* If no token is specified, anonymous access is used.
*/
token?: string;
};
/**
* Reads a single GitLab integration config.
*
* @param config The config object of a single integration
*/
export function readGitLabIntegrationConfig(
config: Config,
): GitLabIntegrationConfig {
const host = config.getOptionalString('host') ?? GITLAB_HOST;
const token = config.getOptionalString('token');
return { host, token };
}
/**
* Reads a set of GitLab integration configs, and inserts some defaults for
* public GitLab if not specified.
*
* @param configs All of the integration config objects
*/
export function readGitLabIntegrationConfigs(
configs: Config[],
): GitLabIntegrationConfig[] {
// First read all the explicit integrations
const result = configs.map(readGitLabIntegrationConfig);
// As a convenience we always make sure there's at least an unauthenticated
// reader for public gitlab repos.
if (!result.some(c => c.host === GITLAB_HOST)) {
result.push({ host: GITLAB_HOST });
}
return result;
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {
readGitLabIntegrationConfig,
readGitLabIntegrationConfigs,
} from './config';
export type { GitLabIntegrationConfig } from './config';
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright 2020 Spotify AB
*
* 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 * from './azure';
export * from './bitbucket';
export * from './github';
export * from './gitlab';
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* 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 {};