@cdbrcl/scaffolder-backend-module-azure-repositories
Version:
A collection of Backstage scaffolder backend modules for Azure repositories.
466 lines (455 loc) • 14.5 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var backendCommon = require('@backstage/backend-common');
var errors = require('@backstage/errors');
var integration = require('@backstage/integration');
var pluginScaffolderBackend = require('@backstage/plugin-scaffolder-backend');
var azdev = require('azure-devops-node-api');
var path = require('path');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var azdev__namespace = /*#__PURE__*/_interopNamespace(azdev);
async function cloneRepo({
dir,
auth,
logger,
remote = "origin",
remoteUrl,
branch = "main"
}) {
const git = backendCommon.Git.fromAuth({
...auth,
logger
});
await git.clone({
url: remoteUrl,
dir,
ref: branch,
noCheckout: false
});
await git.addRemote({
dir,
remote,
url: remoteUrl
});
}
async function commitAndPushBranch({
dir,
auth,
logger,
remote = "origin",
commitMessage,
gitAuthorInfo,
branch = "scaffolder"
}) {
var _a, _b;
const authorInfo = {
name: (_a = gitAuthorInfo == null ? void 0 : gitAuthorInfo.name) != null ? _a : "Scaffolder",
email: (_b = gitAuthorInfo == null ? void 0 : gitAuthorInfo.email) != null ? _b : "scaffolder@backstage.io"
};
const git = backendCommon.Git.fromAuth({
...auth,
logger
});
const currentBranch = await git.currentBranch({ dir });
logger.info(`Current branch is ${currentBranch}`);
logger.info(`Target branch is ${branch}`);
if (currentBranch !== branch) {
try {
await git.branch({
dir,
ref: branch
});
} catch (err) {
if (err instanceof Error && err.name === "AlreadyExistsError") ; else {
throw err;
}
}
await git.checkout({
dir,
ref: branch
});
}
await git.add({
dir,
filepath: "."
});
await git.commit({
dir,
message: commitMessage,
author: authorInfo,
committer: authorInfo
});
await git.push({
dir,
remote,
remoteRef: `refs/heads/${branch}`
});
}
async function createADOPullRequest({
gitPullRequestToCreate,
server,
auth,
repoId,
project,
supportsIterations
}) {
const url = `https://${server}/`;
const orgUrl = url + auth.org;
const token = auth.token || "";
const authHandler = azdev__namespace.getHandlerFromToken(token);
const connection = new azdev__namespace.WebApi(orgUrl, authHandler);
const gitApiObject = await connection.getGitApi();
const pr = await gitApiObject.createPullRequest(
gitPullRequestToCreate,
repoId,
project,
supportsIterations
);
return pr;
}
async function updateADOPullRequest({
gitPullRequestToUpdate,
server,
auth,
repoId,
project,
pullRequestId
}) {
const url = `https://${server}/`;
const orgUrl = url + auth.org;
const token = auth.token || "";
const authHandler = azdev__namespace.getHandlerFromToken(token);
const connection = new azdev__namespace.WebApi(orgUrl, authHandler);
const gitApiObject = await connection.getGitApi();
await gitApiObject.updatePullRequest(
gitPullRequestToUpdate,
repoId,
pullRequestId,
project
);
}
const cloneAzureRepoAction = (options) => {
const { integrations } = options;
return pluginScaffolderBackend.createTemplateAction({
id: "azure:repo:clone",
description: "Clone an Azure repository into the workspace directory.",
schema: {
input: {
required: ["remoteUrl"],
type: "object",
properties: {
remoteUrl: {
title: "Remote URL",
type: "string",
description: "The Git URL to the repository."
},
branch: {
title: "Repository Branch",
type: "string",
description: "The branch to checkout to."
},
targetPath: {
title: "Working Subdirectory",
type: "string",
description: "The subdirectory of the working directory to clone the repository into."
},
server: {
type: "string",
title: "Server hostname",
description: "The hostname of the Azure DevOps service. Defaults to dev.azure.com"
},
token: {
title: "Authenticatino Token",
type: "string",
description: "The token to use for authorization."
}
}
}
},
async handler(ctx) {
var _a;
const { remoteUrl, branch } = ctx.input;
const targetPath = (_a = ctx.input.targetPath) != null ? _a : "./";
const outputDir = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath);
const provider = integration.DefaultAzureDevOpsCredentialsProvider.fromIntegrations(integrations);
const credentials = await provider.getCredentials({ url: remoteUrl });
let auth;
if (ctx.input.token) {
auth = { username: "not-empty", password: ctx.input.token };
} else if ((credentials == null ? void 0 : credentials.type) === "pat") {
auth = { username: "not-empty", password: credentials.token };
} else if ((credentials == null ? void 0 : credentials.type) === "bearer") {
auth = { token: credentials.token };
} else {
throw new errors.InputError(
`No token credentials provided for Azure repository ${remoteUrl}`
);
}
await cloneRepo({
dir: outputDir,
auth,
logger: ctx.logger,
remoteUrl,
branch
});
}
});
};
const getRepoSourceDirectory = (workspacePath, sourcePath) => {
if (sourcePath) {
const safeSuffix = path.normalize(sourcePath).replace(
/^(\.\.(\/|\\|$))+/,
""
);
const path$1 = path.join(workspacePath, safeSuffix);
if (!backendCommon.isChildPath(workspacePath, path$1)) {
throw new Error("Invalid source path");
}
return path$1;
}
return workspacePath;
};
const pushAzureRepoAction = (options) => {
const { integrations, config } = options;
return pluginScaffolderBackend.createTemplateAction({
id: "azure:repo:push",
description: "Push the content in the workspace to a remote Azure repository.",
schema: {
input: {
required: ["remoteUrl"],
type: "object",
properties: {
remoteUrl: {
title: "Remote URL",
type: "string",
description: "The Git URL to the repository."
},
branch: {
title: "Repository Branch",
type: "string",
description: "The branch to checkout to."
},
sourcePath: {
type: "string",
title: "Working Subdirectory",
description: "The subdirectory of the working directory containing the repository."
},
gitCommitMessage: {
title: "Git Commit Message",
type: "string",
description: "Sets the commit message on the repository. The default value is 'Initial commit'"
},
gitAuthorName: {
title: "Default Author Name",
type: "string",
description: "Sets the default author name for the commit. The default value is 'Scaffolder'."
},
gitAuthorEmail: {
title: "Default Author Email",
type: "string",
description: "Sets the default author email for the commit."
},
token: {
title: "Authenticatino Token",
type: "string",
description: "The token to use for authorization."
}
}
}
},
async handler(ctx) {
const {
remoteUrl,
branch,
gitCommitMessage,
gitAuthorName,
gitAuthorEmail
} = ctx.input;
const sourcePath = getRepoSourceDirectory(
ctx.workspacePath,
ctx.input.sourcePath
);
const gitAuthorInfo = {
name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
};
const provider = integration.DefaultAzureDevOpsCredentialsProvider.fromIntegrations(integrations);
const credentials = await provider.getCredentials({ url: remoteUrl });
let auth;
if (ctx.input.token) {
auth = { username: "not-empty", password: ctx.input.token };
} else if ((credentials == null ? void 0 : credentials.type) === "pat") {
auth = { username: "not-empty", password: credentials.token };
} else if ((credentials == null ? void 0 : credentials.type) === "bearer") {
auth = { token: credentials.token };
} else {
throw new errors.InputError(
`No token credentials provided for Azure repository ${remoteUrl}`
);
}
await commitAndPushBranch({
dir: sourcePath,
auth,
logger: ctx.logger,
commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage") || "Initial commit",
gitAuthorInfo,
branch
});
}
});
};
const pullRequestAzureRepoAction = (options) => {
const { integrations } = options;
return pluginScaffolderBackend.createTemplateAction({
id: "azure:repo:pr",
description: "Create a PR to a repository in Azure DevOps.",
schema: {
input: {
type: "object",
required: ["repoId", "title"],
properties: {
organization: {
title: "Organization Name",
type: "string",
description: "The name of the organization in Azure DevOps."
},
sourceBranch: {
title: "Source Branch",
type: "string",
description: "The branch to merge into the source."
},
targetBranch: {
title: "Target Branch",
type: "string",
description: "The branch to merge into (default: main)."
},
title: {
title: "Title",
description: "The title of the pull request.",
type: "string"
},
description: {
title: "Description",
description: "The description of the pull request.",
type: "string"
},
repoId: {
title: "Remote Repo ID",
description: "Repo ID of the pull request.",
type: "string"
},
project: {
title: "ADO Project",
description: "The Project in Azure DevOps.",
type: "string"
},
supportsIterations: {
title: "Supports Iterations",
description: "Whether or not the PR supports iterations.",
type: "boolean"
},
server: {
type: "string",
title: "Server hostname",
description: "The hostname of the Azure DevOps service. Defaults to dev.azure.com"
},
token: {
title: "Authentication Token",
type: "string",
description: "The token to use for authorization."
},
autoComplete: {
title: "Enable auto-completion",
description: "Enable auto-completion of the pull request once policies are met",
type: "boolean"
}
}
},
output: {
type: "number",
properties: {
pullRequestId: {
title: "The ID of the created pull request",
type: "number"
}
}
}
},
async handler(ctx) {
var _a, _b, _c, _d, _e, _f, _g;
const { title, repoId, server, project, supportsIterations } = ctx.input;
const sourceBranch = (_a = `refs/heads/${ctx.input.sourceBranch}`) != null ? _a : `refs/heads/scaffolder`;
const targetBranch = (_b = `refs/heads/${ctx.input.targetBranch}`) != null ? _b : `refs/heads/main`;
const host = server != null ? server : "dev.azure.com";
const provider = integration.DefaultAzureDevOpsCredentialsProvider.fromIntegrations(integrations);
const url = `https://${host}/${ctx.input.organization}`;
const credentials = await provider.getCredentials({ url });
const org = (_c = ctx.input.organization) != null ? _c : "not-empty";
const token = (_d = ctx.input.token) != null ? _d : credentials == null ? void 0 : credentials.token;
const description = (_e = ctx.input.description) != null ? _e : "";
const autoComplete = (_f = ctx.input.autoComplete) != null ? _f : false;
if (!token) {
throw new errors.InputError(`No token credentials provided for ${url}`);
}
const pullRequest = {
sourceRefName: sourceBranch,
targetRefName: targetBranch,
title,
description
};
const pullRequestResponse = await createADOPullRequest({
gitPullRequestToCreate: pullRequest,
server: host,
auth: {
org,
token
},
repoId,
project,
supportsIterations
});
if (autoComplete) {
const updateProperties = {
autoCompleteSetBy: { id: (_g = pullRequestResponse.createdBy) == null ? void 0 : _g.id },
// the idea here is that if you want to fire-and-forget the PR by setting autocomplete, you don't also want
// the branch to stick around afterwards.
completionOptions: {
deleteSourceBranch: true
}
};
await updateADOPullRequest({
gitPullRequestToUpdate: updateProperties,
server: host,
auth: {
org,
token
},
repoId,
project,
pullRequestId: pullRequestResponse.pullRequestId
});
}
ctx.output("pullRequestId", pullRequestResponse.pullRequestId);
}
});
};
exports.cloneAzureRepoAction = cloneAzureRepoAction;
exports.pullRequestAzureRepoAction = pullRequestAzureRepoAction;
exports.pushAzureRepoAction = pushAzureRepoAction;
//# sourceMappingURL=index.cjs.js.map