From f6826d91075f6097169747b8b78a7e02d04e7683 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Tue, 25 Aug 2020 16:21:53 +0200 Subject: [PATCH] Moved git clone to a helper functions and added tests for preparers (#2112) --- .../src/techdocs/stages/prepare/dir.test.ts | 21 ++++++++++ .../src/techdocs/stages/prepare/dir.ts | 42 +------------------ .../src/techdocs/stages/prepare/github.ts | 37 ++++------------ .../src/techdocs/stages/prepare/helpers.ts | 35 ++++++++++++++++ 4 files changed, 66 insertions(+), 69 deletions(-) diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts index fed7a0e107..1635995b9e 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts @@ -15,6 +15,12 @@ */ import { DirectoryPreparer } from './dir'; import { getVoidLogger } from '@backstage/backend-common'; +import { checkoutGitRepository } from './helpers'; + +jest.mock('./helpers', () => ({ + ...jest.requireActual<{}>('./helpers'), + checkoutGitRepository: jest.fn(() => '/tmp/backstage-repo/org/name/branch/'), +})); const logger = getVoidLogger(); @@ -59,4 +65,19 @@ describe('directory preparer', () => { '/our-documentation/techdocs', ); }); + + it('should merge managed-by-location and techdocs-ref when managed-by-location is a git repository', async () => { + const directoryPreparer = new DirectoryPreparer(logger); + + const mockEntity = createMockEntity({ + 'backstage.io/managed-by-location': + 'github:https://github.com/spotify/backstage/blob/master/catalog-info.yaml', + 'backstage.io/techdocs-ref': 'dir:./docs', + }); + + expect(await directoryPreparer.prepare(mockEntity)).toEqual( + '/tmp/backstage-repo/org/name/branch/docs', + ); + expect(checkoutGitRepository).toHaveBeenCalledTimes(1); + }); }); diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts index e108ac4ab9..cc0416c331 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts @@ -13,14 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs-extra'; -import os from 'os'; import { PreparerBase } from './types'; import { Entity } from '@backstage/catalog-model'; import path from 'path'; -import { parseReferenceAnnotation } from './helpers'; +import { parseReferenceAnnotation, checkoutGitRepository } from './helpers'; import { InputError } from '@backstage/backend-common'; -import { Clone } from 'nodegit'; import parseGitUrl from 'git-url-parse'; import { Logger } from 'winston'; @@ -31,41 +28,6 @@ export class DirectoryPreparer implements PreparerBase { this.logger = logger; } - private async cloneGithubRepo(entity: Entity) { - const { type, target } = parseReferenceAnnotation( - 'backstage.io/managed-by-location', - entity, - ); - - if (type !== 'github') { - throw new InputError(`Wrong target type: ${type}, should be 'github'`); - } - - const parsedGitLocation = parseGitUrl(target); - const repositoryTmpPath = path.join( - // fs.realpathSync fixes a problem with macOS returning a path that is a symlink - fs.realpathSync(os.tmpdir()), - 'backstage-repo', - parsedGitLocation.source, - parsedGitLocation.owner, - parsedGitLocation.name, - parsedGitLocation.ref, - ); - if (fs.existsSync(repositoryTmpPath)) { - return repositoryTmpPath; - } - const repositoryCheckoutUrl = parsedGitLocation.toString('https'); - - this.logger.debug( - `[TechDocs] Checking out repository ${repositoryCheckoutUrl} to ${repositoryTmpPath}`, - ); - - fs.mkdirSync(repositoryTmpPath, { recursive: true }); - await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath, {}); - - return repositoryTmpPath; - } - private async resolveManagedByLocationToDir(entity: Entity) { const { type, target } = parseReferenceAnnotation( 'backstage.io/managed-by-location', @@ -78,7 +40,7 @@ export class DirectoryPreparer implements PreparerBase { switch (type) { case 'github': { const parsedGitLocation = parseGitUrl(target); - const repoLocation = await this.cloneGithubRepo(entity); + const repoLocation = await checkoutGitRepository(target); return path.dirname( path.join(repoLocation, parsedGitLocation.filepath), diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts index aa8a4bb228..65c3e551cb 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts @@ -13,15 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import fs from 'fs-extra'; import path from 'path'; -import os from 'os'; import { Entity } from '@backstage/catalog-model'; import { InputError } from '@backstage/backend-common'; import { PreparerBase } from './types'; import parseGitUrl from 'git-url-parse'; -import { Clone } from 'nodegit'; -import { parseReferenceAnnotation } from './helpers'; +import { parseReferenceAnnotation, checkoutGitRepository } from './helpers'; import { Logger } from 'winston'; export class GithubPreparer implements PreparerBase { @@ -41,32 +38,14 @@ export class GithubPreparer implements PreparerBase { throw new InputError(`Wrong target type: ${type}, should be 'github'`); } - const parsedGitLocation = parseGitUrl(target); - const repositoryTmpPath = path.join( - // fs.realpathSync fixes a problem with macOS returning a path that is a symlink - fs.realpathSync(os.tmpdir()), - 'backstage-repo', - parsedGitLocation.source, - parsedGitLocation.owner, - parsedGitLocation.name, - parsedGitLocation.ref, - ); + try { + const repoPath = await checkoutGitRepository(target); - if (fs.existsSync(repositoryTmpPath)) { - this.logger.debug( - `[TechDocs] Found repository already checked out at ${repositoryTmpPath}`, - ); - return path.join(repositoryTmpPath, parsedGitLocation.filepath); + const parsedGitLocation = parseGitUrl(target); + return path.join(repoPath, parsedGitLocation.filepath); + } catch (error) { + this.logger.debug(`Repo checkout failed with error ${error.message}`); + throw error; } - const repositoryCheckoutUrl = parsedGitLocation.toString('https'); - - this.logger.debug( - `[TechDocs] Checking out repository ${repositoryCheckoutUrl} to ${repositoryTmpPath}`, - ); - - fs.mkdirSync(repositoryTmpPath, { recursive: true }); - await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath, {}); - - return path.join(repositoryTmpPath, parsedGitLocation.filepath); } } diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts index 3b3388d58d..05030390a9 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts @@ -16,6 +16,11 @@ import { Entity } from '@backstage/catalog-model'; import { InputError } from '@backstage/backend-common'; import { RemoteProtocol } from './types'; +import parseGitUrl from 'git-url-parse'; +import { Clone } from 'nodegit'; +import fs from 'fs-extra'; +import os from 'os'; +import path from 'path'; export type ParsedLocationAnnotation = { type: RemoteProtocol; @@ -52,3 +57,33 @@ export const parseReferenceAnnotation = ( target, }; }; + +export const clearGithubRepositoryCache = () => { + fs.removeSync(path.join(fs.realpathSync(os.tmpdir()), 'backstage-repo')); +}; + +export const checkoutGitRepository = async ( + repoUrl: string, +): Promise => { + const parsedGitLocation = parseGitUrl(repoUrl); + + const repositoryTmpPath = path.join( + // fs.realpathSync fixes a problem with macOS returning a path that is a symlink + fs.realpathSync(os.tmpdir()), + 'backstage-repo', + parsedGitLocation.source, + parsedGitLocation.owner, + parsedGitLocation.name, + parsedGitLocation.ref, + ); + + if (fs.existsSync(repositoryTmpPath)) { + return repositoryTmpPath; + } + const repositoryCheckoutUrl = parsedGitLocation.toString('https'); + + fs.mkdirSync(repositoryTmpPath, { recursive: true }); + await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath, {}); + + return repositoryTmpPath; +};