Merge branch 'master' into lintMod

This commit is contained in:
Fredrik Adelöw
2021-02-04 07:58:13 +01:00
50 changed files with 880 additions and 495 deletions
@@ -73,4 +73,19 @@ describe('ScmIntegrations', () => {
expect(i.byHost('github.local')).toBe(github);
expect(i.byHost('gitlab.local')).toBe(gitlab);
});
it('can resolveUrl using fallback', () => {
expect(
i.resolveUrl({
url: '../b.yaml',
base: 'https://no-matching-integration.com/x/a.yaml',
}),
).toBe('https://no-matching-integration.com/b.yaml');
expect(
i.resolveUrl({
url: 'https://absolute.com/path',
base: 'https://no-matching-integration.com/x/a.yaml',
}),
).toBe('https://absolute.com/path');
});
});
@@ -81,4 +81,13 @@ export class ScmIntegrations implements ScmIntegrationRegistry {
.map(i => i.byHost(host))
.find(Boolean);
}
resolveUrl(options: { url: string; base: string }): string {
const resolve = this.byUrl(options.base)?.resolveUrl;
if (!resolve) {
return new URL(options.url, options.base).toString();
}
return resolve(options);
}
}
@@ -41,4 +41,52 @@ describe('AzureIntegration', () => {
expect(integration.type).toBe('azure');
expect(integration.title).toBe('h.com');
});
describe('resolveUrl', () => {
it('works for valid urls', () => {
const integration = new AzureIntegration({
host: 'dev.azure.com',
} as any);
expect(
integration.resolveUrl({
url: '../a.yaml',
base:
'https://dev.azure.com/organization/project/_git/repository?path=%2Ffolder%2Fcatalog-info.yaml',
}),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
);
expect(
integration.resolveUrl({
url: './a.yaml',
base: 'https://dev.azure.com/organization/project/_git/repository',
}),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
);
expect(
integration.resolveUrl({
url: 'https://absolute.com/path',
base:
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
}),
).toBe('https://absolute.com/path');
});
it('falls back to regular URL resolution if not in a repo', () => {
const integration = new AzureIntegration({
host: 'dev.azure.com',
} as any);
expect(
integration.resolveUrl({
url: './test',
base: 'https://dev.azure.com/organization/project/_git',
}),
).toBe('https://dev.azure.com/organization/project/test');
});
});
});
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import parseGitUrl from 'git-url-parse';
import { basicIntegrations } from '../helpers';
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
import { AzureIntegrationConfig, readAzureIntegrationConfigs } from './config';
@@ -42,4 +43,39 @@ export class AzureIntegration implements ScmIntegration {
get config(): AzureIntegrationConfig {
return this.integrationConfig;
}
/*
* Azure repo URLs on the form with a `path` query param are treated specially.
*
* Example base URL: https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml
*/
resolveUrl(options: { url: string; base: string }): string {
const { url, base } = options;
// If we can parse the url, it is absolute - then return it verbatim
try {
// eslint-disable-next-line no-new
new URL(url);
return url;
} catch {
// Ignore intentionally - looks like a relative path
}
const parsed = parseGitUrl(base);
const { organization, owner, name, filepath } = parsed;
// If not an actual file path within a repo, treat the URL as raw
if (!organization || !owner || !name) {
return new URL(url, base).toString();
}
const path = filepath?.replace(/^\//, '') || '';
const mockBaseUrl = new URL(`https://a.com/${path}`);
const updatedPath = new URL(url, mockBaseUrl).pathname;
const newUrl = new URL(base);
newUrl.searchParams.set('path', updatedPath);
return newUrl.toString();
}
}
@@ -125,6 +125,7 @@ describe('bitbucket core', () => {
),
),
);
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.mycompany.net',
apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
@@ -250,5 +251,40 @@ describe('bitbucket core', () => {
);
expect(defaultBranch).toEqual('main');
});
it('return default branch for Bitbucket Server for bitbucket version 5.11', async () => {
const defaultBranchResponse = {
displayId: 'main',
};
worker.use(
rest.get(
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch',
(_, res, ctx) =>
res(
ctx.status(404),
ctx.set('Content-Type', 'application/json'),
ctx.json(defaultBranchResponse),
),
),
rest.get(
'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default',
(_, res, ctx) =>
res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
ctx.json(defaultBranchResponse),
),
),
);
const config: BitbucketIntegrationConfig = {
host: 'bitbucket.mycompany.net',
apiBaseUrl: 'https://api.bitbucket.mycompany.net/rest/api/1.0',
};
const defaultBranch = await getBitbucketDefaultBranch(
'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/README.md',
config,
);
expect(defaultBranch).toEqual('main');
});
});
});
+10 -2
View File
@@ -32,11 +32,19 @@ export async function getBitbucketDefaultBranch(
const isHosted = resource === 'bitbucket.org';
// Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184
const branchUrl = isHosted
let branchUrl = isHosted
? `${config.apiBaseUrl}/repositories/${project}/${repoName}`
: `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;
const response = await fetch(branchUrl, getBitbucketRequestOptions(config));
let response = await fetch(branchUrl, getBitbucketRequestOptions(config));
if (response.status === 404 && !isHosted) {
// First try the new format, and then if it gets specifically a 404 it should try the old format
// (to support old Atlassian Bitbucket v5.11.1 format )
branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;
response = await fetch(branchUrl, getBitbucketRequestOptions(config));
}
if (!response.ok) {
const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;
throw new Error(message);
+6 -6
View File
@@ -49,23 +49,23 @@ describe('gitlab core', () => {
{
config: configWithNoToken,
url:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithToken,
url:
'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
result:
'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
'https://gitlab.example.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithNoToken,
url:
'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path/to/file.yaml', // Repo not in subgroup
'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', // Repo not in subgroup
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
// Raw URLs
{
+1 -1
View File
@@ -109,7 +109,7 @@ export function buildProjectUrl(target: string, projectID: Number): URL {
'/api/v4/projects',
projectID,
'repository/files',
encodeURIComponent(filePath.join('/')),
encodeURIComponent(decodeURIComponent(filePath.join('/'))),
'raw',
].join('/');
url.search = `?ref=${branch}`;
+21
View File
@@ -34,6 +34,18 @@ export interface ScmIntegration {
* differentiate between different integrations.
*/
title: string;
/**
* Works like the two-argument form of the URL constructor, resolving an
* absolute or relative URL in relation to a base URL.
*
* If this method is not implemented, the URL constructor is used instead for
* URLs that match this integration.
*
* @param options.url The (absolute or relative) URL or path to resolve
* @param options.base The base URL onto which this resolution happens
*/
resolveUrl?(options: { url: string; base: string }): string;
}
/**
@@ -69,6 +81,15 @@ export interface ScmIntegrationRegistry
bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;
github: ScmIntegrationsGroup<GitHubIntegration>;
gitlab: ScmIntegrationsGroup<GitLabIntegration>;
/**
* Works like the two-argument form of the URL constructor, resolving an
* absolute or relative URL in relation to a base URL.
*
* @param options.url The (absolute or relative) URL or path to resolve
* @param options.base The base URL onto which this resolution happens
*/
resolveUrl(options: { url: string; base: string }): string;
}
export type ScmIntegrationsFactory<T extends ScmIntegration> = (options: {