diff --git a/.changeset/good-needles-clap.md b/.changeset/good-needles-clap.md new file mode 100644 index 0000000000..4eb8e1e26f --- /dev/null +++ b/.changeset/good-needles-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': minor +--- + +Implement readTree on BitBucketUrlReader diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts index 76db191909..d7e4632a69 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.test.ts @@ -47,7 +47,12 @@ describe('BitbucketUrlReader', () => { msw.setupDefaultHandlers(worker); const repoBuffer = fs.readFileSync( - path.resolve('src', 'reading', '__fixtures__', 'repo.zip'), + path.resolve( + 'src', + 'reading', + '__fixtures__', + 'bitbucket-repo-with-commit-hash.zip', + ), ); it('returns the wanted files from an archive', async () => { @@ -61,10 +66,20 @@ describe('BitbucketUrlReader', () => { ctx.body(repoBuffer), ), ), + rest.get( + 'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }], + }), + ), + ), ); const processor = new BitbucketUrlReader( - { host: 'bitbucket.org', apiBaseUrl: 'x' }, + { host: 'bitbucket.org', apiBaseUrl: 'https://api.bitbucket.org/2.0' }, { treeResponseFactory }, ); @@ -75,17 +90,17 @@ describe('BitbucketUrlReader', () => { const files = await response.files(); expect(files.length).toBe(2); - const mkDocsFile = await files[0].content(); - const indexMarkdownFile = await files[1].content(); + const indexMarkdownFile = await files[0].content(); + const mkDocsFile = await files[1].content(); - expect(mkDocsFile.toString()).toBe('site_name: Test\n'); expect(indexMarkdownFile.toString()).toBe('# Test\n'); + expect(mkDocsFile.toString()).toBe('site_name: Test\n'); }); it('uses private bitbucket host', async () => { worker.use( rest.get( - 'https://bitbucket.mycompany.net/projects/a/repos/b/archive?format=tgz', + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/archive?format=tgz', (_, res, ctx) => res( ctx.status(200), @@ -93,15 +108,28 @@ describe('BitbucketUrlReader', () => { ctx.body(repoBuffer), ), ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/?until=master', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }], + }), + ), + ), ); const processor = new BitbucketUrlReader( - { host: 'bitbucket.mycompany.net', apiBaseUrl: 'x' }, + { + host: 'bitbucket.mycompany.net', + apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0', + }, { treeResponseFactory }, ); const response = await processor.readTree( - 'https://bitbucket.mycompany.net/projects/a/repos/b/browse/docs', + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/docs', ); const files = await response.files(); @@ -114,7 +142,7 @@ describe('BitbucketUrlReader', () => { it('must specify a branch', async () => { const processor = new BitbucketUrlReader( - { host: 'bitbucket.org', apiBaseUrl: 'x' }, + { host: 'bitbucket.org', apiBaseUrl: 'https://api.bitbucket.org/2.0' }, { treeResponseFactory }, ); @@ -136,10 +164,20 @@ describe('BitbucketUrlReader', () => { ctx.body(repoBuffer), ), ), + rest.get( + 'https://api.bitbucket.org/2.0/repositories/backstage/mock/commits/master', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + values: [{ hash: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }], + }), + ), + ), ); const processor = new BitbucketUrlReader( - { host: 'bitbucket.org', apiBaseUrl: 'x' }, + { host: 'bitbucket.org', apiBaseUrl: 'https://api.bitbucket.org/2.0' }, { treeResponseFactory }, ); diff --git a/packages/backend-common/src/reading/BitbucketUrlReader.ts b/packages/backend-common/src/reading/BitbucketUrlReader.ts index 00a386d4cc..982422817a 100644 --- a/packages/backend-common/src/reading/BitbucketUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketUrlReader.ts @@ -99,14 +99,8 @@ export class BitbucketUrlReader implements UrlReader { url: string, options?: ReadTreeOptions, ): Promise { - const { - name: repoName, - owner, - ref, - protocol, - resource, - // filepath, - } = parseGitUri(url); + const gitUrl: parseGitUri.GitUrl = parseGitUri(url); + const { name: repoName, owner, ref, protocol, resource, filepath } = gitUrl; const isHosted = resource === 'bitbucket.org'; @@ -130,10 +124,11 @@ export class BitbucketUrlReader implements UrlReader { throw new Error(message); } + const lastCommitShortHash = await this.getLastCommitShortHash(gitUrl); + return this.treeResponseFactory.fromZipArchive({ stream: (response.body as unknown) as Readable, - // TODO: The zip contains the commit hash, not branch name - may need additional api call to get it - // path: `${owner}-${repoName}-4f9778cd49a4/${filepath}`, + path: `${owner}-${repoName}-${lastCommitShortHash}/${filepath}`, filter: options?.filter, }); } @@ -146,4 +141,51 @@ export class BitbucketUrlReader implements UrlReader { } return `bitbucket{host=${host},authed=${authed}}`; } + + private async getLastCommitShortHash( + gitUrl: parseGitUri.GitUrl, + ): Promise { + const { name: repoName, owner, ref, protocol, resource } = gitUrl; + + const isHosted = resource === 'bitbucket.org'; + + const branch = ref ? ref : 'master'; + const commitsApiUrl = isHosted + ? `${protocol}://${this.config.apiBaseUrl}/repositories/${owner}/${repoName}/commits/${branch}` + : `${protocol}://${this.config.apiBaseUrl}/projects/${owner}/repos/${repoName}/commits/?until=${branch}`; + + const commitsResponse = await fetch( + commitsApiUrl, + getApiRequestOptions(this.config), + ); + if (!commitsResponse.ok) { + const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`; + if (commitsResponse.status === 404) { + throw new NotFoundError(message); + } + throw new Error(message); + } + + const commits = await commitsResponse.json(); + if (isHosted) { + if ( + commits && + commits.values && + commits.values.length > 0 && + commits.values[0].hash + ) { + return commits.values[0].hash.substring(0, 12); + } + } else { + if ( + commits && + commits.values && + commits.values.length > 0 && + commits.values[0].id + ) { + return commits.values[0].id.substring(0, 12); + } + } + throw new Error(`Failed to read response from ${commitsApiUrl}`); + } } diff --git a/packages/backend-common/src/reading/__fixtures__/bitbucket-repo-with-commit-hash.zip b/packages/backend-common/src/reading/__fixtures__/bitbucket-repo-with-commit-hash.zip new file mode 100644 index 0000000000..135860afd3 Binary files /dev/null and b/packages/backend-common/src/reading/__fixtures__/bitbucket-repo-with-commit-hash.zip differ