scaffolder: Fix providers

Co-authored-by: Ben Lambert <ben@blam.sh>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2021-02-25 10:13:06 +01:00
parent c19f41494d
commit b853e084dc
6 changed files with 334 additions and 22 deletions
@@ -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 }}'
@@ -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(
@@ -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;
@@ -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,
@@ -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 };
};
@@ -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 <Progress />;
@@ -119,18 +161,37 @@ export const RepoUrlPicker: Field = ({
>
<InputLabel htmlFor="hostInput">Host</InputLabel>
<Select native id="hostInput" onChange={updateHost} value={host}>
{integrations!
.filter(i => allowedHosts?.includes(i.host))
.map(i => (
<option key={i.host} value={i.host}>
{i.title}
</option>
))}
{integrations ? (
integrations
.filter(i => allowedHosts?.includes(i.host))
.map(i => (
<option key={i.host} value={i.host}>
{i.title}
</option>
))
) : (
<p>loading</p>
)}
</Select>
<FormHelperText>
The host where the repository will be created
</FormHelperText>
</FormControl>
{host === 'dev.azure.com' && (
<FormControl
margin="normal"
required
error={rawErrors?.length > 0 && !organization}
>
<InputLabel htmlFor="repoInput">Organization</InputLabel>
<Input
id="repoInput"
onChange={updateOrganization}
value={organization}
/>
<FormHelperText>The name of the organization</FormHelperText>
</FormControl>
)}
<FormControl
margin="normal"
required