fix(scaffolder): Create repository in personal namespace

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>
This commit is contained in:
Gabriel Dugny
2025-03-10 17:25:48 +01:00
parent 8aa63b60be
commit 0df33eaaf3
3 changed files with 99 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
fix: Creating a repository in a user namespace would always lead to an error
@@ -44,6 +44,7 @@ const mockGitlabClient = {
},
Users: {
showCurrentUser: jest.fn(),
allProjects: jest.fn(),
},
ProjectMembers: {
add: jest.fn(),
@@ -211,7 +212,7 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
it('should call the correct Gitlab APIs when the owner is an organization', async () => {
it('should call the correct Gitlab APIs when the owner is a group', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
@@ -232,16 +233,18 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
it('should call the correct Gitlab APIs when the owner is not an organization', async () => {
it('should call the correct Gitlab APIs when the owner is a user', async () => {
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: null });
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Groups.allProjects.mockRejectedValue({});
mockGitlabClient.Users.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
});
await action.handler(mockContext);
expect(mockGitlabClient.Groups.allProjects).not.toHaveBeenCalled();
expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
namespaceId: 12345,
@@ -253,9 +256,12 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
it('should not call the creation Gitlab APIs when the repository already exists', async () => {
it('should not call the creation Gitlab APIs when the repository already exists in a group', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([
{
path: 'repo',
@@ -278,9 +284,41 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
it('should not call the creation Gitlab APIs when the repository already exists in a user namespace', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 12345,
kind: 'user',
});
mockGitlabClient.Users.allProjects.mockResolvedValue([
{
path: 'repo',
http_url_to_repo: 'http://mockurl.git',
},
{
path: 'repo-name',
http_url_to_repo: 'http://mockurl.git',
},
]);
await action.handler({
...mockContext,
input: { ...mockContext.input, skipExisting: true },
});
expect(mockGitlabClient.Groups.allProjects).not.toHaveBeenCalled();
expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
expect(mockGitlabClient.Projects.create).not.toHaveBeenCalled();
expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
it('should call the creation Gitlab APIs when the repository does not yet exists', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([
{
path: 'repo-name',
@@ -305,7 +343,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs when using project settings with override of visibility and topics', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -327,7 +368,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs for branches and protectd branches when branch settings provided', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
id: 123456,
@@ -368,7 +412,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs for variables when their configuration is provided', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
id: 123456,
@@ -401,7 +448,10 @@ describe('publish:gitlab', () => {
it('should call initRepoAndPush with the correct values', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -422,7 +472,10 @@ describe('publish:gitlab', () => {
it('should not call initRepoAndPush when sourcePath is false', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -446,7 +499,10 @@ describe('publish:gitlab', () => {
it('should call initRepoAndPush with the correct default branch', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -502,7 +558,10 @@ describe('publish:gitlab', () => {
});
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -549,7 +608,10 @@ describe('publish:gitlab', () => {
});
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -570,7 +632,10 @@ describe('publish:gitlab', () => {
it('should call output with the remoteUrl and repoContentsUrl and projectId', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -610,7 +675,10 @@ describe('publish:gitlab', () => {
config: customAuthorConfig,
});
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Namespaces.show.mockResolvedValue({
id: 1234,
kind: 'group',
});
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
@@ -379,13 +379,15 @@ export function createPublishGitlabAction(options: {
});
let targetNamespaceId;
let targetNamespaceKind;
try {
const namespaceResponse = (await client.Namespaces.show(owner)) as {
id: number;
kind: string;
};
targetNamespaceId = namespaceResponse.id;
targetNamespaceKind = namespaceResponse.kind;
} catch (e) {
if (e.cause?.response?.status === 404) {
throw new InputError(
@@ -401,13 +403,16 @@ export function createPublishGitlabAction(options: {
if (!targetNamespaceId) {
targetNamespaceId = userId;
targetNamespaceKind = 'user';
}
const existingProjects = await client.Groups.allProjects(owner, {
search: repo,
});
const existingProjects =
targetNamespaceKind === 'user'
? await client.Users.allProjects(owner, { search: repo })
: await client.Groups.allProjects(owner, { search: repo });
const existingProject = existingProjects.find(
searchPathElem => searchPathElem.path === repo,
project => project.path === repo,
);
if (!skipExisting || (skipExisting && !existingProject)) {