Merge branch 'backstage:master' into fix-kubernetes-exports
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.16.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 3280711113: Updated dependency `msw` to `^0.49.0`.
|
||||
- dfc8edf9c5: Internal refactor to avoid usage of deprecated symbols.
|
||||
- Updated dependencies
|
||||
- @backstage/config-loader@1.1.7-next.0
|
||||
- @backstage/integration@1.4.1-next.0
|
||||
- @backstage/types@1.0.2-next.0
|
||||
- @backstage/cli-common@0.1.10
|
||||
- @backstage/config@1.0.5-next.0
|
||||
- @backstage/errors@1.1.4-next.0
|
||||
|
||||
## 0.16.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1-next.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -574,4 +574,86 @@ describe('GitlabUrlReader', () => {
|
||||
).rejects.toThrow(NotModifiedError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGitlabFetchUrl', () => {
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'*/api/v4/projects/group%2Fsubgroup%2Fproject',
|
||||
(_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('should fall back to getGitLabFileFetchUrl for blob urls', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabFetchUrl(
|
||||
'https://gitlab.com/group/subgroup/project/-/blob/branch/my/path/to/file.yaml',
|
||||
),
|
||||
).resolves.toEqual(
|
||||
'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
|
||||
);
|
||||
});
|
||||
it('should work for job artifact urls', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabFetchUrl(
|
||||
'https://gitlab.com/group/subgroup/project/-/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
|
||||
),
|
||||
).resolves.toEqual(
|
||||
'https://gitlab.com/api/v4/projects/12345/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
|
||||
);
|
||||
});
|
||||
it('should fail on unfamiliar or non-Gitlab urls', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabFetchUrl(
|
||||
'https://gitlab.com/some/random/endpoint',
|
||||
),
|
||||
).rejects.toThrow('Please provide full path to yaml file from GitLab');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGitlabArtifactFetchUrl', () => {
|
||||
beforeEach(() => {
|
||||
worker.use(
|
||||
rest.get(
|
||||
'*/api/v4/projects/group%2Fsubgroup%2Fproject',
|
||||
(_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })),
|
||||
),
|
||||
);
|
||||
worker.use(
|
||||
rest.get(
|
||||
'*/api/v4/projects/groupA%2Fsubgroup%2Fproject',
|
||||
(_, res, ctx) => res(ctx.status(404)),
|
||||
),
|
||||
);
|
||||
});
|
||||
it('should reject urls that are not for the job artifacts API', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabArtifactFetchUrl(
|
||||
new URL('https://gitlab.com/some/url'),
|
||||
),
|
||||
).rejects.toThrow('Unable to process url as an GitLab artifact');
|
||||
});
|
||||
it('should work for job artifact urls', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabArtifactFetchUrl(
|
||||
new URL(
|
||||
'https://gitlab.com/group/subgroup/project/-/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
|
||||
),
|
||||
),
|
||||
).resolves.toEqual(
|
||||
new URL(
|
||||
'https://gitlab.com/api/v4/projects/12345/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
|
||||
),
|
||||
);
|
||||
});
|
||||
it('errors in mapping the project ID should be captured', async () => {
|
||||
await expect(
|
||||
(gitlabProcessor as any).getGitlabArtifactFetchUrl(
|
||||
new URL(
|
||||
'https://gitlab.com/groupA/subgroup/project/-/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
|
||||
),
|
||||
),
|
||||
).rejects.toThrow(/^Unable to translate GitLab artifact URL:/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +73,7 @@ export class GitlabUrlReader implements UrlReader {
|
||||
options?: ReadUrlOptions,
|
||||
): Promise<ReadUrlResponse> {
|
||||
const { etag, signal } = options ?? {};
|
||||
const builtUrl = await getGitLabFileFetchUrl(url, this.integration.config);
|
||||
const builtUrl = await this.getGitlabFetchUrl(url);
|
||||
|
||||
let response: Response;
|
||||
try {
|
||||
@@ -256,4 +256,66 @@ export class GitlabUrlReader implements UrlReader {
|
||||
const { host, token } = this.integration.config;
|
||||
return `gitlab{host=${host},authed=${Boolean(token)}}`;
|
||||
}
|
||||
|
||||
private async getGitlabFetchUrl(target: string): Promise<string> {
|
||||
// If the target is for a job artifact then go down that path
|
||||
const targetUrl = new URL(target);
|
||||
if (targetUrl.pathname.includes('/-/jobs/artifacts/')) {
|
||||
return this.getGitlabArtifactFetchUrl(targetUrl).then(value =>
|
||||
value.toString(),
|
||||
);
|
||||
}
|
||||
// Default to the old behavior of assuming the url is for a file
|
||||
return getGitLabFileFetchUrl(target, this.integration.config);
|
||||
}
|
||||
|
||||
// convert urls of the form:
|
||||
// https://example.com/<namespace>/<project>/-/jobs/artifacts/<ref>/raw/<path_to_file>?job=<job_name>
|
||||
// to urls of the form:
|
||||
// https://example.com/api/v4/projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=<job_name>
|
||||
private async getGitlabArtifactFetchUrl(target: URL): Promise<URL> {
|
||||
if (!target.pathname.includes('/-/jobs/artifacts/')) {
|
||||
throw new Error('Unable to process url as an GitLab artifact');
|
||||
}
|
||||
try {
|
||||
const [namespaceAndProject, ref] =
|
||||
target.pathname.split('/-/jobs/artifacts/');
|
||||
const projectPath = new URL(target);
|
||||
projectPath.pathname = namespaceAndProject;
|
||||
const projectId = await this.resolveProjectToId(projectPath);
|
||||
const relativePath = getGitLabIntegrationRelativePath(
|
||||
this.integration.config,
|
||||
);
|
||||
const newUrl = new URL(target);
|
||||
newUrl.pathname = `${relativePath}/api/v4/projects/${projectId}/jobs/artifacts/${ref}`;
|
||||
return newUrl;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Unable to translate GitLab artifact URL: ${target}, ${e}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveProjectToId(pathToProject: URL): Promise<number> {
|
||||
let project = pathToProject.pathname;
|
||||
// Check relative path exist and remove it if so
|
||||
const relativePath = getGitLabIntegrationRelativePath(
|
||||
this.integration.config,
|
||||
);
|
||||
if (relativePath) {
|
||||
project = project.replace(relativePath, '');
|
||||
}
|
||||
// Trim an initial / if it exists
|
||||
project = project.replace(/^\//, '');
|
||||
const result = await fetch(
|
||||
`${
|
||||
pathToProject.origin
|
||||
}${relativePath}/api/v4/projects/${encodeURIComponent(project)}`,
|
||||
);
|
||||
const data = await result.json();
|
||||
if (!result.ok) {
|
||||
throw new Error(`Gitlab error: ${data.error}, ${data.error_description}`);
|
||||
}
|
||||
return Number(data.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ export class Git {
|
||||
});
|
||||
} catch (ex) {
|
||||
this.config.logger?.error(
|
||||
`Failed to fetch repo {dir=${dir},origin=${origin}}`,
|
||||
`Failed to fetch repo {dir=${dir},remote=${remote}}`,
|
||||
);
|
||||
if (ex.data) {
|
||||
throw new Error(`${ex.message} {data=${JSON.stringify(ex.data)}}`);
|
||||
|
||||
Reference in New Issue
Block a user