diff --git a/packages/backend-common/src/reading/GitlabUrlReader.ts b/packages/backend-common/src/reading/GitlabUrlReader.ts index 2387dadf5b..f681adb4d4 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.ts @@ -37,7 +37,7 @@ import { ReadUrlResponse, ReadUrlOptions, } from './types'; -import { trimEnd } from 'lodash'; +import { trimEnd, trimStart } from 'lodash'; import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; /** @@ -117,13 +117,19 @@ export class GitlabUrlReader implements UrlReader { const { etag, signal } = options ?? {}; const { ref, full_name, filepath } = parseGitUrl(url); + // Considering self hosted gitlab with relative + const repo_full_name = trimStart( + full_name, + this.integration.config.relativePath ?? '', + ); + // Use GitLab API to get the default branch // encodeURIComponent is required for GitLab API // https://docs.gitlab.com/ee/api/README.html#namespaced-path-encoding const projectGitlabResponse = await fetch( new URL( `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent( - full_name, + repo_full_name, )}`, ).toString(), getGitLabRequestOptions(this.integration.config), @@ -150,7 +156,7 @@ export class GitlabUrlReader implements UrlReader { const commitsGitlabResponse = await fetch( new URL( `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent( - full_name, + repo_full_name, )}/repository/commits?${commitsReqParams.toString()}`, ).toString(), { @@ -181,7 +187,7 @@ export class GitlabUrlReader implements UrlReader { // https://docs.gitlab.com/ee/api/repositories.html#get-file-archive const archiveGitLabResponse = await fetch( `${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent( - full_name, + repo_full_name, )}/repository/archive?sha=${branch}`, { ...getGitLabRequestOptions(this.integration.config), diff --git a/packages/integration/src/gitlab/GitLabIntegration.test.ts b/packages/integration/src/gitlab/GitLabIntegration.test.ts index 575ce75308..ca84110438 100644 --- a/packages/integration/src/gitlab/GitLabIntegration.test.ts +++ b/packages/integration/src/gitlab/GitLabIntegration.test.ts @@ -28,6 +28,7 @@ describe('GitLabIntegration', () => { token: 't', apiBaseUrl: 'https://h.com/api/v4', baseUrl: 'https://h.com', + relativePath: '', }, ], }, diff --git a/packages/integration/src/gitlab/config.test.ts b/packages/integration/src/gitlab/config.test.ts index db03285206..b642b3c7ae 100644 --- a/packages/integration/src/gitlab/config.test.ts +++ b/packages/integration/src/gitlab/config.test.ts @@ -66,6 +66,7 @@ describe('readGitLabIntegrationConfig', () => { token: 't', apiBaseUrl: 'https://a.com', baseUrl: 'https://baseurl.for.me/gitlab', + relativePath: '/gitlab', }); }); @@ -77,6 +78,7 @@ describe('readGitLabIntegrationConfig', () => { host: 'gitlab.com', apiBaseUrl: 'https://gitlab.com/api/v4', baseUrl: 'https://gitlab.com', + relativePath: '', }); }); @@ -89,6 +91,7 @@ describe('readGitLabIntegrationConfig', () => { host: 'gitlab.com', baseUrl: 'https://gitlab.com', apiBaseUrl: 'https://gitlab.com/api/v4', + relativePath: '', }); }); @@ -119,6 +122,7 @@ describe('readGitLabIntegrationConfig', () => { host: 'a.com', apiBaseUrl: 'https://a.com/api', baseUrl: 'https://a.com', + relativePath: '/', }); }); }); @@ -144,6 +148,7 @@ describe('readGitLabIntegrationConfigs', () => { token: 't', apiBaseUrl: 'https://a.com/api/v4', baseUrl: 'https://a.com', + relativePath: '/', }); }); diff --git a/packages/integration/src/gitlab/config.ts b/packages/integration/src/gitlab/config.ts index 71b4cd868f..89f6b6a822 100644 --- a/packages/integration/src/gitlab/config.ts +++ b/packages/integration/src/gitlab/config.ts @@ -54,6 +54,14 @@ export type GitLabIntegrationConfig = { * If no baseUrl is provided, it will default to `https://${host}` */ baseUrl: string; + + /** + * The relative path for gitlab self hosted installations + * described here https://docs.gitlab.com/ee/install/relative_url.html + * + * If no baseUrl is provided, it will default to `/` + */ + relativePath?: string; }; /** @@ -69,7 +77,7 @@ export function readGitLabIntegrationConfig( let apiBaseUrl = config.getOptionalString('apiBaseUrl'); const token = config.getOptionalString('token'); let baseUrl = config.getOptionalString('baseUrl'); - + let relativePath = ''; if (apiBaseUrl) { apiBaseUrl = trimEnd(apiBaseUrl, '/'); } else if (host === GITLAB_HOST) { @@ -96,7 +104,11 @@ export function readGitLabIntegrationConfig( ); } - return { host, token, apiBaseUrl, baseUrl }; + if (host !== GITLAB_HOST) { + relativePath = new URL(baseUrl).pathname; + } + + return { host, token, apiBaseUrl, baseUrl, relativePath }; } /** diff --git a/packages/integration/src/gitlab/core.test.ts b/packages/integration/src/gitlab/core.test.ts index d01406fe1c..e923c0ee0c 100644 --- a/packages/integration/src/gitlab/core.test.ts +++ b/packages/integration/src/gitlab/core.test.ts @@ -39,12 +39,22 @@ describe('gitlab core', () => { token: '0123456789', apiBaseUrl: '', baseUrl: '', + relativePath: '', }; const configWithNoToken: GitLabIntegrationConfig = { host: 'g.com', apiBaseUrl: '', baseUrl: '', + relativePath: '', + }; + + const configWithSelfHosted: GitLabIntegrationConfig = { + host: 'g.com', + token: '0123456789', + apiBaseUrl: '', + baseUrl: '', + relativePath: '/gitlab', }; describe('getGitLabFileFetchUrl with .yaml extension', () => { @@ -121,6 +131,12 @@ describe('gitlab core', () => { result: 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch', }, + { + config: configWithSelfHosted, + url: 'https://gitlab.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml', + result: + 'https://gitlab.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch', + }, { config: configWithNoToken, // Works with non URI encoded link diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index ba0f9c83de..64b350eb67 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -17,6 +17,9 @@ import { GitLabIntegrationConfig } from './config'; import fetch from 'cross-fetch'; import { InputError } from '@backstage/errors'; +import { relative } from 'path'; +import { trimStart } from 'lodash'; +// import { config } from 'process'; /** * Given a URL pointing to a file on a provider, returns a URL that is suitable @@ -44,7 +47,7 @@ export async function getGitLabFileFetchUrl( // makes sense and it might require some more work. if (url.includes('/-/blob/')) { const projectID = await getProjectId(url, config); - return buildProjectUrl(url, projectID).toString(); + return buildProjectUrl(url, projectID, config).toString(); } return buildRawUrl(url).toString(); } @@ -101,20 +104,26 @@ export function buildRawUrl(target: string): URL { // Converts // from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath // to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch -export function buildProjectUrl(target: string, projectID: Number): URL { +export function buildProjectUrl( + target: string, + projectID: Number, + config: GitLabIntegrationConfig, +): URL { try { const url = new URL(target); const branchAndFilePath = url.pathname.split('/-/blob/')[1]; const [branch, ...filePath] = branchAndFilePath.split('/'); - url.pathname = [ - '/api/v4/projects', - projectID, - 'repository/files', - encodeURIComponent(decodeURIComponent(filePath.join('/'))), - 'raw', - ].join('/'); + url.pathname = + [config.relativePath] + + [ + '/api/v4/projects', + projectID, + 'repository/files', + encodeURIComponent(decodeURIComponent(filePath.join('/'))), + 'raw', + ].join('/'); url.search = `?ref=${branch}`; return url; @@ -137,19 +146,27 @@ export async function getProjectId( } try { - const repo = url.pathname.split('/-/blob/')[0]; + let repo = url.pathname.split('/-/blob/')[0]; + + // Ignore relative path if it's not set + const relativePath = config.relativePath ?? ''; + + // Should replace first match only + repo = repo.replace(relativePath, ''); // Convert // to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo const repoIDLookup = new URL( - `${url.origin}/api/v4/projects/${encodeURIComponent( + `${url.origin}${relativePath}/api/v4/projects/${encodeURIComponent( repo.replace(/^\//, ''), )}`, ); + const response = await fetch( repoIDLookup.toString(), getGitLabRequestOptions(config), ); + const data = await response.json(); if (!response.ok) {