Skip to content

Instantly share code, notes, and snippets.

@vvondra
Created November 17, 2017 09:22
Show Gist options
  • Save vvondra/945c7d2c25f01ba9ead374b7da52cd6a to your computer and use it in GitHub Desktop.
Save vvondra/945c7d2c25f01ba9ead374b7da52cd6a to your computer and use it in GitHub Desktop.
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Resources" : {
"GithubServiceTopic" : {
"Type" : "AWS::SNS::Topic",
"Properties" : {
"DisplayName" : "Topic for Github pull request integration",
"Subscription" : [
{
"Endpoint" : { "Fn::GetAtt" : [ "GithubValidatePullRequest", "Arn" ] },
"Protocol" : "lambda"
}
],
"TopicName" : "GithubServiceTopic"
},
"DependsOn" : [ "InvokeGithubValidationLambdaFn" ]
},
"InvokeGithubValidationLambdaFn" : {
"Type" : "AWS::Lambda::Permission",
"Properties" : {
"Action" : "lambda:InvokeFunction",
"FunctionName" : { "Fn::GetAtt" : [ "GithubValidatePullRequest", "Arn" ] },
"Principal" : "sns.amazonaws.com"
}
},
"GithubServiceUser" : {
"Type" : "AWS::IAM::User",
"Properties" : {
"Path" : "/",
"Policies" : [
{
"PolicyName" : "PublishGithubSnsEvents",
"PolicyDocument" : {
"Version" : "2012-10-17",
"Statement" : [
{
"Action" : [
"sns:Publish"
],
"Resource" : [
{ "Ref" : "GithubServiceTopic" }
],
"Effect" : "Allow"
}
]
}
}
]
}
},
"GithubServiceUserKey" : {
"Type" : "AWS::IAM::AccessKey",
"Properties" : {
"Status" : "Active",
"UserName" : { "Ref" : "GithubServiceUser" }
}
},
"DevToolLambdaExecutor" : {
"Type" : "AWS::IAM::Role",
"Properties" : {
"Path" : "/lambda/",
"Policies" : [ {
"PolicyName" : "DevToolLambdaExecutor",
"PolicyDocument" : {
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource" : "arn:aws:logs:*:*:*"
}
]
}
} ],
"AssumeRolePolicyDocument" : {
"Version" : "2012-10-17",
"Statement" : [ {
"Sid" : "",
"Effect" : "Allow",
"Principal" : {
"Service" : "lambda.amazonaws.com"
},
"Action" : "sts:AssumeRole"
} ]
}
}
},
"GithubValidatePullRequest" : {
"Type" : "AWS::Lambda::Function",
"Properties" : {
"Code" : {
"S3Bucket" : "%TEMPLATES-BUCKET%",
"S3Key" : { "Fn::Join" : [ "/", [
"%TEMPLATE-VERSION%",
"githubValidatePullRequest.zip"
] ] }
},
"Description" : "Creates to validate pull requests reacting on events coming from Github",
"Handler" : "validatePullRequest.handler",
"MemorySize" : "128",
"Role" : { "Fn::GetAtt" : [ "DevToolLambdaExecutor", "Arn" ] },
"Runtime" : "nodejs4.3",
"Timeout" : "60"
}
}
},
"Outputs" : {
"GithubServiceUser" : {
"Description" : "Github service user",
"Value" : { "Ref" : "GithubServiceUser" }
},
"GithubServiceUserKeyId" : {
"Description" : "Github user access key ID",
"Value" : { "Ref" : "GithubServiceUserKey" }
},
"GithubServiceUserSecretAccessKey" : {
"Description" : "Github user secret access key",
"Value" : {
"Fn::GetAtt" : ["GithubServiceUserKey", "SecretAccessKey"]
}
},
"GithubSNSTopicARN" : {
"Description" : "SNS topic for Github notifications ARN",
"Value" : { "Ref" : "GithubServiceTopic" }
}
}
}
var GitHubApi = require('github');
var github = new GitHubApi({
version: '3.0.0'
});
/*
Adding to github
Under Repository admin, add as new *Service*, type *Amazon SNS*
All configuration (access key, secret, and SNS ARN come from CloudFormation)
The only catch is events have by default only "push" event enabled, so you need to do this API trickery:
curl -H "Authorization: token <oauthtoken>" https://api.github.com/repos/company/repo/hooks
curl -X PATCH -d '{"add_events": ["pull_request"]}' -H "Authorization: token <oauthtoken>" https://api.github.com/repos/company/repo/hooks/<id from first request>
curl -X PATCH -d '{"remove_events": ["push"]}' -H "Authorization: token <oauthtoken>" https://api.github.com/repos/company/repo/hooks/<id from first request>
curl -H "Authorization: token <oauthtoken>" https://api.github.com/repos/company/repo/hooks
*/
exports.handler = function(event, context) {
var githubEvent = event.Records[0].Sns.Message;
var eventAttributes = event.Records[0].Sns.MessageAttributes;
console.log('Received GitHub event:', githubEvent);
console.log('Received GitHub event attributes:', eventAttributes);
if (!eventAttributes.hasOwnProperty('X-Github-Event') ||
eventAttributes['X-Github-Event']['Value'] !== 'pull_request') {
console.log("Not a pull_request event");
// Not a pull request event
context.succeed("Not pull_request event");
return;
}
githubEvent = JSON.parse(githubEvent);
if (
githubEvent.action !== 'opened' &&
githubEvent.action !== 'synchronize'
) {
console.log("Event action not opened or synchronize");
context.succeed("Event action not opened or synchronize");
return;
}
if (githubEvent.pull_request.state !== 'open') {
console.log("PR is already closed");
context.succeed("PR is already closed");
return;
}
// Authenticate to comment on the issue
github.authenticate({
type: 'oauth',
token: 'mytoken' // can only change PR statuses, no point in trying to abuse
});
var prData = githubEvent.pull_request;
// Let's do the validations
var isValid = true;
var isAwesome = true;
var description = [];
if (prData.additions > 900) {
isValid = false;
description.push("This is a big PR, what about splitting it up?");
}
if (prData.title.toUpperCase().indexOf('NO_TICKET') !== 0) {
if (!prData.body || prData.body.indexOf("maycompany.atlassian") == -1) {
isAwesome = false;
description.push("I could use that link to JIRA.");
}
if (prData.title.indexOf("LOGI-") !== 0) {
isValid = false;
description.push("I can't see the ticket ID in the title.");
}
}
if (isValid) {
if (prData.deletions > 1500) {
description.push("Do they call you code slayer?");
} else if (prData.additions - prData.deletions < -500) {
description.push("I thought you're paid for writing code, not deleting it.")
}
}
if (isValid && description.length == 0) {
console.log("PR is valid");
if (isAwesome) {
description.push('Your pull request is awesome!');
} else {
description.push('Looks alright.');
}
}
var statusMessage = description.join(" ");
console.log("Posting status message " + statusMessage);
github.statuses.create({
user: githubEvent.repository.owner.login,
repo: githubEvent.repository.name,
sha: prData.head.sha,
state: isValid ? 'success' : 'failure',
description: statusMessage,
context: "Tip of the Day™"
}, context.done);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment