backstage
190 строк · 5.3 Кб
1/*
2* Copyright 2021 The Backstage Authors
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17import { InputError } from '@backstage/errors';
18import {
19GitLabIntegration,
20ScmIntegrationRegistry,
21} from '@backstage/integration';
22import { Gitlab, GroupSchema } from '@gitbeaker/rest';
23import { z } from 'zod';
24import commonGitlabConfig from './commonGitlabConfig';
25import * as util from './util';
26
27export const parseRepoHost = (repoUrl: string): string => {
28let parsed;
29try {
30parsed = new URL(`https://${repoUrl}`);
31} catch (error) {
32throw new InputError(
33`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`,
34);
35}
36return parsed.host;
37};
38
39export const getToken = (
40config: z.infer<typeof commonGitlabConfig>,
41integrations: ScmIntegrationRegistry,
42): { token: string; integrationConfig: GitLabIntegration } => {
43const host = parseRepoHost(config.repoUrl);
44const integrationConfig = integrations.gitlab.byHost(host);
45
46if (!integrationConfig) {
47throw new InputError(
48`No matching integration configuration for host ${host}, please check your integrations config`,
49);
50}
51
52const token = config.token || integrationConfig.config.token!;
53
54return { token: token, integrationConfig: integrationConfig };
55};
56
57export type RepoSpec = {
58repo: string;
59host: string;
60owner?: string;
61};
62
63export const parseRepoUrl = (
64repoUrl: string,
65integrations: ScmIntegrationRegistry,
66): RepoSpec => {
67let parsed;
68try {
69parsed = new URL(`https://${repoUrl}`);
70} catch (error) {
71throw new InputError(
72`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`,
73);
74}
75const host = parsed.host;
76const owner = parsed.searchParams.get('owner') ?? undefined;
77const repo: string = parsed.searchParams.get('repo')!;
78
79const type = integrations.byHost(host)?.type;
80
81if (!type) {
82throw new InputError(
83`No matching integration configuration for host ${host}, please check your integrations config`,
84);
85}
86
87return { host, owner, repo };
88};
89
90export function getClient(props: {
91host: string;
92token?: string;
93integrations: ScmIntegrationRegistry;
94}): InstanceType<typeof Gitlab> {
95const { host, token, integrations } = props;
96const integrationConfig = integrations.gitlab.byHost(host);
97
98if (!integrationConfig) {
99throw new InputError(
100`No matching integration configuration for host ${host}, please check your integrations config`,
101);
102}
103
104const { config } = integrationConfig;
105
106if (!config.token && !token) {
107throw new InputError(`No token available for host ${host}`);
108}
109
110const requestToken = token || config.token!;
111const tokenType = token ? 'oauthToken' : 'token';
112
113const gitlabOptions: any = {
114host: config.baseUrl,
115};
116
117gitlabOptions[tokenType] = requestToken;
118return new Gitlab(gitlabOptions);
119}
120
121export function convertDate(
122inputDate: string | undefined,
123defaultDate: string,
124) {
125try {
126return inputDate
127? new Date(inputDate).toISOString()
128: new Date(defaultDate).toISOString();
129} catch (error) {
130throw new InputError(`Error converting input date - ${error}`);
131}
132}
133
134export async function getTopLevelParentGroup(
135client: InstanceType<typeof Gitlab>,
136groupId: number,
137): Promise<GroupSchema> {
138try {
139const topParentGroup = await client.Groups.show(groupId);
140if (topParentGroup.parent_id) {
141return util.getTopLevelParentGroup(
142client,
143topParentGroup.parent_id as number,
144);
145}
146return topParentGroup as GroupSchema;
147} catch (error: any) {
148throw new InputError(
149`Error finding top-level parent group ID: ${error.message}`,
150);
151}
152}
153
154export async function checkEpicScope(
155client: InstanceType<typeof Gitlab>,
156projectId: number,
157epicId: number,
158) {
159try {
160// If project exists, get the top level group id
161const project = await client.Projects.show(projectId);
162if (!project) {
163throw new InputError(
164`Project with id ${projectId} not found. Check your GitLab instance.`,
165);
166}
167const topParentGroup = await getTopLevelParentGroup(
168client,
169project.namespace.id,
170);
171if (!topParentGroup) {
172throw new InputError(`Couldn't find a suitable top-level parent group.`);
173}
174// Get the epic
175const epic = (await client.Epics.all(topParentGroup.id)).find(
176(x: any) => x.id === epicId,
177);
178if (!epic) {
179throw new InputError(
180`Epic with id ${epicId} not found in the top-level parent group ${topParentGroup.name}.`,
181);
182}
183
184const epicGroup = await client.Groups.show(epic.group_id as number);
185const projectNamespace: string = project.path_with_namespace as string;
186return projectNamespace.startsWith(epicGroup.full_path as string);
187} catch (error: any) {
188throw new InputError(`Could not find epic scope: ${error.message}`);
189}
190}
191