Merge pull request #12403 from OpenSourceZombie/ahmed/fix-gitlab-subpath

Consider hosted gitlab with relative path
This commit is contained in:
Ben Lambert
2022-07-13 10:25:35 +02:00
committed by GitHub
7 changed files with 138 additions and 13 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/backend-common': patch
'@backstage/integration': patch
---
Support self-hosted gitlab installations with relative URL.
@@ -16,6 +16,7 @@
import {
getGitLabFileFetchUrl,
getGitLabIntegrationRelativePath,
getGitLabRequestOptions,
GitLabIntegration,
ScmIntegrations,
@@ -37,7 +38,7 @@ import {
ReadUrlResponse,
ReadUrlOptions,
} from './types';
import { trimEnd } from 'lodash';
import { trimEnd, trimStart } from 'lodash';
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
/**
@@ -117,13 +118,24 @@ export class GitlabUrlReader implements UrlReader {
const { etag, signal } = options ?? {};
const { ref, full_name, filepath } = parseGitUrl(url);
let repoFullName = full_name;
const relativePath = getGitLabIntegrationRelativePath(
this.integration.config,
);
// Considering self hosted gitlab with relative
if (relativePath) {
repoFullName = trimStart(full_name, 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,
repoFullName,
)}`,
).toString(),
getGitLabRequestOptions(this.integration.config),
@@ -150,7 +162,7 @@ export class GitlabUrlReader implements UrlReader {
const commitsGitlabResponse = await fetch(
new URL(
`${this.integration.config.apiBaseUrl}/projects/${encodeURIComponent(
full_name,
repoFullName,
)}/repository/commits?${commitsReqParams.toString()}`,
).toString(),
{
@@ -181,7 +193,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,
repoFullName,
)}/repository/archive?sha=${branch}`,
{
...getGitLabRequestOptions(this.integration.config),
+5
View File
@@ -342,6 +342,11 @@ export function getGitLabFileFetchUrl(
config: GitLabIntegrationConfig,
): Promise<string>;
// @public
export function getGitLabIntegrationRelativePath(
config: GitLabIntegrationConfig,
): string;
// @public
export function getGitLabRequestOptions(config: GitLabIntegrationConfig): {
headers: Record<string, string>;
+17 -1
View File
@@ -69,7 +69,6 @@ export function readGitLabIntegrationConfig(
let apiBaseUrl = config.getOptionalString('apiBaseUrl');
const token = config.getOptionalString('token');
let baseUrl = config.getOptionalString('baseUrl');
if (apiBaseUrl) {
apiBaseUrl = trimEnd(apiBaseUrl, '/');
} else if (host === GITLAB_HOST) {
@@ -124,3 +123,20 @@ export function readGitLabIntegrationConfigs(
return result;
}
/**
* Reads a GitLab integration config, and returns
* relative path.
*
* @param config - GitLabIntegrationConfig object
* @public
*/
export function getGitLabIntegrationRelativePath(
config: GitLabIntegrationConfig,
): string {
let relativePath = '';
if (config.host !== GITLAB_HOST) {
relativePath = new URL(config.baseUrl).pathname;
}
return trimEnd(relativePath, '/');
}
+66 -2
View File
@@ -35,18 +35,32 @@ describe('gitlab core', () => {
});
const configWithToken: GitLabIntegrationConfig = {
host: 'g.com',
host: 'gitlab.com',
token: '0123456789',
apiBaseUrl: '<ignored>',
baseUrl: '<ignored>',
};
const configWithNoToken: GitLabIntegrationConfig = {
host: 'g.com',
host: 'gitlab.com',
apiBaseUrl: '<ignored>',
baseUrl: '<ignored>',
};
const configSelfHosteWithRelativePath: GitLabIntegrationConfig = {
host: 'gitlab.mycompany.com',
token: '0123456789',
apiBaseUrl: '<ignored>',
baseUrl: 'https://gitlab.mycompany.com/gitlab',
};
const configSelfHostedWithoutRelativePath: GitLabIntegrationConfig = {
host: 'gitlab.mycompany.com',
token: '0123456789',
apiBaseUrl: '<ignored>',
baseUrl: 'https://gitlab.mycompany.com',
};
describe('getGitLabFileFetchUrl with .yaml extension', () => {
it.each([
// Project URLs
@@ -56,6 +70,18 @@ describe('gitlab core', () => {
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configSelfHosteWithRelativePath,
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
result:
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configSelfHostedWithoutRelativePath,
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
result:
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithNoToken,
// Works with non URI encoded link
@@ -63,6 +89,18 @@ describe('gitlab core', () => {
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
},
{
config: configSelfHosteWithRelativePath,
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
result:
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
},
{
config: configSelfHostedWithoutRelativePath,
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
result:
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yaml/raw?ref=branch',
},
{
config: configWithNoToken,
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
@@ -121,6 +159,18 @@ describe('gitlab core', () => {
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
},
{
config: configSelfHosteWithRelativePath,
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml',
result:
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
},
{
config: configSelfHostedWithoutRelativePath,
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yml',
result:
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yml/raw?ref=branch',
},
{
config: configWithNoToken,
// Works with non URI encoded link
@@ -128,6 +178,20 @@ describe('gitlab core', () => {
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
},
{
config: configSelfHosteWithRelativePath,
// Works with non URI encoded link
url: 'https://gitlab.mycompany.com/gitlab/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yml',
result:
'https://gitlab.mycompany.com/gitlab/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
},
{
config: configSelfHostedWithoutRelativePath,
// Works with non URI encoded link
url: 'https://gitlab.mycompany.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yml',
result:
'https://gitlab.mycompany.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile%20with%20spaces.yml/raw?ref=branch',
},
{
config: configWithNoToken,
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yml',
+27 -6
View File
@@ -14,7 +14,10 @@
* limitations under the License.
*/
import { GitLabIntegrationConfig } from './config';
import {
getGitLabIntegrationRelativePath,
GitLabIntegrationConfig,
} from './config';
import fetch from 'cross-fetch';
import { InputError } from '@backstage/errors';
@@ -42,9 +45,10 @@ export async function getGitLabFileFetchUrl(
// TODO(Rugvip): From the old GitlabReaderProcessor; used
// the existence of /-/blob/ to switch the logic. Don't know if this
// 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 +105,27 @@ 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('/');
const relativePath = getGitLabIntegrationRelativePath(config);
url.pathname = [
'/api/v4/projects',
...(relativePath ? [relativePath] : []),
'api/v4/projects',
projectID,
'repository/files',
encodeURIComponent(decodeURIComponent(filePath.join('/'))),
'raw',
].join('/');
url.search = `?ref=${branch}`;
return url;
@@ -137,19 +148,29 @@ export async function getProjectId(
}
try {
const repo = url.pathname.split('/-/blob/')[0];
let repo = url.pathname.split('/-/blob/')[0];
// Get gitlab relative path
const relativePath = getGitLabIntegrationRelativePath(config);
// Check relative path exist and replace it if it's the case.
if (relativePath) {
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) {
+1
View File
@@ -17,6 +17,7 @@
export {
readGitLabIntegrationConfig,
readGitLabIntegrationConfigs,
getGitLabIntegrationRelativePath,
} from './config';
export type { GitLabIntegrationConfig } from './config';
export { getGitLabFileFetchUrl, getGitLabRequestOptions } from './core';