Remove all legacy preparer code and dependencies.
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -5,50 +5,24 @@
|
||||
```ts
|
||||
/// <reference types="node" />
|
||||
|
||||
import { AzureIntegrationConfig } from '@backstage/integration';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ContainerRunner } from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import express from 'express';
|
||||
import { GitHubIntegrationConfig } from '@backstage/integration';
|
||||
import { GitLabIntegrationConfig } from '@backstage/integration';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { PluginEndpointDiscovery } from '@backstage/backend-common';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { UrlReader } from '@backstage/backend-common';
|
||||
import { Writable } from 'stream';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "checkoutGitRepository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const checkoutGitRepository: (
|
||||
repoUrl: string,
|
||||
config: Config,
|
||||
logger: Logger_2,
|
||||
) => Promise<string>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "CommonGitPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class CommonGitPreparer implements PreparerBase {
|
||||
constructor(config: Config, logger: Logger_2);
|
||||
// Warning: (ae-forgotten-export) The symbol "PreparerResponse" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
prepare(
|
||||
entity: Entity,
|
||||
options?: {
|
||||
etag?: string;
|
||||
},
|
||||
): Promise<PreparerResponse>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DirectoryPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class DirectoryPreparer implements PreparerBase {
|
||||
constructor(config: Config, _logger: Logger_2, reader: UrlReader);
|
||||
// Warning: (ae-forgotten-export) The symbol "PreparerResponse" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
prepare(
|
||||
entity: Entity,
|
||||
@@ -121,22 +95,6 @@ export class Generators implements GeneratorBuilder {
|
||||
register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase): void;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getAzureIntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getAzureIntegrationConfig: (
|
||||
config: Config,
|
||||
host: string,
|
||||
) => AzureIntegrationConfig;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getDefaultBranch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getDefaultBranch: (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
) => Promise<string>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getDocFilesFromRepository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -151,48 +109,6 @@ export const getDocFilesFromRepository: (
|
||||
| undefined,
|
||||
) => Promise<PreparerResponse>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getGitHost" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function getGitHost(url: string): string;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getGitHubIntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getGitHubIntegrationConfig: (
|
||||
config: Config,
|
||||
host: string,
|
||||
) => GitHubIntegrationConfig;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getGitLabIntegrationConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getGitLabIntegrationConfig: (
|
||||
config: Config,
|
||||
host: string,
|
||||
) => GitLabIntegrationConfig;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getGitRepositoryTempFolder" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getGitRepositoryTempFolder: (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
) => Promise<string>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getGitRepoType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function getGitRepoType(url: string): string;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getLastCommitTimestamp" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getLastCommitTimestamp: (
|
||||
repositoryLocation: string,
|
||||
logger: Logger_2,
|
||||
) => Promise<number>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getLocationForEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -201,14 +117,6 @@ export const getLocationForEntity: (
|
||||
scmIntegration: ScmIntegrationRegistry,
|
||||
) => ParsedLocationAnnotation;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "getTokenForGitRepo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const getTokenForGitRepo: (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
) => Promise<string | undefined>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "ParsedLocationAnnotation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -311,14 +219,8 @@ export type PublisherType =
|
||||
|
||||
// Warning: (ae-missing-release-tag) "RemoteProtocol" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public
|
||||
export type RemoteProtocol =
|
||||
| 'url'
|
||||
| 'dir'
|
||||
| 'github'
|
||||
| 'gitlab'
|
||||
| 'file'
|
||||
| 'azure/api';
|
||||
// @public (undocumented)
|
||||
export type RemoteProtocol = 'url' | 'dir';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "TechdocsGenerator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -46,10 +46,8 @@
|
||||
"@google-cloud/storage": "^5.6.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"aws-sdk": "^2.840.0",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"express": "^4.17.1",
|
||||
"fs-extra": "9.1.0",
|
||||
"git-url-parse": "~11.4.4",
|
||||
"js-yaml": "^4.0.0",
|
||||
"json5": "^2.1.3",
|
||||
"mime-types": "^2.1.27",
|
||||
@@ -62,7 +60,6 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.5",
|
||||
"@types/fs-extra": "^9.0.5",
|
||||
"@types/git-url-parse": "^9.0.0",
|
||||
"@types/js-yaml": "^4.0.0",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* 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 fetch from 'cross-fetch';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
getGitHubRequestOptions,
|
||||
getGitLabRequestOptions,
|
||||
getAzureRequestOptions,
|
||||
} from '@backstage/integration';
|
||||
import {
|
||||
getGitHost,
|
||||
getGitRepoType,
|
||||
getGitHubIntegrationConfig,
|
||||
getGitLabIntegrationConfig,
|
||||
getAzureIntegrationConfig,
|
||||
} from './git-auth';
|
||||
|
||||
interface IGitlabBranch {
|
||||
name: string;
|
||||
merged: boolean;
|
||||
protected: boolean;
|
||||
default: boolean;
|
||||
developers_can_push: boolean;
|
||||
developers_can_merge: boolean;
|
||||
can_push: boolean;
|
||||
web_url: string;
|
||||
commit: {
|
||||
author_email: string;
|
||||
author_name: string;
|
||||
authored_date: string;
|
||||
committed_date: string;
|
||||
committer_email: string;
|
||||
committer_name: string;
|
||||
id: string;
|
||||
short_id: string;
|
||||
title: string;
|
||||
message: string;
|
||||
parent_ids: string[];
|
||||
};
|
||||
}
|
||||
|
||||
function getGithubApiUrl(config: Config, url: string): URL {
|
||||
const { resource, owner, name } = parseGitUrl(url);
|
||||
const providerConfigs =
|
||||
config.getOptionalConfigArray('integrations.github') ?? [];
|
||||
|
||||
const hostConfig = providerConfigs.filter(
|
||||
providerConfig => providerConfig.getOptionalString('host') === resource,
|
||||
);
|
||||
|
||||
const apiBaseUrl =
|
||||
hostConfig[0]?.getOptionalString('apiBaseUrl') ?? 'https://api.github.com';
|
||||
const apiRepos = 'repos';
|
||||
|
||||
return new URL(`${apiBaseUrl}/${apiRepos}/${owner}/${name}`);
|
||||
}
|
||||
|
||||
function getGitlabApiUrl(url: string): URL {
|
||||
const { protocol, resource, full_name: fullName } = parseGitUrl(url);
|
||||
const apiProjectsBasePath = 'api/v4/projects';
|
||||
const project = encodeURIComponent(fullName);
|
||||
const branches = 'repository/branches';
|
||||
|
||||
return new URL(
|
||||
`${protocol}://${resource}/${apiProjectsBasePath}/${project}/${branches}`,
|
||||
);
|
||||
}
|
||||
|
||||
function getAzureApiUrl(url: string): URL {
|
||||
const { protocol, resource, organization, owner, name } = parseGitUrl(url);
|
||||
const apiRepoPath = '_apis/git/repositories';
|
||||
const apiVersion = 'api-version=6.0';
|
||||
|
||||
return new URL(
|
||||
`${protocol}://${resource}/${organization}/${owner}/${apiRepoPath}/${name}?${apiVersion}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function getGithubDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGithubApiUrl(config, repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const integrationConfig = getGitHubIntegrationConfig(config, host);
|
||||
const options = getGitHubRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
|
||||
if (!raw.ok) {
|
||||
throw new Error(
|
||||
`Failed to load url: ${raw.status} ${raw.statusText}. Make sure you have permission to repository: ${repositoryUrl}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { default_branch: branch } = await raw.json();
|
||||
|
||||
if (!branch) {
|
||||
throw new Error('Not found github default branch');
|
||||
}
|
||||
|
||||
return branch;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get github default branch: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function getGitlabDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getGitlabApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const integrationConfig = getGitLabIntegrationConfig(config, host);
|
||||
const options = getGitLabRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const raw = await fetch(path, options);
|
||||
|
||||
if (!raw.ok) {
|
||||
throw new Error(
|
||||
`Failed to load url: ${raw.status} ${raw.statusText}. Make sure you have permission to repository: ${repositoryUrl}`,
|
||||
);
|
||||
}
|
||||
|
||||
const result = await raw.json();
|
||||
const { name } = (result || []).find(
|
||||
(branch: IGitlabBranch) => branch.default === true,
|
||||
);
|
||||
|
||||
if (!name) {
|
||||
throw new Error('Not found gitlab default branch');
|
||||
}
|
||||
|
||||
return name;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get gitlab default branch: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function getAzureDefaultBranch(
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> {
|
||||
const path = getAzureApiUrl(repositoryUrl).toString();
|
||||
const host = getGitHost(repositoryUrl);
|
||||
|
||||
const integrationConfig = getAzureIntegrationConfig(config, host);
|
||||
const options = getAzureRequestOptions(integrationConfig);
|
||||
|
||||
try {
|
||||
const urlResponse = await fetch(path, options);
|
||||
if (!urlResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to load url: ${urlResponse.status} ${urlResponse.statusText}. Make sure you have permission to repository: ${repositoryUrl}`,
|
||||
);
|
||||
}
|
||||
const urlResult = await urlResponse.json();
|
||||
|
||||
const idResponse = await fetch(urlResult.url, options);
|
||||
if (!idResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to load url: ${idResponse.status} ${idResponse.statusText}. Make sure you have permission to repository: ${urlResult.repository.url}`,
|
||||
);
|
||||
}
|
||||
const idResult = await idResponse.json();
|
||||
const name = idResult.defaultBranch;
|
||||
|
||||
if (!name) {
|
||||
throw new Error('Not found Azure DevOps default branch');
|
||||
}
|
||||
|
||||
return name;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to get Azure DevOps default branch: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const getDefaultBranch = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return await getGithubDefaultBranch(repositoryUrl, config);
|
||||
case 'gitlab':
|
||||
return await getGitlabDefaultBranch(repositoryUrl, config);
|
||||
case 'azure/api':
|
||||
return await getAzureDefaultBranch(repositoryUrl, config);
|
||||
|
||||
default:
|
||||
throw new Error('Failed to get repository type');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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 parseGitUrl from 'git-url-parse';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
readGitHubIntegrationConfigs,
|
||||
readGitLabIntegrationConfigs,
|
||||
readAzureIntegrationConfigs,
|
||||
GitHubIntegrationConfig,
|
||||
GitLabIntegrationConfig,
|
||||
AzureIntegrationConfig,
|
||||
} from '@backstage/integration';
|
||||
|
||||
export function getGitHost(url: string): string {
|
||||
const { resource } = parseGitUrl(url);
|
||||
return resource;
|
||||
}
|
||||
|
||||
export function getGitRepoType(url: string): string {
|
||||
const typeMapping = [
|
||||
{ url: /github*/g, type: 'github' },
|
||||
{ url: /gitlab*/g, type: 'gitlab' },
|
||||
{ url: /azure*/g, type: 'azure/api' },
|
||||
];
|
||||
|
||||
const type = typeMapping.filter(item => item.url.test(url))[0]?.type;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
export const getGitHubIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): GitHubIntegrationConfig => {
|
||||
const allGitHubConfigs = readGitHubIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.github') ?? [],
|
||||
);
|
||||
const gitHubIntegrationConfig = allGitHubConfigs.find(v => v.host === host);
|
||||
if (!gitHubIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitHub integration for the host ${host}`);
|
||||
}
|
||||
return gitHubIntegrationConfig;
|
||||
};
|
||||
|
||||
export const getGitLabIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): GitLabIntegrationConfig => {
|
||||
const allGitLabConfigs = readGitLabIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.gitlab') ?? [],
|
||||
);
|
||||
const gitLabIntegrationConfig = allGitLabConfigs.find(v => v.host === host);
|
||||
if (!gitLabIntegrationConfig) {
|
||||
throw new Error(`Unable to locate GitLab integration for the host ${host}`);
|
||||
}
|
||||
return gitLabIntegrationConfig;
|
||||
};
|
||||
|
||||
export const getAzureIntegrationConfig = (
|
||||
config: Config,
|
||||
host: string,
|
||||
): AzureIntegrationConfig => {
|
||||
const allAzureIntegrationConfig = readAzureIntegrationConfigs(
|
||||
config.getOptionalConfigArray('integrations.azure') ?? [],
|
||||
);
|
||||
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> => {
|
||||
const host = getGitHost(repositoryUrl);
|
||||
const type = getGitRepoType(repositoryUrl);
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'github':
|
||||
return getGitHubIntegrationConfig(config, host).token;
|
||||
case 'gitlab':
|
||||
return getGitLabIntegrationConfig(config, host).token;
|
||||
case 'azure/api':
|
||||
return getAzureIntegrationConfig(config, host).token;
|
||||
default:
|
||||
throw new Error('Failed to get repository type');
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -14,26 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Git,
|
||||
resolveSafeChildPath,
|
||||
UrlReader,
|
||||
} from '@backstage/backend-common';
|
||||
import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common';
|
||||
import {
|
||||
Entity,
|
||||
getEntitySourceLocation,
|
||||
parseLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import fs from 'fs-extra';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import { getDefaultBranch } from './default-branch';
|
||||
import { getGitRepoType, getTokenForGitRepo } from './git-auth';
|
||||
import { PreparerResponse, RemoteProtocol } from './stages/prepare/types';
|
||||
|
||||
export type ParsedLocationAnnotation = {
|
||||
@@ -121,9 +111,6 @@ export const getLocationForEntity = (
|
||||
);
|
||||
|
||||
switch (annotation.type) {
|
||||
case 'github':
|
||||
case 'gitlab':
|
||||
case 'azure/api':
|
||||
case 'url':
|
||||
return annotation;
|
||||
case 'dir':
|
||||
@@ -133,124 +120,6 @@ export const getLocationForEntity = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getGitRepositoryTempFolder = async (
|
||||
repositoryUrl: string,
|
||||
config: Config,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repositoryUrl);
|
||||
// removes .git from git location path
|
||||
parsedGitLocation.git_suffix = false;
|
||||
|
||||
if (!parsedGitLocation.ref) {
|
||||
parsedGitLocation.ref = await getDefaultBranch(
|
||||
parsedGitLocation.toString('https'),
|
||||
config,
|
||||
);
|
||||
}
|
||||
|
||||
return path.join(
|
||||
// fs.realpathSync fixes a problem with macOS returning a path that is a symlink
|
||||
fs.realpathSync(os.tmpdir()),
|
||||
'backstage-repo',
|
||||
parsedGitLocation.resource,
|
||||
parsedGitLocation.owner,
|
||||
parsedGitLocation.name,
|
||||
parsedGitLocation.ref,
|
||||
);
|
||||
};
|
||||
|
||||
export const checkoutGitRepository = async (
|
||||
repoUrl: string,
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
): Promise<string> => {
|
||||
const parsedGitLocation = parseGitUrl(repoUrl);
|
||||
const repositoryTmpPath = await getGitRepositoryTempFolder(repoUrl, config);
|
||||
const token = await getTokenForGitRepo(repoUrl, config);
|
||||
|
||||
// Initialize a git client
|
||||
let git = Git.fromAuth({ logger });
|
||||
|
||||
// Docs about why username and password are set to these specific values.
|
||||
// https://isomorphic-git.org/docs/en/onAuth#oauth2-tokens
|
||||
if (token) {
|
||||
const type = getGitRepoType(repoUrl);
|
||||
switch (type) {
|
||||
case 'github':
|
||||
git = Git.fromAuth({
|
||||
username: 'x-access-token',
|
||||
password: token,
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `x-access-token:${token}`;
|
||||
break;
|
||||
case 'gitlab':
|
||||
git = Git.fromAuth({
|
||||
username: 'oauth2',
|
||||
password: token,
|
||||
logger,
|
||||
});
|
||||
parsedGitLocation.token = `dummyUsername:${token}`;
|
||||
parsedGitLocation.git_suffix = true;
|
||||
break;
|
||||
case 'azure/api':
|
||||
git = Git.fromAuth({
|
||||
username: 'notempty',
|
||||
password: token,
|
||||
logger: logger,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
parsedGitLocation.token = `:${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Pull from repository if it has already been cloned.
|
||||
if (fs.existsSync(repositoryTmpPath)) {
|
||||
try {
|
||||
const currentBranchName = await git.currentBranch({
|
||||
dir: repositoryTmpPath,
|
||||
});
|
||||
|
||||
await git.fetch({ dir: repositoryTmpPath, remote: 'origin' });
|
||||
await git.merge({
|
||||
dir: repositoryTmpPath,
|
||||
theirs: `origin/${currentBranchName}`,
|
||||
ours: currentBranchName || undefined,
|
||||
author: { name: 'Backstage TechDocs', email: 'techdocs@backstage.io' },
|
||||
committer: {
|
||||
name: 'Backstage TechDocs',
|
||||
email: 'techdocs@backstage.io',
|
||||
},
|
||||
});
|
||||
return repositoryTmpPath;
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
`Found error "${e.message}" in cached repository "${repoUrl}" when getting latest changes. Removing cached repository.`,
|
||||
);
|
||||
fs.removeSync(repositoryTmpPath);
|
||||
}
|
||||
}
|
||||
|
||||
const repositoryCheckoutUrl = parsedGitLocation.toString('https');
|
||||
|
||||
fs.mkdirSync(repositoryTmpPath, { recursive: true });
|
||||
await git.clone({ url: repositoryCheckoutUrl, dir: repositoryTmpPath });
|
||||
|
||||
return repositoryTmpPath;
|
||||
};
|
||||
|
||||
export const getLastCommitTimestamp = async (
|
||||
repositoryLocation: string,
|
||||
logger: Logger,
|
||||
): Promise<number> => {
|
||||
const git = Git.fromAuth({ logger });
|
||||
const sha = await git.resolveRef({ dir: repositoryLocation, ref: 'HEAD' });
|
||||
const commit = await git.readCommit({ dir: repositoryLocation, sha });
|
||||
|
||||
return commit.commit.committer.timestamp;
|
||||
};
|
||||
|
||||
export const getDocFilesFromRepository = async (
|
||||
reader: UrlReader,
|
||||
entity: Entity,
|
||||
|
||||
@@ -15,5 +15,3 @@
|
||||
*/
|
||||
export * from './stages';
|
||||
export * from './helpers';
|
||||
export * from './default-branch';
|
||||
export * from './git-auth';
|
||||
|
||||
@@ -124,7 +124,7 @@ describe('helpers', () => {
|
||||
};
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'file',
|
||||
type: 'file' as RemoteProtocol,
|
||||
target: '/home/user/workspace/docs-repository/catalog-info.yaml',
|
||||
};
|
||||
|
||||
@@ -147,7 +147,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should return correct target url for supported hosts', () => {
|
||||
const parsedLocationAnnotation1: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/backstage/backstage.git',
|
||||
};
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('helpers', () => {
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation2: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/org/repo',
|
||||
};
|
||||
|
||||
@@ -165,7 +165,7 @@ describe('helpers', () => {
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation3: ParsedLocationAnnotation = {
|
||||
type: 'gitlab',
|
||||
type: 'gitlab' as RemoteProtocol,
|
||||
target: 'https://gitlab.com/org/repo',
|
||||
};
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('helpers', () => {
|
||||
);
|
||||
|
||||
const parsedLocationAnnotation4: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target:
|
||||
'github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
};
|
||||
@@ -200,7 +200,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should add repo_url to mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/backstage/backstage',
|
||||
};
|
||||
|
||||
@@ -219,7 +219,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should add repo_url to mkdocs.yml that contains custom yaml tags', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/backstage/backstage',
|
||||
};
|
||||
|
||||
@@ -241,7 +241,7 @@ describe('helpers', () => {
|
||||
|
||||
it('should not override existing repo_url in mkdocs.yml', async () => {
|
||||
const parsedLocationAnnotation: ParsedLocationAnnotation = {
|
||||
type: 'github',
|
||||
type: 'github' as RemoteProtocol,
|
||||
target: 'https://github.com/neworg/newrepo',
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import path, { resolve as resolvePath } from 'path';
|
||||
import { PassThrough, Writable } from 'stream';
|
||||
import { Logger } from 'winston';
|
||||
import { ParsedLocationAnnotation } from '../../helpers';
|
||||
import { RemoteProtocol } from '../prepare/types';
|
||||
import { SupportedGeneratorKey } from './types';
|
||||
|
||||
// TODO: Implement proper support for more generators.
|
||||
@@ -94,12 +93,12 @@ export const runCommand = async ({
|
||||
* - (anything that is not valid as described above)
|
||||
*
|
||||
* @param {string} repoUrl URL supposed to be used as repo_url in mkdocs.yml
|
||||
* @param {RemoteProtocol} locationType Type of source code host - github, gitlab, dir, url, etc.
|
||||
* @param {string} locationType Type of source code host - github, gitlab, dir, url, etc.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isValidRepoUrlForMkdocs = (
|
||||
repoUrl: string,
|
||||
locationType: RemoteProtocol,
|
||||
locationType: string,
|
||||
): boolean => {
|
||||
// Trim trailing slash
|
||||
const cleanRepoUrl = repoUrl.replace(/\/$/, '');
|
||||
@@ -266,6 +265,8 @@ export const patchMkdocsYmlPreBuild = async (
|
||||
const repoUrl = getRepoUrlFromLocationAnnotation(parsedLocationAnnotation);
|
||||
if (repoUrl !== undefined) {
|
||||
// mkdocs.yml will not build with invalid repo_url. So, make sure it is valid.
|
||||
// TODO: this is no longer working/meaningful because annotation type is
|
||||
// now only ever "url" or "dir." Should be re-implemented!
|
||||
if (isValidRepoUrlForMkdocs(repoUrl, parsedLocationAnnotation.type)) {
|
||||
mkdocsYml.repo_url = repoUrl;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* 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 { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { checkoutGitRepository } from '../../helpers';
|
||||
import { CommonGitPreparer } from './commonGit';
|
||||
|
||||
function normalizePath(path: string) {
|
||||
return path
|
||||
.replace(/^[a-z]:/i, '')
|
||||
.split('\\')
|
||||
.join('/');
|
||||
}
|
||||
|
||||
jest.mock('../../helpers', () => ({
|
||||
...jest.requireActual<{}>('../../helpers'),
|
||||
checkoutGitRepository: jest.fn(() => '/tmp/backstage-repo/org/name/branch'),
|
||||
getLastCommitTimestamp: jest.fn(() => 12345678),
|
||||
}));
|
||||
|
||||
const createMockEntity = (annotations = {}) => {
|
||||
return {
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'test-component-name',
|
||||
annotations: {
|
||||
...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(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
'github:https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/examples/documented-component',
|
||||
});
|
||||
|
||||
const { preparedDir } = await preparer.prepare(mockEntity);
|
||||
expect(checkoutGitRepository).toHaveBeenCalledTimes(1);
|
||||
expect(normalizePath(preparedDir)).toEqual(
|
||||
'/tmp/backstage-repo/org/name/branch/plugins/techdocs-backend/examples/documented-component',
|
||||
);
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from gitlab repo', async () => {
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
'gitlab:https://gitlab.com/xesjkeee/go-logger/blob/master/catalog-info.yaml',
|
||||
});
|
||||
|
||||
const { preparedDir } = await preparer.prepare(mockEntity);
|
||||
expect(checkoutGitRepository).toHaveBeenCalledTimes(2);
|
||||
expect(normalizePath(preparedDir)).toEqual(
|
||||
'/tmp/backstage-repo/org/name/branch/catalog-info.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
it('should prepare temp docs path from azure repo', async () => {
|
||||
const preparer = new CommonGitPreparer(mockConfig, logger);
|
||||
|
||||
const mockEntity = createMockEntity({
|
||||
'backstage.io/techdocs-ref':
|
||||
'azure/api:https://dev.azure.com/backstage-org/backstage-project/_git/template-repo?path=%2Ftemplate.yaml',
|
||||
});
|
||||
|
||||
const { preparedDir } = await preparer.prepare(mockEntity);
|
||||
expect(checkoutGitRepository).toHaveBeenCalledTimes(3);
|
||||
expect(normalizePath(preparedDir)).toEqual(
|
||||
'/tmp/backstage-repo/org/name/branch/template.yaml',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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 { NotModifiedError } from '@backstage/errors';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import parseGitUrl from 'git-url-parse';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import {
|
||||
checkoutGitRepository,
|
||||
getLastCommitTimestamp,
|
||||
parseReferenceAnnotation,
|
||||
} from '../../helpers';
|
||||
import { PreparerBase, PreparerResponse } from './types';
|
||||
|
||||
export class CommonGitPreparer implements PreparerBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
|
||||
constructor(config: Config, logger: Logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
async prepare(
|
||||
entity: Entity,
|
||||
options?: { etag?: string },
|
||||
): Promise<PreparerResponse> {
|
||||
this.logger.warn(
|
||||
`You are using the legacy git preparer in TechDocs for \`${entity.metadata.name}\` which will be removed in near future (March 2021). ` +
|
||||
`Migrate to URL reader by updating \`backstage.io/techdocs-ref\` annotation in \`catalog-info.yaml\` ` +
|
||||
`to be prefixed with \`url:\`. Read the migration guide and benefits at https://github.com/backstage/backstage/issues/4409 `,
|
||||
);
|
||||
|
||||
const { target } = parseReferenceAnnotation(
|
||||
'backstage.io/techdocs-ref',
|
||||
entity,
|
||||
);
|
||||
|
||||
try {
|
||||
// Update repository or do a fresh clone.
|
||||
const repoPath = await checkoutGitRepository(
|
||||
target,
|
||||
this.config,
|
||||
this.logger,
|
||||
);
|
||||
// Check if etag has changed for cache invalidation.
|
||||
const etag = await getLastCommitTimestamp(repoPath, this.logger);
|
||||
if (options?.etag === etag.toString()) {
|
||||
throw new NotModifiedError();
|
||||
}
|
||||
|
||||
const parsedGitLocation = parseGitUrl(target);
|
||||
return {
|
||||
preparedDir: path.join(repoPath, parsedGitLocation.filepath),
|
||||
etag: etag.toString(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof NotModifiedError) {
|
||||
this.logger.debug(`Cache is valid for etag ${options?.etag}`);
|
||||
} else {
|
||||
this.logger.debug(`Repo checkout failed with error ${error.message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { DirectoryPreparer } from './dir';
|
||||
export { CommonGitPreparer } from './commonGit';
|
||||
export { UrlPreparer } from './url';
|
||||
export { Preparers } from './preparers';
|
||||
export type { PreparerBuilder, PreparerBase, RemoteProtocol } from './types';
|
||||
|
||||
@@ -19,7 +19,6 @@ import { Config } from '@backstage/config';
|
||||
import { Logger } from 'winston';
|
||||
import { parseReferenceAnnotation } from '../../helpers';
|
||||
import { DirectoryPreparer } from './dir';
|
||||
import { CommonGitPreparer } from './commonGit';
|
||||
import { UrlPreparer } from './url';
|
||||
import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types';
|
||||
|
||||
@@ -47,12 +46,6 @@ export class Preparers implements PreparerBuilder {
|
||||
const directoryPreparer = new DirectoryPreparer(config, logger, reader);
|
||||
preparers.register('dir', directoryPreparer);
|
||||
|
||||
// Common git preparers will be deprecated soon.
|
||||
const commonGitPreparer = new CommonGitPreparer(config, logger);
|
||||
preparers.register('github', commonGitPreparer);
|
||||
preparers.register('gitlab', commonGitPreparer);
|
||||
preparers.register('azure/api', commonGitPreparer);
|
||||
|
||||
return preparers;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,14 +48,4 @@ export type PreparerBuilder = {
|
||||
get(entity: Entity): PreparerBase;
|
||||
};
|
||||
|
||||
/**
|
||||
* Everything except `url` will be deprecated.
|
||||
* Read more https://github.com/backstage/backstage/issues/4409
|
||||
*/
|
||||
export type RemoteProtocol =
|
||||
| 'url'
|
||||
| 'dir'
|
||||
| 'github'
|
||||
| 'gitlab'
|
||||
| 'file'
|
||||
| 'azure/api';
|
||||
export type RemoteProtocol = 'url' | 'dir';
|
||||
|
||||
Reference in New Issue
Block a user