Update slack command lambda to work with new slack app

This commit is contained in:
Brian Peiris 2020-07-22 14:15:09 -07:00
Родитель 5bc43f0166
Коммит 67f1623600
1 изменённых файлов: 100 добавлений и 52 удалений

Просмотреть файл

@ -7,12 +7,37 @@ const https = require('https');
const slackToken = process.env.slackToken;
const jenkinsToken = process.env.jenkinsToken;
const reticulumToken = process.env.reticulumToken;
const slackClientIdToken = process.env.slackClientIdToken;
const slackSecretToken = process.env.slackSecretToken;
let token;
let jtoken;
let rtoken;
let slackClientId;
let slackSecret;
function processOAuth(event, callback) {
const params = new URLSearchParams({
code: event.queryStringParameters.code,
client_id: slackClientId,
client_secret: slackSecret
});
https.get("https://slack.com/api/oauth.v2.access?" + params, (res) => {
if (res.statusCode < 400) {
callback(null, 'Slack App successfully registered', 'text');
} else {
callback(null, `Slack App failed to register (${res.statusCode})`, 'text');
}
})
}
function processEvent(event, callback) {
if (event.httpMethod === "GET" && "oauth" in event.queryStringParameters) {
processOAuth(event, callback);
return;
}
const params = qs.parse(event.body);
const requestToken = params.token;
if (requestToken !== token) {
@ -22,26 +47,33 @@ function processEvent(event, callback) {
const user = params.user_name;
const commandText = params.text;
const commandParts = commandText.split(" ");
const ciBaseUrl = 'https://ci-dev.reticulum.io/buildByToken/buildWithParameters';
if (commandText.startsWith("hab promote ")) {
const pkg = commandText.split(" ")[2].trim();
const url = `https:\/\/ci-dev.reticulum.io/buildByToken/buildWithParameters?job=hab-promote&PACKAGE=${pkg}&CHANNEL=stable&token=${jtoken}&SOURCE=${user}`;
https.get(url, (res) => { callback(null, "Promotion started. See #mr-push."); });
const pkg = commandParts[2].trim();
const url = `${ciBaseUrl}?job=hab-promote&PACKAGE=${pkg}&CHANNEL=stable&token=${jtoken}&SOURCE=${user}`;
https.get(url, () => { callback(null, "Promotion started. See #mr-push."); });
} else if (commandText.startsWith("hubs deploy ")) {
const buildVersion = commandText.split(" ")[2].trim();
const s3url = commandText.split(" ")[3].trim();
const url = `https:\/\/ci-dev.reticulum.io/buildByToken/buildWithParameters?job=hubs-deploy&S3URL=${s3url}&token=${jtoken}&SOURCE=${user}&BUILD_VERSION=${buildVersion}`;
https.get(url, (res) => { callback(null, "Hubs deploy started. See #mr-push."); });
const buildVersion = commandParts[2].trim();
const s3url = commandParts[3].trim();
const url = `${ciBaseUrl}?job=hubs-deploy&S3URL=${s3url}&token=${jtoken}&SOURCE=${user}&BUILD_VERSION=${buildVersion}`;
https.get(url, () => { callback(null, "Hubs deploy started. See #mr-push."); });
} else if (commandText.startsWith("spoke deploy ")) {
const buildVersion = commandText.split(" ")[2].trim();
const s3url = commandText.split(" ")[3].trim();
const url = `https:\/\/ci-dev.reticulum.io/buildByToken/buildWithParameters?job=spoke-deploy&S3URL=${s3url}&token=${jtoken}&SOURCE=${user}&BUILD_VERSION=${buildVersion}`;
https.get(url, (res) => { callback(null, "Spoke deploy started. See #mr-push."); });
const buildVersion = commandParts[2].trim();
const s3url = commandParts[3].trim();
const url = `${ciBaseUrl}?job=spoke-deploy&S3URL=${s3url}&token=${jtoken}&SOURCE=${user}&BUILD_VERSION=${buildVersion}`;
https.get(url, () => { callback(null, "Spoke deploy started. See #mr-push."); });
} else if (commandText.startsWith("ret deploy ")) {
const retVersion = commandText.split(" ")[2].trim();
const retPool = commandText.split(" ")[3].trim();
const url = `https:\/\/ci-dev.reticulum.io/buildByToken/buildWithParameters?job=ret-deploy&RET_VERSION=${retVersion}&RET_POOL=${retPool}&token=${jtoken}&SOURCE=${user}`;
https.get(url, (res) => { callback(null, "Reticulum deploy started. See #mr-push."); });
const retVersion = commandParts[2].trim();
const retPool = commandParts[3].trim();
const url = `${ciBaseUrl}?job=ret-deploy&RET_VERSION=${retVersion}&RET_POOL=${retPool}&token=${jtoken}&SOURCE=${user}`;
https.get(url, () => { callback(null, "Reticulum deploy started. See #mr-push."); });
} else if (commandText.startsWith("test ")) {
const msg = commandParts[2].trim();
const url = `${ciBaseUrl}?job=bp-test&MSG=${msg}&token=${jtoken}&SOURCE=${user}`;
https.get(url, () => { callback(null, "Test job started. See #mr-push."); });
} else if (commandText.startsWith("hubs support on")) {
const options = {
hostname: 'hubs.mozilla.com',
@ -54,7 +86,7 @@ function processEvent(event, callback) {
}
};
const req = https.request(options, (res) => { callback(null, "You are now available for Hubs support requests."); });
const req = https.request(options, () => { callback(null, "You are now available for Hubs support requests."); });
req.write("{ \"subscription\": { \"identifier\": \"" + user + "\" } }");
req.end();
} else if (commandText.startsWith("hubs support off")) {
@ -69,58 +101,74 @@ function processEvent(event, callback) {
}
};
const req = https.request(options, (res) => { callback(null, "You are now no longer available for Hubs support requests."); });
const req = https.request(options, () => {
callback(null, "You are now no longer available for Hubs support requests.");
});
req.write("{ }");
req.end();
} else {
callback(null, `Invalid command, try \`hab promote <package>, ret deploy <version> <pool>, hubs deploy <version> <s3 target>, hubs support on, hubs support off\``);
callback(null, (
`Invalid command, try \`hab promote <package>, ret deploy <version> <pool>, ` +
`hubs deploy <version> <s3 target>, hubs support on, hubs support off\``
));
}
}
function decrypt(kms, token, useFunctionName) {
return new Promise((resolve, reject) => {
const cipherText = { CiphertextBlob: Buffer.from(token, 'base64') };
if (useFunctionName) {
cipherText.EncryptionContext = {LambdaFunctionName: process.env.AWS_LAMBDA_FUNCTION_NAME}
}
kms.decrypt(cipherText, (err, data) => {
if (err) {
console.log('Decrypt error:', err);
reject(err);
return;
}
resolve(data.Plaintext.toString('ascii'));
});
});
}
function decryptWithFunctionContext(kms, token) {
return decrypt(kms, token, true);
}
exports.handler = (event, context, callback) => {
const done = (err, res) => callback(null, {
const done = (err, res, type) => callback(null, {
statusCode: err ? '400' : '200',
body: err ? (err.message || err) : JSON.stringify(res),
body: err ? (err.message || err) : type === "text" ? res : JSON.stringify(res),
headers: {
'Content-Type': 'application/json',
'Content-Type': type === "text" ? 'text/plain' : 'application/json',
},
});
if (token && jtoken && rtoken) {
if (token && jtoken && rtoken && slackClientId && slackSecret) {
// Container reuse, simply process the event with the key in memory
processEvent(event, done);
} else if (slackToken && slackToken !== '<slackToken>') {
const cipherText = { CiphertextBlob: new Buffer(slackToken, 'base64') };
const kms = new AWS.KMS();
kms.decrypt(cipherText, (err, data) => {
if (err) {
console.log('Decrypt error:', err);
return done(err);
}
token = data.Plaintext.toString('ascii');
const jcipherText = { CiphertextBlob: new Buffer(jenkinsToken, 'base64') };
kms.decrypt(jcipherText, (err, data) => {
if (err) {
console.log('Decrypt error:', err);
return done(err);
}
jtoken = data.Plaintext.toString('ascii');
const rcipherText = { CiphertextBlob: new Buffer(reticulumToken, 'base64') };
kms.decrypt(rcipherText, (err, data) => {
if (err) {
console.log('Decrypt error:', err);
return done(err);
}
rtoken = data.Plaintext.toString('ascii');
processEvent(event, done);
});
});
});
try {
// We want to use `await` for the decryption calls in order to avoid callback hell.
// You'd think we could just make the `handler` function async, but that changes the behavior
// with respect to AWS Lambda's execution context. We'd have to `await` processEvent all the way
// through. Instead, we still want to use `done` to signal completion, so we just wrap this section with
// an anonymous async iife in order to await the decryption calls.
(async () => {
const kms = new AWS.KMS();
token = await decryptWithFunctionContext(kms, slackToken);
jtoken = await decrypt(kms, jenkinsToken);
rtoken = await decrypt(kms, reticulumToken);
slackClientId = await decryptWithFunctionContext(kms, slackClientIdToken);
slackSecret = await decryptWithFunctionContext(kms, slackSecretToken);
processEvent(event, done);
})()
} catch(e) {
done(e);
}
} else {
done('Token has not been set.');
}