diff --git a/.changeset/sour-phones-fix.md b/.changeset/sour-phones-fix.md new file mode 100644 index 0000000000..d2519f2ef5 --- /dev/null +++ b/.changeset/sour-phones-fix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +--- + +Updated `gitlab:group:ensureExists` action to instead use oauth client. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts index b9377fd97b..202b397d19 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.examples.test.ts @@ -27,7 +27,8 @@ const mockGitlabClient = { create: jest.fn(), }, }; -jest.mock('@gitbeaker/node', () => ({ + +jest.mock('@gitbeaker/rest', () => ({ Gitlab: class { constructor() { return mockGitlabClient; @@ -38,8 +39,8 @@ jest.mock('@gitbeaker/node', () => ({ describe('gitlab:group:ensureExists', () => { const mockContext = createMockActionContext(); - afterEach(() => { - jest.resetAllMocks(); + beforeEach(() => { + jest.clearAllMocks(); }); it(`Should ${examples[0].description}`, async () => { @@ -55,7 +56,7 @@ describe('gitlab:group:ensureExists', () => { { host: 'gitlab.com', token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', + apiBaseUrl: 'https://gitlab.com/api/v4', }, ], }, @@ -114,7 +115,7 @@ describe('gitlab:group:ensureExists', () => { 'group2', 'group2', { - parent_id: 1, + parentId: 1, }, ); @@ -161,7 +162,7 @@ describe('gitlab:group:ensureExists', () => { 'group3', 'group3', { - parent_id: 2, + parentId: 2, }, ); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts index a92637ba46..1192052e6d 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.test.ts @@ -18,6 +18,7 @@ import { ConfigReader } from '@backstage/core-app-api'; import { ScmIntegrations } from '@backstage/integration'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { createGitlabGroupEnsureExistsAction } from './gitlabGroupEnsureExists'; +import { getClient } from '../util'; const mockGitlabClient = { Groups: { @@ -25,7 +26,8 @@ const mockGitlabClient = { create: jest.fn(), }, }; -jest.mock('@gitbeaker/node', () => ({ + +jest.mock('@gitbeaker/rest', () => ({ Gitlab: class { constructor() { return mockGitlabClient; @@ -33,13 +35,33 @@ jest.mock('@gitbeaker/node', () => ({ }, })); -describe('gitlab:group:ensureExists', () => { - const mockContext = createMockActionContext(); +jest.mock('../util', () => ({ + getClient: jest.fn().mockImplementation(() => mockGitlabClient), + parseRepoUrl: () => ({ host: 'gitlab.com', owner: 'owner', repo: 'repo' }), +})); - afterEach(() => { - jest.resetAllMocks(); +describe('gitlab:group:ensureExists', () => { + beforeEach(() => { + jest.clearAllMocks(); }); + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://gitlab.com/api/v4', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + + const action = createGitlabGroupEnsureExistsAction({ integrations }); + + const mockContext = createMockActionContext(); + it('should create a new group if it does not exists', async () => { mockGitlabClient.Groups.search.mockResolvedValue([ { @@ -57,31 +79,16 @@ describe('gitlab:group:ensureExists', () => { full_path: 'foo/bar', }); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], - }, - }); - const integrations = ScmIntegrations.fromConfig(config); - - const action = createGitlabGroupEnsureExistsAction({ integrations }); - await action.handler({ ...mockContext, input: { - repoUrl: 'gitlab.com', + repoUrl: 'gitlab.com?repo=repo&owner=owner', path: ['foo', 'bar'], }, }); expect(mockGitlabClient.Groups.create).toHaveBeenCalledWith('bar', 'bar', { - parent_id: 2, + parentId: 2, }); expect(mockContext.output).toHaveBeenCalledWith('groupId', 3); @@ -103,25 +110,10 @@ describe('gitlab:group:ensureExists', () => { }, ]); - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], - }, - }); - const integrations = ScmIntegrations.fromConfig(config); - - const action = createGitlabGroupEnsureExistsAction({ integrations }); - await action.handler({ ...mockContext, input: { - repoUrl: 'gitlab.com', + repoUrl: 'gitlab.com?repo=repo&owner=owner', path: ['foo', 'bar'], }, }); @@ -132,26 +124,11 @@ describe('gitlab:group:ensureExists', () => { }); it('should not call API on dryRun', async () => { - const config = new ConfigReader({ - integrations: { - gitlab: [ - { - host: 'gitlab.com', - token: 'tokenlols', - apiBaseUrl: 'https://api.gitlab.com', - }, - ], - }, - }); - const integrations = ScmIntegrations.fromConfig(config); - - const action = createGitlabGroupEnsureExistsAction({ integrations }); - await action.handler({ ...mockContext, isDryRun: true, input: { - repoUrl: 'gitlab.com', + repoUrl: 'gitlab.com?repo=repo&owner=owner', path: ['foo', 'bar'], }, }); @@ -161,4 +138,51 @@ describe('gitlab:group:ensureExists', () => { expect(mockContext.output).toHaveBeenCalledWith('groupId', 42); }); + + it('should use the token from the integration config when none is provided', async () => { + mockGitlabClient.Groups.search.mockResolvedValue([ + { + id: 1, + full_path: 'foobar', + }, + ]); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: ['foobar'], + }, + }); + + expect(getClient).toHaveBeenCalledWith( + expect.not.objectContaining({ + token: expect.anything(), + }), + ); + }); + + it('should use a provided token as bearer authentication', async () => { + mockGitlabClient.Groups.search.mockResolvedValue([ + { + id: 1, + full_path: 'foobar', + }, + ]); + + await action.handler({ + ...mockContext, + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + path: ['foobar'], + token: 'mysecrettoken', + }, + }); + + expect(getClient).toHaveBeenCalledWith( + expect.objectContaining({ + token: 'mysecrettoken', + }), + ); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts index 29f260fb75..41bc5755c9 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabGroupEnsureExists.ts @@ -17,10 +17,9 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { GroupSchema } from '@gitbeaker/core/dist/types/resources/Groups'; -import { Gitlab } from '@gitbeaker/node'; import { z } from 'zod'; import commonGitlabConfig from '../commonGitlabConfig'; -import { getToken } from '../util'; +import { getClient, parseRepoUrl } from '../util'; import { examples } from './gitlabGroupEnsureExists.examples'; /** @@ -60,16 +59,14 @@ export const createGitlabGroupEnsureExistsAction = (options: { return; } - const { path } = ctx.input; - const { token, integrationConfig } = getToken(ctx.input, integrations); + const { token, repoUrl, path } = ctx.input; - const api = new Gitlab({ - host: integrationConfig.config.baseUrl, - token: token, - }); + const { host } = parseRepoUrl(repoUrl, integrations); + + const api = getClient({ host, integrations, token }); let currentPath: string | null = null; - let parent: GroupSchema | null = null; + let parentId: number | null = null; for (const pathElement of path) { const fullPath: string = currentPath ? `${currentPath}/${pathElement}` @@ -82,22 +79,24 @@ export const createGitlabGroupEnsureExistsAction = (options: { ); if (!subGroup) { ctx.logger.info(`creating missing group ${fullPath}`); - parent = await api.Groups.create( - pathElement, - pathElement, - parent - ? { - parent_id: parent.id, - } - : {}, - ); + parentId = ( + await api.Groups.create( + pathElement, + pathElement, + parentId + ? { + parentId: parentId, + } + : {}, + ) + )?.id; } else { - parent = subGroup; + parentId = subGroup.id; } currentPath = fullPath; } - if (parent !== null) { - ctx.output('groupId', parent?.id); + if (parentId !== null) { + ctx.output('groupId', parentId); } }, });