UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

226 lines (213 loc) 8.48 kB
/* * Copyright © 2020 Atomist, Inc. * * 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 { HandlerContext } from "@atomist/automation-client/lib/HandlerContext"; import { guid } from "@atomist/automation-client/lib/internal/util/string"; import { RepoRef } from "@atomist/automation-client/lib/operations/common/RepoId"; import { AnyProjectEditor, EditResult, ProjectEditor, toEditor, } from "@atomist/automation-client/lib/operations/edit/projectEditor"; import { isLocalProject } from "@atomist/automation-client/lib/project/local/LocalProject"; import { Project } from "@atomist/automation-client/lib/project/Project"; import { buttonForCommand } from "@atomist/automation-client/lib/spi/message/MessageClient"; import { logger } from "@atomist/automation-client/lib/util/logger"; import { bold, codeBlock, italic } from "@atomist/slack-messages"; import { CodeTransformRegistration } from "../../../api/registration/CodeTransformRegistration"; import { DryRunParameter, MsgIdParameter } from "../../machine/handlerRegistrations"; import { execPromise } from "../../misc/child_process"; import { slackErrorMessage, slackInfoMessage, slackSuccessMessage } from "../../misc/slack/messages"; import { confirmEditedness } from "./confirmEditedness"; /** * Wrap this editor to make it chatty, so it responds to Slack if there's nothing to do. * It also honors the dryRun parameter flag to just capture the git diff and send it back to Slack instead * of pushing changes to Git. */ export function chattyDryRunAwareEditor( ctr: CodeTransformRegistration<any>, underlyingEditor: AnyProjectEditor, ): ProjectEditor { return async (project: Project, context: HandlerContext, params: any) => { const id = project.id; const editorName = ctr.name; try { await sendDryRunUpdateMessage(editorName, id, params, context, ctr); const tentativeEditResult = await toEditor(underlyingEditor)(project, context, params); const editResult = await confirmEditedness(tentativeEditResult); // Figure out if this CodeTransform is running in dryRun mode; if so capture git diff and don't push changes if (!editResult.edited) { if (!editResult.success) { await sendFailureMessage(editorName, id, params, editResult, context, ctr); } else { await sendNoUpdateMessage(editorName, id, params, context, ctr); } return { target: project, edited: false, success: false }; } else if (isDryRun(params)) { if (!isLocalProject(project)) { const message = `Project is not a local project, cannot diff`; logger.warn(message); return { target: project, edited: false, success: true }; } let diff = ""; try { const gitDiffResult = await execPromise("git", ["diff"], { cwd: project.baseDir }); diff = gitDiffResult.stdout; } catch (err) { logger.error(`Error diffing project: %s`, err.message); diff = `Error obtaining \`git diff\`:\n\n${codeBlock(err.message)}`; } await sendDryRunSummaryMessage(editorName, id, diff, params, context, ctr); return { target: project, edited: false, success: true }; } else { await sendSuccessMessage(editorName, id, params, context, ctr); } return editResult; } catch (err) { await context.messageClient.respond( slackErrorMessage( `Code Transform${isDryRun(params) ? " (dry run)" : ""}`, `Code transform ${italic(editorName)} failed while changing ${bold(slug(id))}:\n\n${codeBlock( err.message, )}`, context, ), { id: params[MsgIdParameter.name] }, ); logger.warn("Code Transform error acting on %j: %s", project.id, err); return { target: project, edited: false, success: false }; } }; } function isDryRun(params: any): boolean { return !!params && params[DryRunParameter.name] === true; } function slug(id: RepoRef): string { return `${id.owner}/${id.repo}/${id.branch}`; } function isChatty(ctr: CodeTransformRegistration): boolean { if (ctr.chatty !== undefined) { return ctr.chatty; } else { return true; } } async function sendDryRunUpdateMessage( codeTransformName: string, id: RepoRef, params: any, ctx: HandlerContext, ctr: CodeTransformRegistration, ): Promise<void> { if (isChatty(ctr) && !!params[MsgIdParameter.name]) { await ctx.messageClient.respond( slackInfoMessage( "Code Transform", `Applying code transform ${italic(codeTransformName)} to ${bold(slug(id))}`, ), { id: params[MsgIdParameter.name] }, ); } } async function sendFailureMessage( codeTransformName: string, id: RepoRef, params: any, editResult: EditResult, ctx: HandlerContext, ctr: CodeTransformRegistration, ): Promise<void> { if (isChatty(ctr)) { await ctx.messageClient.respond( slackErrorMessage( `Code Transform${isDryRun(params) ? " (dry run)" : ""}`, `Code transform ${italic(codeTransformName)} failed while changing ${bold(slug(id))}:\n\n${ editResult.error ? codeBlock(editResult.error.message) : "" }`, ctx, ), { id: params[MsgIdParameter.name] }, ); } } async function sendNoUpdateMessage( codeTransformName: string, id: RepoRef, params: any, ctx: HandlerContext, ctr: CodeTransformRegistration, ): Promise<void> { if (isChatty(ctr)) { await ctx.messageClient.respond( slackInfoMessage( `Code Transform${isDryRun(params) ? " (dry run)" : ""}`, `Code transform ${italic(codeTransformName)} made no changes to ${bold(slug(id))}`, ), { id: params[MsgIdParameter.name] }, ); } } async function sendSuccessMessage( codeTransformName: string, id: RepoRef, params: any, ctx: HandlerContext, ctr: CodeTransformRegistration, ): Promise<void> { if (isChatty(ctr)) { const msgId = params[MsgIdParameter.name]; await ctx.messageClient.respond( slackSuccessMessage( "Code Transform", `Successfully applied code transform ${italic(codeTransformName)} to ${bold(slug(id))}`, ), { id: msgId }, ); } } async function sendDryRunSummaryMessage( codeTransformName: string, id: RepoRef, diff: string, params: any, ctx: HandlerContext, ctr: CodeTransformRegistration, ): Promise<void> { const msgId = params[MsgIdParameter.name] || guid(); const applyAction = { actions: [ buttonForCommand({ text: "Apply Transform" }, codeTransformName, { // reuse the other parameters, but set the dryRun flag to false and pin to one repo ...params, "dry-run": false, "msgId": msgId, "targets.sha": params.targets.sha, "targets.owner": id.owner, "targets.repo": id.repo, }), ], }; await ctx.messageClient.respond( slackInfoMessage( `Code Transform (dry run)`, `Code transform ${italic(codeTransformName)} would make the following changes to ${bold(slug(id))}: ${codeBlock(diff)} `, applyAction, ), { id: msgId }, ); }