From b853e084dc8192ec067203b083d6fefb739d5e21 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 25 Feb 2021 10:13:06 +0100 Subject: [PATCH] scaffolder: Fix providers Co-authored-by: Ben Lambert Signed-off-by: Johan Haals --- .../test-template-v2/template.yaml | 215 ++++++++++++++++++ .../actions/builtin/publish/azure.ts | 23 +- .../actions/builtin/publish/bitbucket.ts | 1 + .../actions/builtin/publish/gitlab.ts | 2 +- .../actions/builtin/publish/util.ts | 20 +- .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 95 ++++++-- 6 files changed, 334 insertions(+), 22 deletions(-) diff --git a/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml b/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml index 27b744664a..3856906454 100644 --- a/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/test-template-v2/template.yaml @@ -138,6 +138,221 @@ spec: catalogInfoPath: '/catalog-info.yaml' + output: + remoteUrl: '{{ steps.publish.output.remoteUrl }}' + entityRef: '{{ steps.register.output.entityRef }}' + +--- + +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: test-template-v2-gitlab + title: Test Template v2-gitlab + description: Testing out the new template schema +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - gitlab.com + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:cookiecutter + parameters: + url: ./template + values: + name: '{{ parameters.name }}' + + - id: fetch-docs + name: Fetch Docs + action: fetch:plain + parameters: + targetPath: ./community + url: https://github.com/backstage/community/tree/main/backstage-community-sessions + + - id: publish + name: Publish + action: publish:gitlab + parameters: + allowedHosts: ['gitlab.com'] + description: 'This is {{ parameters.name }}' + repoUrl: '{{ parameters.repoUrl }}' + + - id: register + name: Register + action: catalog:register + parameters: + repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}' + catalogInfoPath: '/catalog-info.yaml' + + + output: + remoteUrl: '{{ steps.publish.output.remoteUrl }}' + entityRef: '{{ steps.register.output.entityRef }}' + +--- + +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: test-template-v2-bitbucket + title: Test Template v2-bitbucket + description: Testing out the new template schema +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - bitbucket.org + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:cookiecutter + parameters: + url: ./template + values: + name: '{{ parameters.name }}' + + - id: fetch-docs + name: Fetch Docs + action: fetch:plain + parameters: + targetPath: ./community + url: https://github.com/backstage/community/tree/main/backstage-community-sessions + + - id: publish + name: Publish + action: publish:bitbucket + parameters: + allowedHosts: ['bitbucket.org'] + description: 'This is {{ parameters.name }}' + repoUrl: '{{ parameters.repoUrl }}' + + - id: register + name: Register + action: catalog:register + parameters: + repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}' + catalogInfoPath: '/catalog-info.yaml' + + + output: + remoteUrl: '{{ steps.publish.output.remoteUrl }}' + entityRef: '{{ steps.register.output.entityRef }}' + +--- +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: test-template-v2-azure + title: Test Template v2-azure + description: Testing out the new template schema +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - dev.azure.com + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:cookiecutter + parameters: + url: ./template + values: + name: '{{ parameters.name }}' + + - id: fetch-docs + name: Fetch Docs + action: fetch:plain + parameters: + targetPath: ./community + url: https://github.com/backstage/community/tree/main/backstage-community-sessions + + - id: publish + name: Publish + action: publish:azure + parameters: + allowedHosts: ['dev.azure.com'] + description: 'This is {{ parameters.name }}' + repoUrl: '{{ parameters.repoUrl }}' + + - id: register + name: Register + action: catalog:register + parameters: + repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}' + catalogInfoPath: '/catalog-info.yaml' + + output: remoteUrl: '{{ steps.publish.output.remoteUrl }}' entityRef: '{{ steps.register.output.entityRef }}' 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 0728e753b2..be2f41d06c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -47,7 +47,16 @@ export function createPublishAzureAction(options: { }, }, async handler(ctx) { - const { owner, repo, host } = parseRepoUrl(ctx.parameters.repoUrl); + const { owner, repo, host, organization } = parseRepoUrl( + ctx.parameters.repoUrl, + ); + + if (!organization) { + throw new InputError( + `No Organization was included in the repo URL to create ${ctx.parameters.repoUrl}`, + ); + } + const integrationConfig = integrations.azure.byHost(host); if (!integrationConfig) { @@ -62,10 +71,18 @@ export function createPublishAzureAction(options: { integrationConfig.config.token, ); - const webApi = new WebApi(`https://${host}/${owner}`, authHandler); + const webApi = new WebApi(`https://${host}/${organization}`, authHandler); const client = await webApi.getGitApi(); const createOptions: GitRepositoryCreateOptions = { name: repo }; - const { remoteUrl } = await client.createRepository(createOptions, owner); + const returnedRepo = await client.createRepository(createOptions, owner); + + if (!returnedRepo) { + throw new InputError( + `Unable to create the repository with Organization ${organization}, Project ${owner} and Repo ${repo}. + Please make sure you that both the Org and Project are typed corrected and exist.`, + ); + } + const remoteUrl = returnedRepo.remoteUrl; if (!remoteUrl) { throw new InputError( 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 d530fb90bc..bc0c1f97c7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -22,6 +22,7 @@ import { import { TemplateAction } from '../../types'; import { initRepoAndPush } from '../../../stages/publish/helpers'; import { parseRepoUrl } from './util'; +import fetch from 'cross-fetch'; const createBitbucketCloudRepository = async (opts: { owner: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index f5af2885aa..67ba402354 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -87,7 +87,7 @@ export function createPublishGitlabAction(options: { await initRepoAndPush({ dir: ctx.workspacePath, - remoteUrl, + remoteUrl: http_url_to_repo as string, auth: { username: 'oauth2', password: integrationConfig.config.token, 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 8a67c9c5f0..9349d5b5eb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -1,3 +1,19 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { InputError } from '@backstage/backend-common'; export const parseRepoUrl = (repoUrl: string) => { @@ -24,5 +40,7 @@ export const parseRepoUrl = (repoUrl: string) => { ); } - return { host, owner, repo }; + const organization = parsed.searchParams.get('organization'); + + return { host, owner, repo, organization }; }; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index f346a65a4b..31b4c4fa9b 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -28,6 +28,7 @@ function splitFormData(url: string | undefined) { let host = undefined; let owner = undefined; let repo = undefined; + let organization = undefined; try { if (url) { @@ -35,18 +36,21 @@ function splitFormData(url: string | undefined) { host = parsed.host; owner = parsed.searchParams.get('owner') || undefined; repo = parsed.searchParams.get('repo') || undefined; + // This is azure dev ops specific. not used for any other provider. + organization = parsed.searchParams.get('organization') || undefined; } } catch { /* ok */ } - return { host, owner, repo }; + return { host, owner, repo, organization }; } function serializeFormData(data: { host?: string; owner?: string; repo?: string; + organization?: string; }) { if (!data.host) { return undefined; @@ -58,6 +62,9 @@ function serializeFormData(data: { if (data.repo) { params.set('repo', data.repo); } + if (data.organization) { + params.set('organization', data.organization); + } return `${data.host}?${params.toString()}`; } @@ -75,36 +82,71 @@ export const RepoUrlPicker: Field = ({ return await api.getIntegrationsList({ allowedHosts }); }); - const { host, owner, repo } = splitFormData(formData); + const { host, owner, repo, organization } = splitFormData(formData); const updateHost = useCallback( (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => onChange( - serializeFormData({ host: evt.target.value as string, owner, repo }), + serializeFormData({ + host: evt.target.value as string, + owner, + repo, + organization, + }), ), - [onChange, owner, repo], + [onChange, owner, repo, organization], ); const updateOwner = useCallback( (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => onChange( - serializeFormData({ host, owner: evt.target.value as string, repo }), + serializeFormData({ + host, + owner: evt.target.value as string, + repo, + organization, + }), ), - [onChange, host, repo], + [onChange, host, repo, organization], ); const updateRepo = useCallback( (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => onChange( - serializeFormData({ host, owner, repo: evt.target.value as string }), + serializeFormData({ + host, + owner, + repo: evt.target.value as string, + organization, + }), ), - [onChange, host, owner], + [onChange, host, owner, organization], + ); + + const updateOrganization = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ + host, + owner, + repo, + organization: evt.target.value as string, + }), + ), + [onChange, host, owner, repo], ); useEffect(() => { if (host === undefined && integrations?.length) { - onChange(serializeFormData({ host: integrations[0].host, owner, repo })); + onChange( + serializeFormData({ + host: integrations[0].host, + owner, + repo, + organization, + }), + ); } - }, [onChange, integrations, host, owner, repo]); + }, [onChange, integrations, host, owner, repo, organization]); if (loading) { return ; @@ -119,18 +161,37 @@ export const RepoUrlPicker: Field = ({ > Host The host where the repository will be created + {host === 'dev.azure.com' && ( + 0 && !organization} + > + Organization + + The name of the organization + + )}