UNPKG

@atomist/sdm

Version:

Atomist Software Delivery Machine SDK

355 lines 19.6 kB
"use strict"; /* * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.mapCommand = exports.decorateSoftwareDeliveryMachine = void 0; const decorators_1 = require("@atomist/automation-client/lib/decorators"); const HandlerResult_1 = require("@atomist/automation-client/lib/HandlerResult"); const metadataReading_1 = require("@atomist/automation-client/lib/internal/metadata/metadataReading"); const parameterPopulation_1 = require("@atomist/automation-client/lib/internal/parameterPopulation"); const string_1 = require("@atomist/automation-client/lib/internal/util/string"); const constructionUtils_1 = require("@atomist/automation-client/lib/util/constructionUtils"); const slack_messages_1 = require("@atomist/slack-messages"); const _ = require("lodash"); const handlerRegistrations_1 = require("../../../api-helper/machine/handlerRegistrations"); const messages_1 = require("../../../api-helper/misc/slack/messages"); const CommandRegistration_1 = require("../../../api/registration/CommandRegistration"); function decorateSoftwareDeliveryMachine(sdm) { const proxy = new Proxy(sdm, { get: (target, propKey) => { if (propKey === "addCommand") { return (...args) => { const cmd = args[0]; target[propKey](Object.assign({ name: cmd.name }, mapCommand(cmd)(sdm))); }; } else { return target[propKey]; } }, }); return proxy; } exports.decorateSoftwareDeliveryMachine = decorateSoftwareDeliveryMachine; function mapCommand(chr) { return sdm => { const ch = handlerRegistrations_1.commandHandlerRegistrationToCommand(sdm, chr); const metadata = metadataReading_1.metadataFromInstance(constructionUtils_1.toFactory(ch)()); const parameterNames = metadata.parameters.filter(p => p.displayable === undefined || !!p.displayable).map(p => p.name); const mappedParameterNames = metadata.mapped_parameters.map(p => p.name); const allParameters = [...parameterNames, ...mappedParameterNames]; const mapIntent = (intents) => { if (!!intents && intents.length > 0) { if (parameterNames.length > 0) { return `^(?:${intents.map(i => i.replace(/ /g, "\\s+")).join("|")})(?:\\s+--(?:${allParameters.join("|")})=(?:'[^']*?'|"[^"]*?"|[\\w-]*?))*$`; } else { return `^(?:${intents.map(i => i.replace(/ /g, "\\s+")).join("|")})$`; } } else { return undefined; } }; return { name: metadata.name, description: metadata.description, intent: mapIntent(metadata.intent || []), tags: (metadata.tags || []).map(t => t.name), listener: async (ci) => { const instance = constructionUtils_1.toFactory(ch)(); const parametersInstance = instance.freshParametersInstance(); const parameterDefinition = {}; const intent = (ci.context.trigger).raw_message; if (!!intent) { const args = require("yargs-parser")(intent, { configuration: { "dot-notation": false } }); ci.context.trigger.parameters.push(..._.map(args, (v, k) => ({ name: k, value: v, }))); } metadata.parameters.forEach(p => { parameterDefinition[p.name] = Object.assign(Object.assign({}, p), { pattern: !!p.pattern ? new RegExp(p.pattern) : undefined }); }); const parameters = await ci.promptFor(parameterDefinition, { autoSubmit: metadata.auto_submit, parameterStyle: CommandRegistration_1.ParameterStyle.Dialog[metadata.question], }); parameterPopulation_1.populateParameters(parametersInstance, metadata, _.map(parameters, (v, k) => ({ name: k, value: v, }))); parameterPopulation_1.populateValues(parametersInstance, metadata, ci.configuration); await populateSecrets(parametersInstance, metadata, ci); try { const missing = await populateMappedParameters(parametersInstance, metadata, ci); if (missing.length > 0) { await ci.addressChannels(messages_1.slackErrorMessage("Missing Mapped Parameters", missing.join("\n"), ci.context)); return HandlerResult_1.Success; } } catch (e) { if (e instanceof MappedParamterError) { await ci.addressChannels(messages_1.slackErrorMessage(e.title, e.message, ci.context)); return HandlerResult_1.Success; } else { throw e; } } return instance.handle(ci.context, parametersInstance); }, }; }; } exports.mapCommand = mapCommand; async function populateSecrets(parameters, metadata, ci) { var _a; for (const secret of (metadata.secrets || [])) { if (secret.uri.startsWith(decorators_1.Secrets.UserToken)) { const chatId = _.get(ci, "context.trigger.source.slack.user.id"); if (!!chatId) { const resourceUser = await ci.context.graphClient.query({ name: "ResourceUser", variables: { id: chatId, }, }); const credential = _.get(resourceUser, "ChatId[0].person.gitHubId.credential"); if (!!credential) { const s = credential.secret; _.update(parameters, secret.name, () => s); } else { // Query GitHubAppResourceProvider to get the resource provider id const provider = await ci.context.graphClient.query({ name: "GitHubAppResourceProvider", }); if (!!((_a = provider === null || provider === void 0 ? void 0 : provider.GitHubAppResourceProvider[0]) === null || _a === void 0 ? void 0 : _a.id)) { // Send message when there is a GitHubAppResourceProvider const orgUrl = `https://api.atomist.com/v2/auth/teams/${ci.context.workspaceId}/resource-providers/${provider.GitHubAppResourceProvider[0].id}/token?state=${string_1.guid()}&redirect-uri=https://www.atomist.com/success.html`; await ci.addressChannels(messages_1.slackInfoMessage("Link GitHub Account", `In order to run this command Atomist needs to link your GitHub identity to your Slack user.\n\nPlease ${slack_messages_1.url(orgUrl, "click here")} to link your account.`)); throw new handlerRegistrations_1.CommandListenerExecutionInterruptError("Sending token collection message"); } } } } else if (secret.uri === decorators_1.Secrets.OrgToken) { // TODO cd add this } } } async function populateMappedParameters(parameters, metadata, ci) { const missing = []; for (const mp of (metadata.mapped_parameters || [])) { const value = ci.context.trigger.parameters.find(p => p.name === mp.name); if (value !== undefined) { _.update(parameters, mp.name, () => value.value); } else { switch (mp.uri) { case decorators_1.MappedParameters.GitHubOwner: case decorators_1.MappedParameters.GitHubOwnerWithUser: const ownerDetails = await loadRepositoryDetailsFromChannel(ci, metadata); _.update(parameters, mp.name, () => ownerDetails.owner); break; case decorators_1.MappedParameters.GitHubRepository: case decorators_1.MappedParameters.GitHubAllRepositories: const repoDetails = await loadRepositoryDetailsFromChannel(ci, metadata); _.update(parameters, mp.name, () => repoDetails.name); break; case decorators_1.MappedParameters.GitHubApiUrl: const apiUrlDetails = await loadRepositoryDetailsFromChannel(ci, metadata); _.update(parameters, mp.name, () => apiUrlDetails.apiUrl); break; case decorators_1.MappedParameters.GitHubRepositoryProvider: const providerIdDetails = await loadRepositoryDetailsFromChannel(ci, metadata); _.update(parameters, mp.name, () => providerIdDetails.providerId); break; case decorators_1.MappedParameters.GitHubUrl: const urlDetails = await loadRepositoryDetailsFromChannel(ci, metadata); _.update(parameters, mp.name, () => urlDetails.url); break; case decorators_1.MappedParameters.GitHubUserLogin: const chatId = _.get(ci, "context.trigger.source.slack.user.id"); const resourceUser = await ci.context.graphClient.query({ name: "ResourceUser", variables: { id: chatId, }, }); _.update(parameters, mp.name, () => _.get(resourceUser, "ChatId[0].person.gitHubId.login")); break; case decorators_1.MappedParameters.SlackChannel: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.channel.id")); break; case decorators_1.MappedParameters.SlackChannelName: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.channel.name")); break; case decorators_1.MappedParameters.SlackChannelType: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.channel.type")); break; case decorators_1.MappedParameters.SlackUser: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.user.id")); break; case decorators_1.MappedParameters.SlackUserName: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.user.name")); break; case decorators_1.MappedParameters.SlackTeam: _.update(parameters, mp.name, () => _.get(ci, "context.trigger.source.slack.team.id")); break; } } if (parameters[mp.name] === undefined && mp.required === true) { missing.push(`Required mapped parameter '${mp.name}' missing`); } } return missing; } async function loadRepositoryDetailsFromChannel(ci, metadata) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u; // If owner and repo was provided, find the remaining mapped parameters from that const incomingParameters = ci.context.trigger.parameters; const ownerMp = metadata.mapped_parameters.find(mp => mp.uri === decorators_1.MappedParameters.GitHubOwner); const repoMp = metadata.mapped_parameters.find(mp => mp.uri === decorators_1.MappedParameters.GitHubRepository); const ownerParameter = !!ownerMp ? incomingParameters.find(p => p.name === ownerMp.name) : undefined; const repoParameter = !!repoMp ? incomingParameters.find(p => p.name === (repoMp === null || repoMp === void 0 ? void 0 : repoMp.name)) : undefined; if (!!ownerMp && !!repoMp && !!ownerParameter && !!repoParameter) { const repo = await ci.context.graphClient.query({ name: "RepositoryByOwnerAndName", variables: { owner: ownerParameter.value, name: repoParameter.value, }, }); if (((_a = repo === null || repo === void 0 ? void 0 : repo.Repo) === null || _a === void 0 ? void 0 : _a.length) === 1) { return { name: (_b = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _b === void 0 ? void 0 : _b.name, owner: (_c = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _c === void 0 ? void 0 : _c.owner, providerId: (_d = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _d === void 0 ? void 0 : _d.org.provider.providerId, providerType: (_e = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _e === void 0 ? void 0 : _e.org.provider.providerType, apiUrl: (_f = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _f === void 0 ? void 0 : _f.org.provider.apiUrl, url: (_g = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _g === void 0 ? void 0 : _g.org.provider.url, }; } } // Check if we want a list of repositories if (metadata.mapped_parameters.some(mp => mp.uri === decorators_1.MappedParameters.GitHubAllRepositories || mp.uri === decorators_1.MappedParameters.GitHubOwnerWithUser)) { const parameters = await ci.promptFor({ repo_slug: { description: "Slug of repository", displayName: "Repository (owner/repository)", pattern: /^\S+\/\S+$/, }, }, {}); const repo = await ci.context.graphClient.query({ name: "RepositoryByOwnerAndName", variables: { owner: parameters.repo_slug.split("/")[0], name: parameters.repo_slug.split("/")[1], }, }); if (!(repo === null || repo === void 0 ? void 0 : repo.Repo[0])) { throw new MappedParamterError("Repository", `Repository ${slack_messages_1.italic(parameters.repo_slug)} could not be found.`); } return { name: (_h = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _h === void 0 ? void 0 : _h.name, owner: (_j = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _j === void 0 ? void 0 : _j.owner, providerId: (_k = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _k === void 0 ? void 0 : _k.org.provider.providerId, providerType: (_l = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _l === void 0 ? void 0 : _l.org.provider.providerType, apiUrl: (_m = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _m === void 0 ? void 0 : _m.org.provider.apiUrl, url: (_o = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _o === void 0 ? void 0 : _o.org.provider.url, }; } else { const channelId = _.get(ci, "context.trigger.source.slack.channel.id"); const channels = await ci.context.graphClient.query({ name: "RepositoryMappedChannels", variables: { id: channelId, }, }); const repos = _.get(channels, "ChatChannel[0].repos") || []; if (!!repos) { if (repos.length === 1) { return { name: repos[0].name, owner: repos[0].owner, providerId: repos[0].org.provider.providerId, providerType: repos[0].org.provider.providerType, apiUrl: repos[0].org.provider.apiUrl, url: repos[0].org.provider.url, }; } else if (repos.length > 0) { const parameters = await ci.promptFor({ repo_id: { displayName: "Repository", type: { kind: "single", options: repos.map(r => ({ description: `${r.owner}/${r.name}`, value: r.id })), }, }, }, {}); const repo = repos.find(r => r.id === parameters.repo_id); return { name: repo.name, owner: repo.owner, providerId: repo.org.provider.providerId, providerType: repo.org.provider.providerType, apiUrl: repo.org.provider.apiUrl, url: repo.org.provider.url, }; } else { const parameters = await ci.promptFor({ repo_slug: { displayName: "Repository (owner/repository)", description: "Slug of repository", pattern: /^\S+\/\S+$/, }, }, {}); const repo = await ci.context.graphClient.query({ name: "RepositoryByOwnerAndName", variables: { owner: parameters.repo_slug.split("/")[0], name: parameters.repo_slug.split("/")[1], }, }); if (!(repo === null || repo === void 0 ? void 0 : repo.Repo[0])) { throw new MappedParamterError("Repository", `Repository ${slack_messages_1.italic(parameters.repo_slug)} could not be found.`); } return { name: (_p = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _p === void 0 ? void 0 : _p.name, owner: (_q = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _q === void 0 ? void 0 : _q.owner, providerId: (_r = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _r === void 0 ? void 0 : _r.org.provider.providerId, providerType: (_s = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _s === void 0 ? void 0 : _s.org.provider.providerType, apiUrl: (_t = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _t === void 0 ? void 0 : _t.org.provider.apiUrl, url: (_u = repo === null || repo === void 0 ? void 0 : repo.Repo[0]) === null || _u === void 0 ? void 0 : _u.org.provider.url, }; } } } return {}; } class MappedParamterError extends Error { constructor(title, msg) { super(msg); this.title = title; } } //# sourceMappingURL=mapCommand.js.map