Remove all legacy preparer code and dependencies.

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-07-28 18:45:51 +02:00
parent 602b1e5c97
commit 8ef28f5dbc
13 changed files with 18 additions and 772 deletions
+4 -102
View File
@@ -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)
//
-3
View File
@@ -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;
}
};
-109
View File
@@ -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;
}
};
+1 -132
View File
@@ -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,
-2
View File
@@ -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';