diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index 7f48a155ed..5dfba7776e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -69,7 +69,13 @@ export function createGithubActionsDispatchAction(options: { async handler(ctx) { const { repoUrl, workflowId, branchOrTagName } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } ctx.logger.info( `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index f5c7879fa9..7e394a8d8b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -80,7 +80,10 @@ export function createPublishAzureAction(options: { async handler(ctx) { const { repoUrl, defaultBranch = 'master' } = ctx.input; - const { owner, repo, host, organization } = parseRepoUrl(repoUrl); + const { owner, repo, host, organization } = parseRepoUrl( + repoUrl, + integrations, + ); if (!organization) { throw new InputError( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts index 8efd644bbe..61421857de 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts @@ -50,7 +50,7 @@ describe('publish:bitbucket', () => { const action = createPublishBitbucketAction({ integrations, config }); const mockContext = { input: { - repoUrl: 'bitbucket.org?type=bitbucket&workspace=workspace&project=project&repo=repo', + repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', repoVisibility: 'private', }, workspacePath: 'lol', @@ -70,21 +70,21 @@ describe('publish:bitbucket', () => { await expect( action.handler({ ...mockContext, - input: { repoUrl: 'bitbucket.org?type=bitbucket&project=project&repo=repo' }, + input: { repoUrl: 'bitbucket.org?project=project&repo=repo' }, }), ).rejects.toThrow(/missing workspace/); await expect( action.handler({ ...mockContext, - input: { repoUrl: 'bitbucket.org?type=bitbucket&workspace=workspace&repo=repo' }, + input: { repoUrl: 'bitbucket.org?workspace=workspace&repo=repo' }, }), ).rejects.toThrow(/missing project/); await expect( action.handler({ ...mockContext, - input: { repoUrl: 'bitbucket.org?type=bitbucket&workspace=workspace&project=project' }, + input: { repoUrl: 'bitbucket.org?workspace=workspace&project=project' }, }), ).rejects.toThrow(/missing repo/); }); @@ -93,7 +93,9 @@ describe('publish:bitbucket', () => { await expect( action.handler({ ...mockContext, - input: { repoUrl: 'missing.com?type=bitbucket&workspace=workspace&project=project&repo=repo' }, + input: { + repoUrl: 'missing.com?workspace=workspace&project=project&repo=repo', + }, }), ).rejects.toThrow(/No matching integration configuration/); }); @@ -103,7 +105,8 @@ describe('publish:bitbucket', () => { action.handler({ ...mockContext, input: { - repoUrl: 'notoken.bitbucket.com?type=bitbucket&workspace=workspace&project=project&repo=repo', + repoUrl: + 'notoken.bitbucket.com?workspace=workspace&project=project&repo=repo', }, }), ).rejects.toThrow(/Authorization has not been provided for Bitbucket/); @@ -116,7 +119,11 @@ describe('publish:bitbucket', () => { 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer tokenlols'); - expect(req.body).toEqual({ is_private: true, scm: 'git', project: { key: 'project' } }); + expect(req.body).toEqual({ + is_private: true, + scm: 'git', + project: { key: 'project' }, + }); return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), @@ -176,7 +183,7 @@ describe('publish:bitbucket', () => { ...mockContext, input: { ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?type=bitbucket&project=project&repo=repo', + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', }, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index 1291684719..daa6bc249b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -33,7 +33,14 @@ const createBitbucketCloudRepository = async (opts: { repoVisibility: 'private' | 'public'; authorization: string; }) => { - const { workspace, project, repo, description, repoVisibility, authorization } = opts; + const { + workspace, + project, + repo, + description, + repoVisibility, + authorization, + } = opts; const options: RequestInit = { method: 'POST', @@ -115,7 +122,7 @@ const createBitbucketServerRepository = async (opts: { try { const baseUrl = apiBaseUrl ? apiBaseUrl : `https://${host}/rest/api/1.0`; - response = await fetch(`${baseUrl}/projects/${owner}/repos`, options); + response = await fetch(`${baseUrl}/projects/${project}/repos`, options); } catch (e) { throw new Error(`Unable to create repository, ${e}`); } @@ -162,10 +169,10 @@ const getAuthorizationHeader = (config: BitbucketIntegrationConfig) => { const performEnableLFS = async (opts: { authorization: string; host: string; - owner: string; + project: string; repo: string; }) => { - const { authorization, host, owner, repo } = opts; + const { authorization, host, project, repo } = opts; const options: RequestInit = { method: 'PUT', @@ -175,7 +182,7 @@ const performEnableLFS = async (opts: { }; const { ok, status, statusText } = await fetch( - `https://${host}/rest/git-lfs/admin/projects/${owner}/repos/${repo}/enabled`, + `https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`, options, ); @@ -268,10 +275,10 @@ export function createPublishBitbucketAction(options: { // Workspace is only required for bitbucket cloud if (host === 'bitbucket.org') { if (!workspace) { - throw new InputError( - `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, - ); - } + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, + ); + } } // Project is required for both bitbucket cloud and bitbucket server @@ -293,9 +300,9 @@ export function createPublishBitbucketAction(options: { const apiBaseUrl = integrationConfig.config.apiBaseUrl; const createMethod = - host === 'bitbucket.org' - ? createBitbucketCloudRepository - : createBitbucketServerRepository; + host === 'bitbucket.org' + ? createBitbucketCloudRepository + : createBitbucketServerRepository; const { remoteUrl, repoContentsUrl } = await createMethod({ authorization, @@ -333,7 +340,7 @@ export function createPublishBitbucketAction(options: { }); if (enableLFS && host !== 'bitbucket.org') { - await performEnableLFS({ authorization, host, owner, repo }); + await performEnableLFS({ authorization, host, project, repo }); } ctx.output('remoteUrl', remoteUrl); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 408ec243e7..e21ffc5cd0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -173,7 +173,13 @@ export const createPublishGithubPullRequestAction = ({ sourcePath, } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } const client = await clientFactory({ integrations, host, owner, repo }); const fileRoot = sourcePath diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index 0cc0830fd7..5c10e66e39 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -65,38 +65,19 @@ export const parseRepoUrl = ( } if (type === 'bitbucket') { - - const organization = parsed.searchParams.get('organization') ?? undefined; - - return { host, owner, repo, organization }; -}; - -export const parseRepoUrlForBitbucket = (repoUrl: string) => { - let parsed; - try { - parsed = new URL(`https://${repoUrl}`); - } catch (error) { - throw new InputError( - `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`, - ); - } - const host = parsed.host; - - const workspace = parsed.searchParams.get('workspace'); - - if (!workspace) { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`, - ); + if (host === 'bitbucket.org') { + if (!workspace) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`, + ); + } } if (!project) { throw new InputError( `Invalid repo URL passed to publisher: ${repoUrl}, missing project`, ); } - } - - else { + } else { if (!owner) { throw new InputError( `Invalid repo URL passed to publisher: ${repoUrl}, missing owner`, @@ -111,8 +92,5 @@ export const parseRepoUrlForBitbucket = (repoUrl: string) => { ); } - const organization = parsed.searchParams.get('organization'); - return { host, owner, repo, organization, workspace, project }; }; - diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 6b18d0dac3..2c274df7fc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -22,6 +22,7 @@ import { RepoSpec } from '../actions/builtin/publish/util'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; import { TaskWorker } from './TaskWorker'; +import { ScmIntegrations } from '@backstage/integration'; async function createStore(): Promise { const manager = DatabaseManager.fromConfig( @@ -188,6 +189,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -221,6 +223,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -254,6 +257,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index d34979599d..819df56109 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -27,6 +27,7 @@ import { parseRepoUrl } from '../actions/builtin/publish/util'; import { TemplateActionRegistry } from '../actions/TemplateActionRegistry'; import { isTruthy } from './helper'; import { Task, TaskBroker } from './types'; +import { ScmIntegrations } from '@backstage/integration'; type Options = { logger: Logger; @@ -50,7 +51,7 @@ export class TaskWorker { }); this.handlebars.registerHelper('projectSlug', repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl); + const { owner, repo } = parseRepoUrl(repoUrl, options.integrations); return `${owner}/${repo}`; }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 278f1c3ae8..43ca5bd52e 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -100,20 +100,21 @@ export const RepoUrlPicker = ({ return await scaffolderApi.getIntegrationsList({ allowedHosts }); }); - const { host, type, owner, repo, organization, workspace, project } = splitFormData(formData); + const { host, owner, repo, organization, workspace, project } = splitFormData( + formData, + ); const updateHost = useCallback( (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => { onChange( serializeFormData({ host: evt.target.value as string, - type: (integrations?getIntegrationTypeByHost(evt.target.value as string, integrations): undefined), owner, repo, organization, workspace, project, }), - ) + ); }, [onChange, owner, repo, organization, workspace, project], ); @@ -160,7 +161,7 @@ export const RepoUrlPicker = ({ project, }), ), - [onChange, host , type, owner, repo, workspace, project], + [onChange, host, owner, repo, workspace, project], ); const updateWorkspace = useCallback( @@ -206,7 +207,16 @@ export const RepoUrlPicker = ({ }), ); } - }, [onChange, integrations, host, type, owner, repo, organization, workspace, project]); + }, [ + onChange, + integrations, + host, + owner, + repo, + organization, + workspace, + project, + ]); if (loading) { return ; @@ -263,7 +273,11 @@ export const RepoUrlPicker = ({ error={rawErrors?.length > 0 && !workspace} > Workspace - + The workspace where the repository will be created @@ -275,7 +289,11 @@ export const RepoUrlPicker = ({ error={rawErrors?.length > 0 && !project} > Project - + The project where the repository will be created