From 802b41b65b760e990bca4dcc862bedb0065200ec Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Thu, 18 Mar 2021 15:43:50 -0700 Subject: [PATCH 1/5] feat: allow custom directory to be specified for github publish action Signed-off-by: Jonah Back --- .changeset/bright-lions-camp.md | 5 +++++ .../src/scaffolder/actions/builtin/publish/github.ts | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .changeset/bright-lions-camp.md diff --git a/.changeset/bright-lions-camp.md b/.changeset/bright-lions-camp.md new file mode 100644 index 0000000000..ccb812b01e --- /dev/null +++ b/.changeset/bright-lions-camp.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Allow custom directory to be specified for github publish action diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 4f9b6038a1..5979136488 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { resolve as resolvePath } from 'path'; import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, @@ -40,6 +40,7 @@ export function createPublishGithubAction(options: { repoUrl: string; description?: string; access?: string; + repoPath?: string; repoVisibility: 'private' | 'internal' | 'public'; }>({ id: 'publish:github', @@ -67,6 +68,10 @@ export function createPublishGithubAction(options: { type: 'string', enum: ['private', 'public', 'internal'], }, + repoPath: { + title: 'Repository Path', + type: 'string', + }, }, }, output: { @@ -158,9 +163,12 @@ export function createPublishGithubAction(options: { const remoteUrl = data.clone_url; const repoContentsUrl = `${data.html_url}/blob/master`; + const outputPath = ctx.input.repoPath + ? resolvePath(ctx.workspacePath, ctx.input.repoPath) + : ctx.workspacePath; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: outputPath, remoteUrl, auth: { username: 'x-access-token', From d20e2993849515dad830d453d6f3ab4e09f2d0db Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Sun, 21 Mar 2021 21:01:00 -0700 Subject: [PATCH 2/5] Use 'sourcePath' for custom git source directory Signed-off-by: Jonah Back --- .../src/scaffolder/actions/builtin/publish/github.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 5979136488..a791f3ad39 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -40,7 +40,7 @@ export function createPublishGithubAction(options: { repoUrl: string; description?: string; access?: string; - repoPath?: string; + sourcePath?: string; repoVisibility: 'private' | 'internal' | 'public'; }>({ id: 'publish:github', @@ -68,7 +68,7 @@ export function createPublishGithubAction(options: { type: 'string', enum: ['private', 'public', 'internal'], }, - repoPath: { + sourcePath: { title: 'Repository Path', type: 'string', }, @@ -163,8 +163,8 @@ export function createPublishGithubAction(options: { const remoteUrl = data.clone_url; const repoContentsUrl = `${data.html_url}/blob/master`; - const outputPath = ctx.input.repoPath - ? resolvePath(ctx.workspacePath, ctx.input.repoPath) + const outputPath = ctx.input.sourcePath + ? resolvePath(ctx.workspacePath, ctx.input.sourcePath) : ctx.workspacePath; await initRepoAndPush({ From 5a572c4c83fe06679718835b0c1c57530e5e4b05 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Wed, 24 Mar 2021 15:55:16 -0700 Subject: [PATCH 3/5] Update .changeset/bright-lions-camp.md Co-authored-by: Patrik Oldsberg Signed-off-by: Jonah Back --- .changeset/bright-lions-camp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/bright-lions-camp.md b/.changeset/bright-lions-camp.md index ccb812b01e..fee5d5b5fc 100644 --- a/.changeset/bright-lions-camp.md +++ b/.changeset/bright-lions-camp.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -Allow custom directory to be specified for github publish action +Allow custom directory to be specified for GitHub publish action From d3accca86001d6c367e58dfd89c1ee7b14054964 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Wed, 24 Mar 2021 21:09:28 -0700 Subject: [PATCH 4/5] Sanitize input to avoid directory traversal Signed-off-by: Jonah Back --- .../src/scaffolder/actions/builtin/publish/github.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index a791f3ad39..ca1b0f3d82 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { resolve as resolvePath } from 'path'; +import { join as joinPath, normalize as normalizePath } from 'path'; import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, @@ -69,7 +69,7 @@ export function createPublishGithubAction(options: { enum: ['private', 'public', 'internal'], }, sourcePath: { - title: 'Repository Path', + title: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', type: 'string', }, }, @@ -163,9 +163,11 @@ export function createPublishGithubAction(options: { const remoteUrl = data.clone_url; const repoContentsUrl = `${data.html_url}/blob/master`; - const outputPath = ctx.input.sourcePath - ? resolvePath(ctx.workspacePath, ctx.input.sourcePath) - : ctx.workspacePath; + let outputPath = ctx.input.sourcePath; + if (ctx.input.sourcePath) { + const safeSuffix = normalizePath(ctx.input.sourcePath).replace(/^(\.\.(\/|\\|$))+/, ''); + outputPath = joinPath(ctx.workspace, safeSuffix); + } await initRepoAndPush({ dir: outputPath, From 8419d669c717cc122139d0a5bf6310b0151f4d2f Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Wed, 24 Mar 2021 21:20:58 -0700 Subject: [PATCH 5/5] Add sourcePath option to all git publishers Signed-off-by: Jonah Back --- .../scaffolder/actions/builtin/publish/azure.ts | 10 ++++++++-- .../actions/builtin/publish/bitbucket.ts | 10 ++++++++-- .../scaffolder/actions/builtin/publish/github.ts | 13 ++++--------- .../scaffolder/actions/builtin/publish/gitlab.ts | 10 ++++++++-- .../scaffolder/actions/builtin/publish/util.ts | 15 +++++++++++++++ 5 files changed, 43 insertions(+), 15 deletions(-) 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 0141de3499..e1a064b3e6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -19,7 +19,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { initRepoAndPush } from '../../../stages/publish/helpers'; import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishAzureAction(options: { @@ -30,6 +30,7 @@ export function createPublishAzureAction(options: { return createTemplateAction<{ repoUrl: string; description?: string; + sourcePath?: string; }>({ id: 'publish:azure', description: @@ -47,6 +48,11 @@ export function createPublishAzureAction(options: { title: 'Repository Description', type: 'string', }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -112,7 +118,7 @@ export function createPublishAzureAction(options: { const repoContentsUrl = remoteUrl; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: 'notempty', 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 a7370356cc..13f639d745 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -20,7 +20,7 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import fetch from 'cross-fetch'; import { createTemplateAction } from '../../createTemplateAction'; @@ -165,6 +165,7 @@ export function createPublishBitbucketAction(options: { repoUrl: string; description: string; repoVisibility: 'private' | 'public'; + sourcePath?: string; }>({ id: 'publish:bitbucket', description: @@ -187,6 +188,11 @@ export function createPublishBitbucketAction(options: { type: 'string', enum: ['private', 'public'], }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -233,7 +239,7 @@ export function createPublishBitbucketAction(options: { }); await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: integrationConfig.config.username diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index ca1b0f3d82..666c04f368 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { join as joinPath, normalize as normalizePath } from 'path'; import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, @@ -21,7 +20,7 @@ import { } from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishGithubAction(options: { @@ -69,7 +68,8 @@ export function createPublishGithubAction(options: { enum: ['private', 'public', 'internal'], }, sourcePath: { - title: 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', type: 'string', }, }, @@ -163,14 +163,9 @@ export function createPublishGithubAction(options: { const remoteUrl = data.clone_url; const repoContentsUrl = `${data.html_url}/blob/master`; - let outputPath = ctx.input.sourcePath; - if (ctx.input.sourcePath) { - const safeSuffix = normalizePath(ctx.input.sourcePath).replace(/^(\.\.(\/|\\|$))+/, ''); - outputPath = joinPath(ctx.workspace, safeSuffix); - } await initRepoAndPush({ - dir: outputPath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: 'x-access-token', 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 12fc90cc94..37074d6f57 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -18,7 +18,7 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { Gitlab } from '@gitbeaker/node'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishGitlabAction(options: { @@ -29,6 +29,7 @@ export function createPublishGitlabAction(options: { return createTemplateAction<{ repoUrl: string; repoVisibility: 'private' | 'internal' | 'public'; + sourcePath?: string; }>({ id: 'publish:gitlab', description: @@ -47,6 +48,11 @@ export function createPublishGitlabAction(options: { type: 'string', enum: ['private', 'public', 'internal'], }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -106,7 +112,7 @@ export function createPublishGitlabAction(options: { const repoContentsUrl = `${remoteUrl}/-/blob/master`; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl: http_url_to_repo as string, auth: { username: 'oauth2', 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 89bbf24574..07b3823e7b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -15,6 +15,21 @@ */ import { InputError } from '@backstage/errors'; +import { join as joinPath, normalize as normalizePath } from 'path'; + +export const getRepoSourceDirectory = ( + workspacePath: string, + sourcePath: string | undefined, +) => { + if (sourcePath) { + const safeSuffix = normalizePath(sourcePath).replace( + /^(\.\.(\/|\\|$))+/, + '', + ); + return joinPath(workspacePath, safeSuffix); + } + return workspacePath; +}; export const parseRepoUrl = (repoUrl: string) => { let parsed;