techdocs: Use @backstage/integration for scm tokens and request options
This commit is contained in:
@@ -16,13 +16,17 @@
|
||||
import fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
getAzureHostToken,
|
||||
getGitHubRequestOptions,
|
||||
getGitLabRequestOptions,
|
||||
getAzureRequestOptions,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
getGitHost,
|
||||
getGithubHostToken,
|
||||
getGitlabHostToken,
|
||||
getGitRepoType,
|
||||
getGitHubIntegrationConfig,
|
||||
getGitLabIntegrationConfig,
|
||||
getAzureIntegrationConfig,
|
||||
} from './git-auth';
|
||||
|
||||
interface IGitlabBranch {
|
||||
@@ -86,62 +90,15 @@ function getAzureApiUrl(url: string): URL {
|
||||
);
|
||||
}
|
||||
|
||||
function getGithubRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
Accept: 'application/vnd.github.v3.raw',
|
||||
};
|
||||
|
||||
const token = getGithubHostToken(config, host);
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `token ${token}`;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getGitlabRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {
|
||||
'PRIVATE-TOKEN': '',
|
||||
};
|
||||
|
||||
const token = getGitlabHostToken(config, host);
|
||||
if (token) {
|
||||
headers['PRIVATE-TOKEN'] = token;
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
function getAzureRequestOptions(config: Config, host: string): RequestInit {
|
||||
const headers: HeadersInit = {};
|
||||
|
||||
const token = getAzureHostToken(config, host);
|
||||
|
||||
if (token !== '') {
|
||||
headers.Authorization = `Basic ${Buffer.from(`:${token}`, 'utf8').toString(
|
||||
'base64',
|
||||
)}`;
|
||||
}
|
||||
|
||||
const requestOptions: RequestInit = {
|
||||
headers,
|
||||
};
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
async function getGithubDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGithubApiUrl(config, repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getGithubRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getGitHubIntegrationConfig(config, host);
|
||||
const options = getGitHubRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -169,9 +126,10 @@ async function getGitlabDefaultBranch(
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGitlabApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const gitlabHost = getGitHost(repositoryUrl);
|
||||
const options = getGitlabRequestOptions(config, gitlabHost);
|
||||
const integrationConfig = getGitLabIntegrationConfig(config, host);
|
||||
const options = getGitLabRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
@@ -203,7 +161,9 @@ async function getAzureDefaultBranch(
|
||||
): Promise<string> {
|
||||
const path = getAzureApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const options = getAzureRequestOptions(config, host);
|
||||
|
||||
const integrationConfig = getAzureIntegrationConfig(config, host);
|
||||
const options = getAzureRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const urlResponse = await fetch(path, options);
|
||||
@@ -235,12 +195,8 @@ async function getAzureDefaultBranch(
|
||||
|
||||
export const getDefaultBranch = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
*/
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getRootLogger, loadBackendConfig } from '@backstage/backend-common';
|
||||
import {
|
||||
readGitHubIntegrationConfigs,
|
||||
readGitLabIntegrationConfigs,
|
||||
readAzureIntegrationConfigs,
|
||||
GitHubIntegrationConfig,
|
||||
GitLabIntegrationConfig,
|
||||
AzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
export function getGitHost(url: string): string {
|
||||
const { resource } = parseGitUrl(url);
|
||||
@@ -34,85 +41,67 @@ export function getGitRepoType(url: string): string {
|
||||
return type;
|
||||
}
|
||||
|
||||
export function getGithubHostToken(
|
||||
export const getGitHubIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.github') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitHubIntegrationConfig => {
|
||||
const allGitHubConfigs = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.github.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.githubApi.privateToken') ??
|
||||
process.env.GITHUB_TOKEN;
|
||||
const gitHubIntegrationConfig = allGitHubConfigs.find(v => v.host === host);
|
||||
if (!gitHubIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitHub integration for the host ${host}`);
|
||||
}
|
||||
return gitHubIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getGitlabHostToken(
|
||||
export const getGitLabIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): GitLabIntegrationConfig => {
|
||||
const allGitLabConfigs = readGitLabIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.gitlab.privateToken') ??
|
||||
config.getOptionalString('catalog.processors.gitlabApi.privateToken') ??
|
||||
process.env.GITLAB_TOKEN;
|
||||
const gitLabIntegrationConfig = allGitLabConfigs.find(v => v.host === host);
|
||||
if (!gitLabIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitLab integration for the host ${host}`);
|
||||
}
|
||||
return gitLabIntegrationConfig;
|
||||
};
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
export function getAzureHostToken(
|
||||
export const getAzureIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): string | undefined {
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === host,
|
||||
): AzureIntegrationConfig => {
|
||||
const allAzureIntegrationConfig = readAzureIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [],
|
||||
);
|
||||
const token =
|
||||
hostConfig[0]?.getOptionalString('token') ??
|
||||
config.getOptionalString('catalog.processors.azureApi.privateToken') ??
|
||||
process.env.AZURE_TOKEN;
|
||||
|
||||
return token;
|
||||
}
|
||||
const azureIntegrationConfig = allAzureIntegrationConfig.find(
|
||||
v => v.host === host,
|
||||
);
|
||||
if (!azureIntegrationConfig) {
|
||||
throw new Error(`Unable to locate Azure integration for the host ${host}`);
|
||||
}
|
||||
return azureIntegrationConfig;
|
||||
};
|
||||
|
||||
export const getTokenForGitRepo = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string | undefined> => {
|
||||
// TODO(Rugvip): Config should not be loaded here, pass it in instead
|
||||
const config = await loadBackendConfig({
|
||||
logger: getRootLogger(),
|
||||
argv: process.argv,
|
||||
});
|
||||
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return getGithubHostToken(config, host);
|
||||
return getGitHubIntegrationConfig(config, host).token;
|
||||
case 'gitlab':
|
||||
return getGitlabHostToken(config, host);
|
||||
return getGitLabIntegrationConfig(config, host).token;
|
||||
case 'azure/api':
|
||||
return getAzureHostToken(config, host);
|
||||
|
||||
return getAzureIntegrationConfig(config, host).token;
|
||||
default:
|
||||
throw new Error('Failed to get repository type');
|
||||
throw new Error('Failed to get reository type');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
|
||||
@@ -19,10 +19,11 @@ import path from 'path';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import NodeGit, { Clone, Repository } from 'nodegit';
|
||||
import fs from 'fs-extra';
|
||||
import { InputError, UrlReader } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getDefaultBranch } from './default-branch';
|
||||
import { getGitRepoType, getTokenForGitRepo } from './git-auth';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { InputError, UrlReader } from '@backstage/backend-common';
|
||||
import { RemoteProtocol } from './stages/prepare/types';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@@ -94,6 +95,7 @@ export const getLocationForEntity = (
|
||||
|
||||
export const getGitRepositoryTempFolder = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repositoryUrl);
|
||||
// removes .git from git location path
|
||||
@@ -102,6 +104,7 @@ export const getGitRepositoryTempFolder = async (
|
||||
if (!parsedGitLocation.ref) {
|
||||
parsedGitLocation.ref = await getDefaultBranch(
|
||||
parsedGitLocation.toString('https'),
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,11 +121,12 @@ export const getGitRepositoryTempFolder = async (
|
||||
|
||||
export const checkoutGitRepository = async (
|
||||
repoUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl);
|
||||
const token = await getTokenForGitRepo(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl, config);
|
||||
const token = await getTokenForGitRepo(repoUrl, config);
|
||||
|
||||
if (fs.existsSync(repositoryTmpPath)) {
|
||||
try {
|
||||
@@ -170,9 +174,14 @@ export const checkoutGitRepository = async (
|
||||
|
||||
export const getLastCommitTimestamp = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<number> => {
|
||||
const repositoryLocation = await checkoutGitRepository(repositoryUrl, logger);
|
||||
const repositoryLocation = await checkoutGitRepository(
|
||||
repositoryUrl,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
|
||||
const repository = await Repository.open(repositoryLocation);
|
||||
const commit = await repository.getReferenceCommit('HEAD');
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { CommonGitPreparer } from './commonGit';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
@@ -43,11 +44,13 @@ const createMockEntity = (annotations = {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
describe('commonGit preparer', () => {
|
||||
it('should prepare temp docs path from github repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -62,7 +65,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from gitlab repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
@@ -77,7 +80,7 @@ describe('commonGit preparer', () => {
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from azure repo', async () => {
|
||||
const preparer = new CommonGitPreparer(logger);
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
|
||||
@@ -14,17 +14,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import path from 'path';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { PreparerBase } from './types';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PreparerBase } from './types';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class CommonGitPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -35,7 +38,11 @@ export class CommonGitPreparer implements PreparerBase {
|
||||
);
|
||||
|
||||
try {
|
||||
const repoPath = await checkoutGitRepository(target, this.logger);
|
||||
const repoPath = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
|
||||
return path.join(repoPath, parsedGitLocation.filepath);
|
||||
|
||||
@@ -13,8 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
|
||||
function normalizePath(path: string) {
|
||||
@@ -44,9 +45,11 @@ const createMockEntity = (annotations: {}) => {
|
||||
};
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
describe('directory preparer', () => {
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is relative', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -60,7 +63,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when techdocs-ref is absolute', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
@@ -74,7 +77,7 @@ describe('directory preparer', () => {
|
||||
});
|
||||
|
||||
it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => {
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/managed-by-location':
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { PreparerBase } from './types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import path from 'path';
|
||||
import { parseReferenceAnnotation, checkoutGitRepository } from '../../helpers';
|
||||
import { InputError } from '@backstage/backend-common';
|
||||
@@ -22,9 +23,11 @@ import parseGitUrl from 'git-url-parse';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
export class DirectoryPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(logger: Logger) {
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@@ -43,7 +46,11 @@ export class DirectoryPreparer implements PreparerBase {
|
||||
case 'url':
|
||||
case 'azure/api': {
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
const repoLocation = await checkoutGitRepository(target, this.logger);
|
||||
const repoLocation = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
|
||||
return path.dirname(
|
||||
path.join(repoLocation, parsedGitLocation.filepath),
|
||||
|
||||
@@ -30,18 +30,15 @@ export class Preparers implements PreparerBuilder {
|
||||
private preparerMap = new Map<RemoteProtocol, PreparerBase>();
|
||||
|
||||
static async fromConfig(
|
||||
// @ts-ignore
|
||||
// Config not used now, but will be used in urlPreparer when it starts using
|
||||
// @backstage/integration to get the tokens for providers.
|
||||
config: Config,
|
||||
{ logger, reader }: factoryOptions,
|
||||
): Promise<PreparerBuilder> {
|
||||
const preparers = new Preparers();
|
||||
|
||||
const directoryPreparer = new DirectoryPreparer(logger);
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger);
|
||||
preparers.register('dir', directoryPreparer);
|
||||
|
||||
const commonGitPreparer = new CommonGitPreparer(logger);
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
preparers.register('azure/api', commonGitPreparer);
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Logger } from 'winston';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { PreparerBase } from './types';
|
||||
import { getDocFilesFromRepository } from '../../helpers';
|
||||
import { Logger } from 'winston';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
|
||||
export class UrlPreparer implements PreparerBase {
|
||||
private readonly logger: Logger;
|
||||
|
||||
Reference in New Issue
Block a user