Added some patches to the plugin-scaffolder-backend-module-gitlab Part2

Signed-off-by: thomastroschke <thomas.troschke89@googlemail.com>
This commit is contained in:
ThomasTroschke
2024-01-18 19:36:27 +01:00
committed by blam
parent 36dbf7e6a1
commit 1cd2740402
6 changed files with 156 additions and 203 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
Establishing compatibility of scaffolder action gitlab:projectAccessToken:create with GitLab version ^16 by introducing the expired at parameter.
+1
View File
@@ -63,6 +63,7 @@
"@backstage/plugin-rollbar-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^",
"@backstage/plugin-scaffolder-backend-module-rails": "workspace:^",
"@backstage/plugin-search-backend": "workspace:^",
"@backstage/plugin-search-backend-module-catalog": "workspace:^",
@@ -13,23 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import { PassThrough } from 'stream';
import yaml from 'yaml';
import { createGitlabProjectAccessTokenAction } from './createGitlabProjectAccessTokenAction'; // Adjust the import based on your project structure
import { ScmIntegrations } from '@backstage/integration';
import { ConfigReader } from '@backstage/config';
import { getVoidLogger } from '@backstage/backend-common';
import { PassThrough } from 'stream';
import { examples } from './createGitlabProjectAccessTokenAction.examples';
import { DateTime } from 'luxon';
jest.mock('node-fetch');
const mockGitlabClient = {
ProjectDeployTokens: {
ProjectAccessTokens: {
create: jest.fn(),
},
};
jest.mock('@gitbeaker/node', () => ({
jest.mock('@gitbeaker/rest', () => ({
Gitlab: class {
constructor() {
return mockGitlabClient;
@@ -73,231 +75,127 @@ describe('gitlab:projectAccessToken:create examples', () => {
});
it('Create a GitLab project access token with minimal options.', async () => {
const fetchMock = jest.spyOn(global, 'fetch');
const mockResponse = {
token: 'mock-access-token',
};
fetchMock.mockResolvedValue({
json: async () => mockResponse,
} as Response);
jest.mock('../util', () => ({
getToken: jest.fn().mockReturnValue({
token: 'mock-api-token',
integrationConfig: { config: { baseUrl: 'https://api.gitlab.com' } },
}),
}));
mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({
token: 'TOKEN',
username: 'User',
});
const input = yaml.parse(examples[0].example).steps[0].input;
await action.handler({
...mockContext,
input,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://gitlab.com/api/v4/projects/456/access_tokens',
expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith(
'456',
'tokenname',
['read_repository'],
{
method: 'POST',
headers: {
'PRIVATE-TOKEN': 'tokenlols',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: undefined,
scopes: undefined,
access_level: undefined,
}),
accessLevel: 40,
expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!,
},
);
expect(mockContext.output).toHaveBeenCalledWith(
'access_token',
'mock-access-token',
);
expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN');
});
it('Create a GitLab project access token with custom scopes.', async () => {
mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({
token: 'TOKEN',
username: 'User',
});
const input = yaml.parse(examples[1].example).steps[0].input;
const mockResponse = {
token: 'mock-access-token',
};
const fetchMock = jest.spyOn(global, 'fetch');
fetchMock.mockResolvedValue({
json: async () => mockResponse,
} as Response);
jest.mock('../util', () => ({
getToken: jest.fn().mockReturnValue({
token: 'mock-api-token',
integrationConfig: { config: { baseUrl: 'https://api.gitlab.com' } },
}),
}));
await action.handler({
...mockContext,
input,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://gitlab.com/api/v4/projects/789/access_tokens',
expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith(
'789',
'tokenname',
['read_registry', 'write_repository'],
{
method: 'POST',
headers: {
'PRIVATE-TOKEN': 'tokenlols',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: undefined,
scopes: ['read_registry', 'write_repository'],
access_level: undefined,
}),
accessLevel: 40,
expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!,
},
);
expect(mockContext.output).toHaveBeenCalledWith(
'access_token',
'mock-access-token',
);
expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN');
});
it('Create a GitLab project access token with a specified name.', async () => {
mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({
token: 'TOKEN',
username: 'User',
});
const input = yaml.parse(examples[2].example).steps[0].input;
const mockResponse = {
token: 'mock-access-token',
};
const fetchMock = jest.spyOn(global, 'fetch');
fetchMock.mockResolvedValue({
json: async () => mockResponse,
} as Response);
jest.mock('../util', () => ({
getToken: jest.fn().mockReturnValue({
token: 'mock-api-token',
integrationConfig: { config: { baseUrl: 'https://api.gitlab.com' } },
}),
}));
await action.handler({
...mockContext,
input,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://gitlab.com/api/v4/projects/101112/access_tokens',
expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith(
'101112',
'my-custom-token',
['read_repository'],
{
method: 'POST',
headers: {
'PRIVATE-TOKEN': 'tokenlols',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'my-custom-token',
scopes: undefined,
access_level: undefined,
}),
accessLevel: 40,
expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!,
},
);
expect(mockContext.output).toHaveBeenCalledWith(
'access_token',
'mock-access-token',
);
expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN');
});
it('Create a GitLab project access token with a numeric project ID.', async () => {
mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({
token: 'TOKEN',
username: 'User',
});
const input = yaml.parse(examples[3].example).steps[0].input;
const mockResponse = {
token: 'mock-access-token',
};
const fetchMock = jest.spyOn(global, 'fetch');
fetchMock.mockResolvedValue({
json: async () => mockResponse,
} as Response);
jest.mock('../util', () => ({
getToken: jest.fn().mockReturnValue({
token: 'mock-api-token',
integrationConfig: { config: { baseUrl: 'https://api.gitlab.com' } },
}),
}));
await action.handler({
...mockContext,
input,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://gitlab.com/api/v4/projects/42/access_tokens',
expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith(
42,
'tokenname',
['read_repository'],
{
method: 'POST',
headers: {
'PRIVATE-TOKEN': 'tokenlols',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: undefined,
scopes: undefined,
access_level: undefined,
}),
accessLevel: 40,
expiresAt: DateTime.now().plus({ days: 365 }).toISODate()!,
},
);
expect(mockContext.output).toHaveBeenCalledWith(
'access_token',
'mock-access-token',
);
expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN');
});
it('Create a GitLab project access token using specific GitLab integrations.', async () => {
it('Create a GitLab project access token with a specified expired Date.', async () => {
mockGitlabClient.ProjectAccessTokens.create.mockResolvedValue({
token: 'TOKEN',
username: 'User',
});
const input = yaml.parse(examples[4].example).steps[0].input;
const mockResponse = {
token: 'mock-access-token',
};
const fetchMock = jest.spyOn(global, 'fetch');
fetchMock.mockResolvedValue({
json: async () => mockResponse,
} as Response);
jest.mock('../util', () => ({
getToken: jest.fn().mockReturnValue({
token: 'mock-api-token',
integrationConfig: { config: { baseUrl: 'https://api.gitlab.com' } },
}),
}));
await action.handler({
...mockContext,
input,
});
expect(fetchMock).toHaveBeenCalledWith(
'https://gitlab.com/api/v4/projects/123/access_tokens',
expect(mockGitlabClient.ProjectAccessTokens.create).toHaveBeenCalledWith(
'123',
'tokenname',
['read_repository'],
{
method: 'POST',
headers: {
'PRIVATE-TOKEN': 'tokenlols',
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: undefined,
scopes: undefined,
access_level: undefined,
}),
accessLevel: 40,
expiresAt: '2024-06-25',
},
);
expect(mockContext.output).toHaveBeenCalledWith(
'access_token',
'mock-access-token',
);
expect(mockContext.output).toHaveBeenCalledWith('access_token', 'TOKEN');
});
});
@@ -86,7 +86,7 @@ export const examples: TemplateExample[] = [
},
{
description:
'Create a GitLab project access token using specific GitLab integrations.',
'Create a GitLab project access token with a specified expired Date.',
example: yaml.stringify({
steps: [
{
@@ -96,6 +96,7 @@ export const examples: TemplateExample[] = [
input: {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
projectId: '123',
expiresAt: '2024-06-25',
},
},
],
@@ -14,11 +14,14 @@
* limitations under the License.
*/
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { InputError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import commonGitlabConfig from '../commonGitlabConfig';
import { getToken } from '../util';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { AccessTokenScopes } from '@gitbeaker/core';
import { Gitlab } from '@gitbeaker/rest';
import { DateTime } from 'luxon';
import { z } from 'zod';
import { getToken } from '../util';
import { examples } from './createGitlabProjectAccessTokenAction.examples';
/**
@@ -27,6 +30,9 @@ import { examples } from './createGitlabProjectAccessTokenAction.examples';
* @param options - Templating configuration.
* @public
*/
const gitbeakerAccessTokenScopesType: z.ZodType<AccessTokenScopes[]> = z.any();
export const createGitlabProjectAccessTokenAction = (options: {
integrations: ScmIntegrationRegistry;
}) => {
@@ -35,46 +41,86 @@ export const createGitlabProjectAccessTokenAction = (options: {
id: 'gitlab:projectAccessToken:create',
examples,
schema: {
input: commonGitlabConfig.merge(
z.object({
projectId: z.union([z.number(), z.string()], {
description: 'Project ID',
}),
name: z.string({ description: 'Deploy Token Name' }).optional(),
accessLevel: z
.number({ description: 'Access Level of the Token' })
.optional(),
scopes: z.array(z.string(), { description: 'Scopes' }).optional(),
input: z.object({
projectId: z.number({
description: 'Project ID/Name(slug) of the Gitlab Project',
}),
),
name: z.string({ description: 'Name of Access Key' }).optional(),
repoUrl: z.string({ description: 'URL to gitlab instance' }),
accessLevel: z
.number({
description:
'Access Level of the Token, 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), and 50 (Owner)',
})
.optional(),
scopes: gitbeakerAccessTokenScopesType,
expiresAt: z
.string({
description:
'Expiration date of the access token in ISO format (YYYY-MM-DD). If Empty, it will set to the maximum of 365 days.',
})
.optional(),
token: z
.string({
description: 'The token to use for authorization to GitLab',
})
.optional(),
}),
output: z.object({
access_token: z.string({ description: 'Access Token' }),
}),
},
async handler(ctx) {
ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`);
const { projectId, name, accessLevel, scopes } = ctx.input;
const {
projectId,
name = 'tokenname',
accessLevel = 40,
scopes = ['read_repository'],
expiresAt,
} = ctx.input;
const { token, integrationConfig } = getToken(ctx.input, integrations);
const response = await fetch(
`${integrationConfig.config.baseUrl}/api/v4/projects/${projectId}/access_tokens`,
if (!integrationConfig.config.token && token) {
throw new InputError(
`No token available for host ${integrationConfig.config.baseUrl}`,
);
}
let api;
if (!ctx.input.token) {
api = new Gitlab({
host: integrationConfig.config.baseUrl,
token: token,
});
} else {
api = new Gitlab({
host: integrationConfig.config.baseUrl,
oauthToken: token,
});
}
const mappedAccessLevel = Number(accessLevel.valueOf()) as AccessLevel;
const response = await api.ProjectAccessTokens.create(
projectId,
name,
scopes,
{
method: 'POST', // *GET, POST, PUT, DELETE, etc.
headers: {
'PRIVATE-TOKEN': token,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: name,
scopes: scopes,
access_level: accessLevel,
}),
expiresAt:
expiresAt || DateTime.now().plus({ days: 365 }).toISODate()!,
accessLevel: mappedAccessLevel,
},
);
if (!response.token) {
throw new Error('Could not create project access token');
}
const result = await response.json();
ctx.output('access_token', result.token);
ctx.output('access_token', response.token);
},
});
};
declare type AccessLevel = 0 | 5 | 10 | 20 | 30 | 40 | 50;
+3 -1
View File
@@ -8385,6 +8385,7 @@ __metadata:
"@gitbeaker/node": ^35.8.0
"@gitbeaker/rest": ^39.25.0
jest-date-mock: ^1.0.8
luxon: ^3.4.4
yaml: ^2.0.0
zod: ^3.22.4
languageName: unknown
@@ -26885,6 +26886,7 @@ __metadata:
"@backstage/plugin-rollbar-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-confluence-to-markdown": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-gitlab": "workspace:^"
"@backstage/plugin-scaffolder-backend-module-rails": "workspace:^"
"@backstage/plugin-search-backend": "workspace:^"
"@backstage/plugin-search-backend-module-catalog": "workspace:^"
@@ -33297,7 +33299,7 @@ __metadata:
languageName: node
linkType: hard
"luxon@npm:^3.0.0, luxon@npm:^3.3.0, luxon@npm:^3.4.3":
"luxon@npm:^3.0.0, luxon@npm:^3.3.0, luxon@npm:^3.4.3, luxon@npm:^3.4.4":
version: 3.4.4
resolution: "luxon@npm:3.4.4"
checksum: 36c1f99c4796ee4bfddf7dc94fa87815add43ebc44c8934c924946260a58512f0fd2743a629302885df7f35ccbd2d13f178c15df046d0e3b6eb71db178f1c60c