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
+
+ )}