Merge branch 'master' of github.com:backstage/backstage into github-installations-limit

This commit is contained in:
Brian Fletcher
2021-08-09 14:53:17 +01:00
388 changed files with 5078 additions and 4982 deletions
@@ -51,8 +51,7 @@ describe('AzureIntegration', () => {
expect(
integration.resolveUrl({
url: '../a.yaml',
base:
'https://dev.azure.com/organization/project/_git/repository?path=%2Ffolder%2Fcatalog-info.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',
@@ -61,8 +60,7 @@ describe('AzureIntegration', () => {
expect(
integration.resolveUrl({
url: '/a.yaml',
base:
'https://dev.azure.com/organization/project/_git/repository?path=%2Ffolder%2Fcatalog-info.yaml',
base: 'https://dev.azure.com/organization/project/_git/repository?path=%2Ffolder%2Fcatalog-info.yaml',
lineNumber: 14,
}),
).toBe(
@@ -81,8 +79,7 @@ describe('AzureIntegration', () => {
expect(
integration.resolveUrl({
url: 'https://absolute.com/path',
base:
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
base: 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
}),
).toBe('https://absolute.com/path');
});
+4 -8
View File
@@ -43,26 +43,22 @@ describe('azure core', () => {
describe('getAzureFileFetchUrl', () => {
it.each([
{
url:
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
result:
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master',
},
{
url:
'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
result:
'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml',
},
{
url:
'https://api.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
url: 'https://api.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
result:
'https://api.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml',
},
{
url:
'https://api.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
url: 'https://api.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
result:
'https://api.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master',
},
+4 -14
View File
@@ -31,13 +31,8 @@ export function getAzureFileFetchUrl(url: string): string {
try {
const parsedUrl = new URL(url);
const [
empty,
userOrOrg,
project,
srcKeyword,
repoName,
] = parsedUrl.pathname.split('/');
const [empty, userOrOrg, project, srcKeyword, repoName] =
parsedUrl.pathname.split('/');
const path = parsedUrl.searchParams.get('path') || '';
const ref = parsedUrl.searchParams.get('version')?.substr(2);
@@ -117,13 +112,8 @@ export function getAzureCommitsUrl(url: string): string {
try {
const parsedUrl = new URL(url);
const [
empty,
userOrOrg,
project,
srcKeyword,
repoName,
] = parsedUrl.pathname.split('/');
const [empty, userOrOrg, project, srcKeyword, repoName] =
parsedUrl.pathname.split('/');
// Remove the "GB" from "GBmain" for example.
const ref = parsedUrl.searchParams.get('version')?.substr(2);
@@ -56,8 +56,7 @@ describe('GitHubIntegration', () => {
expect(
integration.resolveUrl({
url: '../a.yaml',
base:
'https://github.com/backstage/backstage/blob/master/test/README.md',
base: 'https://github.com/backstage/backstage/blob/master/test/README.md',
lineNumber: 17,
}),
).toBe('https://github.com/backstage/backstage/tree/master/a.yaml#L17');
@@ -65,8 +64,7 @@ describe('GitHubIntegration', () => {
expect(
integration.resolveUrl({
url: './',
base:
'https://github.com/backstage/backstage/blob/master/test/README.md',
base: 'https://github.com/backstage/backstage/blob/master/test/README.md',
}),
).toBe('https://github.com/backstage/backstage/tree/master/test/');
});
@@ -133,7 +133,7 @@ describe('GithubCredentialsProvider tests', () => {
expect(token).toEqual('secret_token');
});
it('should fail to issue tokens for an organization when the app is installed for a single repo', async () => {
it('should not fail to issue tokens for an organization when the app is installed for a single repo', async () => {
octokit.apps.listInstallations.mockResolvedValue({
headers: {
etag: '123',
@@ -156,13 +156,12 @@ describe('GithubCredentialsProvider tests', () => {
},
} as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']);
await expect(
github.getCredentials({
url: 'https://github.com/backstage',
}),
).rejects.toThrow(
'The Backstage GitHub application used in the backstage organization must be installed for the entire organization to be able to issue credentials without a specified repository.',
);
const { token, headers } = await github.getCredentials({
url: 'https://github.com/backstage',
});
const expectedToken = 'secret_token';
expect(headers).toEqual({ Authorization: `Bearer ${expectedToken}` });
expect(token).toEqual('secret_token');
});
it('should throw if the app is suspended', async () => {
@@ -232,9 +231,9 @@ describe('GithubCredentialsProvider tests', () => {
],
token: 'hardcoded_token',
});
octokit.apps.listInstallations.mockResolvedValue(({
octokit.apps.listInstallations.mockResolvedValue({
data: [],
} as unknown) as RestEndpointMethodTypes['apps']['listInstallations']['response']);
} as unknown as RestEndpointMethodTypes['apps']['listInstallations']['response']);
await expect(
githubProvider.getCredentials({
@@ -23,7 +23,6 @@ import { DateTime } from 'luxon';
type InstallationData = {
installationId: number;
suspended: boolean;
repositorySelection: 'selected' | 'all';
};
class Cache {
@@ -87,45 +86,41 @@ class GithubAppManager {
owner: string,
repo?: string,
): Promise<{ accessToken: string }> {
const {
installationId,
suspended,
repositorySelection,
} = await this.getInstallationData(owner);
const { installationId, suspended } = await this.getInstallationData(owner);
if (this.allowedInstallationOwners) {
if (!this.allowedInstallationOwners?.includes(owner)) {
throw new Error(
`The GitHub application for ${[owner, repo]
.filter(Boolean)
.join(
'/',
)} is not included in the allowed installation list (${installationId}).`,
`The GitHub application for ${owner} is not included in the allowed installation list (${installationId}).`,
);
}
}
if (suspended) {
throw new Error(
`The GitHub application for ${[owner, repo]
.filter(Boolean)
.join('/')} is suspended`,
);
}
if (repositorySelection !== 'all' && !repo) {
throw new Error(
`The Backstage GitHub application used in the ${owner} organization must be installed for the entire organization to be able to issue credentials without a specified repository.`,
);
throw new Error(`The GitHub application for ${owner} is suspended`);
}
const cacheKey = !repo ? owner : `${owner}/${repo}`;
const repositories = repositorySelection !== 'all' ? [repo!] : undefined;
const cacheKey = repo ? `${owner}/${repo}` : owner;
// Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.
return this.cache.getOrCreateToken(cacheKey, async () => {
const result = await this.appClient.apps.createInstallationAccessToken({
installation_id: installationId,
headers: HEADERS,
repositories,
});
if (repo && result.data.repository_selection === 'selected') {
const installationClient = new Octokit({
auth: result.data.token,
});
const repos =
await installationClient.apps.listReposAccessibleToInstallation();
const hasRepo = repos.data.repositories.some(repository => {
return repository.name === repo;
});
if (!hasRepo) {
throw new Error(
`The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,
);
}
}
return {
token: result.data.token,
expiresAt: DateTime.fromISO(result.data.expires_at),
@@ -148,7 +143,6 @@ class GithubAppManager {
return {
installationId: installation.id,
suspended: Boolean(installation.suspended_by),
repositorySelection: installation.repository_selection,
};
}
const notFoundError = new Error(
+5 -10
View File
@@ -52,37 +52,32 @@ describe('gitlab core', () => {
// Project URLs
{
config: configWithNoToken,
url:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file.yaml',
result:
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithNoToken,
// Works with non URI encoded link
url:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
url: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path/to/file with spaces.yaml',
result:
'https://gitlab.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',
url: '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%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithToken,
url:
'https://gitlab.example.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml',
url: '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%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
{
config: configWithNoToken,
url:
'https://gitlab.com/groupA/teams/teamA/repoA/-/blob/branch/my/path%20with%20spaces/to/file.yaml', // Repo not in subgroup
url: '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%20with%20spaces%2Fto%2Ffile.yaml/raw?ref=branch',
},
+2 -7
View File
@@ -68,13 +68,8 @@ export function buildRawUrl(target: string): URL {
try {
const url = new URL(target);
const [
empty,
userOrOrg,
repoName,
blobKeyword,
...restOfPath
] = url.pathname.split('/');
const [empty, userOrOrg, repoName, blobKeyword, ...restOfPath] =
url.pathname.split('/');
if (
empty !== '' ||
+10 -20
View File
@@ -57,8 +57,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: './b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
}),
).toBe(
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml',
@@ -67,8 +66,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: './b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
}),
).toBe(
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml?at=master',
@@ -77,8 +75,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: 'b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
}),
).toBe(
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/b.yaml',
@@ -89,8 +86,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: '/other/b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
}),
).toBe(
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/other/b.yaml',
@@ -99,8 +95,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: '/other/b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
}),
).toBe(
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/other/b.yaml?at=master',
@@ -111,8 +106,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: './b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
lineNumber: 11,
}),
).toBe(
@@ -122,8 +116,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: 'b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
lineNumber: 12,
}),
).toBe(
@@ -133,8 +126,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: '/other/b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml',
lineNumber: 13,
}),
).toBe(
@@ -144,8 +136,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: '/other/b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
lineNumber: 14,
}),
).toBe(
@@ -157,8 +148,7 @@ describe('defaultScmResolveUrl', () => {
expect(
defaultScmResolveUrl({
url: 'https://b.com/b.yaml',
base:
'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
base: 'https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/folder/a.yaml?at=master',
}),
).toBe('https://b.com/b.yaml');
});