Sample Webhook Providers and Functions

This page contains examples of provider match and function scripts for some common providers. 

GitHub

Provider Match - Hmac Secret

This provider match script for GitHub validates based on the secret configured on your GitHub webhook. This script is largely based around GitHub's Hmac encryption

GitHub Provider Match Script
// perform checks and functions to ensure an incoming message is valid and matches this provider
LOG.fine("Evaluating GitHub for incoming message");
def match = false;
def gitHubSecret = 'REPLACE_ME';

// validating based on GitHub secret
if (HTTP_HEADERS['user-agent'] && HTTP_HEADERS['user-agent'].toLowerCase().contains('github-hookshot'))
{
    //generate hmac string, be sure to replace with your github secret
    def HMAC_RESULT = HMAC.generateHmacSHA1(FLX_PRISTINE_PAYLOAD, gitHubSecret);
    def RECEIVED_HMAC = HTTP_HEADERS['x-hub-signature'];
	
    match = RECEIVED_HMAC && RECEIVED_HMAC.contains(HMAC_RESULT);
}

LOG.fine("GitHub provider is a match: ${match}");
return match;

Function Script - Manage Streams

GitHub offers the flexibility to manage branch events either separately or along with a push event. The following implementation assumes a GitHub webhook is created for branch events, managed separately from push events. This script manages FlexDeploy streams based on GitHub branch events (i.e. delete, create).

GitHub Manage FlexDeploy Streams
// get necessary information from GitHub headers, payload, and query parameters
def event = HTTP_HEADERS["x-github-event"];
LOG.info("Webhook triggered by ${event} event");
def projectId = QUERY_PARAMS.projectId;
def repoName = PAYLOAD.repository.name;
def branch = PAYLOAD.ref;

LOG.info("Running GitHub function for: ${repoName}, ${branch}");

// if projectId was passed, assume event is only associated with this project
if (projectId)
{
  //the query param received is a string, we need a long
  def lProjectId = new Long(projectId);

  if (event.equals("create"))
  {
    LOG.fine("Creating stream ${branch} for project ${projectId}");
    def stream = FLEXDEPLOY.createStream(lProjectId, branch);

    LOG.setMessage("Successfully created stream ${branch} for project ${projectId}");
  }
  else if (event.equals("delete"))
  {
    LOG.fine("Inactivating stream ${branch} for project ${projectId}");
    FLEXDEPLOY.inactivateStream(lProjectId, branch);

    LOG.setMessage("Successfully inactivated stream ${branch} for project ${projectId}");
  }
}
// get all project ids for projects affected by this SCM change
else
{
  def projects = FLEXDEPLOY.findProjectsForChange(repoName, null, null);
  if (event.equals("create"))
  {
    LOG.fine("Creating streams with name ${branch} for projects ${projects}");
    for (def project in projects)
    {
      def stream = FLEXDEPLOY.createStream(project, branch);
      LOG.fine("Successfully created stream ${stream}");
    }
    LOG.setMessage("Successfully created stream ${branch} for projects ${projects}");
  }
  else if (event.equals("delete"))
  {
    LOG.fine("Inactivating stream ${branch} for projects ${projects}");
    for (def project in projects)
    {
      FLEXDEPLOY.inactivateStream(project, branch);
    }
    LOG.setMessage("Successfully inactivated stream ${branch} for projects ${projects}");
  }
}


LOG.info("Successfully ran GitHub function for: ${repoName}, ${branch}");

Function Script - Build Projects

This sample GitHub function script manages FlexDeploy builds with the assumption it will be triggered only from a GitHub push event. The function first parses the payload for change logs to send to the buildProject function. Using this function isn't necessary, but it saves time during the build, as this step can be skipped. Then, it will either find all FlexDeploy projects affected by this event or use the projectId query parameter, and initiate a build for each project.  It will also create the stream if it does not already exist on the project.

GitHub Build Project(s) on Push
import flexagon.ff.common.core.exceptions.FlexNotFoundException;

def repoName = PAYLOAD.repository.name;
def branch = PAYLOAD.ref - 'refs/heads/';
def projectId = QUERY_PARAMS.projectId;
def streamId;
 
LOG.info("Running GitHub push function: ${repoName}, ${branch}");
 
def logs = GITHUB.getChangeLogs(PAYLOAD);
LOG.info("Building projects for revision: ${logs.getRevision()}");

// if projectId is passed, then we assume one project per repo and build that project
if (projectId)
{
  //the query param received is a string, we need a long
  def lProjectId = new Long(projectId);
 
  streamId = getStreamId(lProjectId, branch);
  FLEXDEPLOY.buildProject(streamId, lProjectId, logs);
  LOG.setMessage("Submitted project ${lProjectId} for build");
}
// otherwise find and build affected projects
else
{   
  // get all project ids for projects affected by this SCM change
  def projects = FLEXDEPLOY.findProjectsForChange(repoName, branch, logs);
 
  if (projects.size() == 0)
  {
    LOG.info('No projects found for change');
    LOG.setMessage("No projects found for change");
  }
  else
  {
    LOG.info("Building projects : ${projects}");
    // get remaining parameters and build each project
    for (def project in projects)
    {
      streamId = getStreamId(project, branch);
      def requestId = FLEXDEPLOY.buildProject(streamId, project, logs);
      LOG.info("Successfully submitted build request ${requestId} for ${project}.");
    }
    LOG.setMessage("Submitted projects ${projects} for build");
      
  }
}

//this will create the stream if it doesnt exist
def getStreamId(projectId, branch) {
  def streamId;
  try {
    streamId = FLEXDEPLOY.findStreamId(projectId,branch);
  }
  catch(FlexNotFoundException fnfe) {
    streamId = FLEXDEPLOY.createStream(projectId,branch);
    LOG.info("Added stream ${branch} to project ${projectId}")
  }
  return streamId;
}

Bitbucket

Provider Match - Token

This sample provider match script for Bitbucket validates based on a token passed in the query parameters. Bitbucket server offers hmac encryption similar to Github but Bitbucket Cloud does not at the time of this writing.

Bitbucket Provider Match Script
LOG.fine("Evaluating Bitbucket for incoming message");
def match = false;
 
// validating based on token and user agent headers
def userAgent = HTTP_HEADERS.get('user-agent');
def token = QUERY_PARAMS.get('token');
 
if (token && userAgent)
{
  //validate token matches what we expect in FlexDeploy
  //It's recommended to store the token as an encrypted provider property but it is not done here for completeness sake
  LOG.fine("Using token ${token} and user agent ${userAgent}");
  if (token.equals('your_custom_token'))
  {
    if(userAgent.toLowerCase().equals('bitbucket-webhooks/2.0'))
    {
      match = true;
    }
  }
}
 
LOG.fine("Bitbucket provider is a match: ${match}");
return match;

Function Script - Build Projects and Manage Streams

This sample Bitbucket function script manages FlexDeploy builds and project streams with the assumption it will be triggered from a Bitbucket push event. Branch creation and deletion all falls under the push event in Bitbucket. This function will create and inactivate FlexDeploy streams as branches are created and deleted, and build all affected projects on a push.

Bitbucket does not send changed files in the push event, but that information is available via the diffstat API. The getChangeLogs function will use that API and as such requires a valid user/password to make the api call. It is recommended that BITBUCKET_USER and BITBUCKET_PASSWORD provider properties are created for passing to this function.


Bitbucket Push Function Script
def repoName = PAYLOAD.repository.full_name;
def branch;
def isNewBranch = PAYLOAD.push.changes[0].old == null;
def isDeletedBranch = PAYLOAD.push.changes[0].new == null;
def streamCache = [:];

if (isDeletedBranch)
{
  branch = PAYLOAD.push.changes[0].old.name;
  LOG.info("Running BitbucketPush function: ${repoName}, ${branch}");

  LOG.info("Branch ${branch} has been deleted. Inactivating all associated streams");

  def logs = BITBUCKET.getChangeLogs(PAYLOAD, BITBUCKET_USER, BITBUCKET_PASSWORD);

  // if projectId is passed, then we assume one project per repo and only that project was affected
  if (QUERY_PARAMS.projectId)
  {
    projects = QUERY_PARAMS.projectId;
  }
  else
  {
    projects = FLEXDEPLOY.findProjectsForChange(repoName, branch, logs);
  }

  for (def project in projects)
  {
    LOG.info("Inactivating stream ${branch} for project ${project}");
    FLEXDEPLOY.inactivateStream(project, branch);
  }
}
else
{
  branch = PAYLOAD.push.changes[0].new.name;
  LOG.info("Running BitbucketPush function: ${repoName}, ${branch}");

  if (isNewBranch)
  {
    streamCache = tryCreateStreams(repoName, branch);
  }

  // find the changed files for the push
  def logs = BITBUCKET.getChangeLogs(PAYLOAD, BITBUCKET_USER, BITBUCKET_PASSWORD);

  LOG.info(String.format("Building projects for revision: %s", logs.getRevision()));

  def projects;

  // if projectId is passed, then we assume one project per repo and build that project
  if (QUERY_PARAMS.projectId)
  {
    projects = QUERY_PARAMS.projectId;
  }
  else
  {
    projects = FLEXDEPLOY.findProjectsForChange(repoName, branch, logs);
  }

  LOG.info(String.format("Building projects : %s", projects));

  if (projects.size() == 0) 
  {
    LOG.info("No projects found for change");
  }
  else 
  {  
    for (def project in projects)
    {
      LOG.info("Building project ${project}");
      
      def streamId = findStreamId(project, branch, streamCache, isNewBranch);
      def requestId = FLEXDEPLOY.buildProject(streamId, project, logs);
      LOG.info("Successfully submitted build request ${requestId} for ${project}.");
    }
  }
}

// if this commit was on a new branch, get from cache, otherwise check on server
def findStreamId(projectId, streamName, streamCache, isNewBranch) 
{
  def streamId = null;
  if (isNewBranch) 
  {
    streamId = streamCache[projectId];
  }
  else 
  {
    streamId = FLEXDEPLOY.findStreamId(projectId, streamName);
  }
  return streamId;
}

// this event is only received once (on creation of a new branch)
def tryCreateStreams(repoName, branch) 
{
  def allProjects = FLEXDEPLOY.findProjectsForChange(repoName, null, null);
  def streams = [:];
  
  for (def project in allProjects)
  {
    LOG.info(String.format("Creating stream %s on project %s", branch, project));
    def streamId = FLEXDEPLOY.createStream(project, branch);
    streams[project] = streamId;
  }
  
  return streams;
}

Jira

Provider Match - Token

This example provider match script for Jira validates based on a secret token passed as a query parameter. This token is purely convention and has no strict enforcing in Jira.

Jira Provider Match Script
// perform checks and functions to ensure an incoming message is valid and matches this provider
LOG.fine("Evaluating Jira for incoming message");
def match = false;
 
// validating based on token and user agent headers
def userAgent = HTTP_HEADERS.get('user-agent');
def token = QUERY_PARAMS.get('token');
 
if (token && userAgent)
{
  //validate token matches what we expect in FlexDeploy
  //It's recommended to store the token as an encrypted provider property but it is not done here for completeness sake
  LOG.fine("Using token ${token} and user agent ${userAgent}");
  if (token.equals('your_custom_token'))
  {
    if(userAgent.toLowerCase().equals('atlassian webhook http client'))
    {
      match = true;
    }
  }
}
 
LOG.fine("Jira provider is a match: ${match}");
return match;

Function Script - Create Package

This Jira function creates a project package from the issue key and description. The particular function assumes the Jira project name is the same as the FlexDeploy project name, although an issue could be mapped to a FlexDeploy project in a number of ways. You may want to trigger this package creation whenever a Jira issue is created, or when an issue goes into development.

Create Package on Issue Started
import flexagon.ff.common.core.exceptions.FlexCheckedException;

def functionName = "createPackage";
LOG.info("Running function: ${functionName}");

def issueKey = PAYLOAD.issue.key;
def summary = PAYLOAD.issue.fields.summary;
def projectName = PAYLOAD.issue.project.name;

tryCreatePackage(projectName, issueKey, summary);

def tryCreatePackage(name, description) 
{
  try 
  {
    LOG.info("Attempting to create package ${name}.");
	def projectId = FLEXDEPLOY.findProjectId(projectName);
    def packageId = FLEXDEPLOY.createPackage(projectId, name, description, []);
    LOG.info("Created package ${name} with id ${packageId} for project ${projectName}");
  }
  catch (FlexCheckedException e)
  {
    // this exception indicates the package already exists
    if (e.getMessage().contains('JBO-FDML-2700'))
	{
      LOG.setMessage("Package ${name} already exists.");
    }
    else 
    {
      throw e;
    }
  }
}

Slack

Provider Match Script - Hmac Secret

This sample provider match script for Slack validates based on the Slack Secret, which is an hmac encryption of version, timestamp, and payload concatenated together.

// perform checks and functions to ensure an incoming message is valid and matches this provider
LOG.fine("Evaluating Slack for incoming message");

def match = false;
def slackSecret = 'REPLACE_ME';

// validating based on slack secret
def slackSig = HTTP_HEADERS.get('x-slack-signature');

if (slackSig)
{
    LOG.fine("Validating Slack provider with signature ${slackSig}");

    def version = slackSig.split('=')[0];
    def slackTimestamp = HTTP_HEADERS.get('x-slack-request-timestamp');
    def hmacInput = version.concat(':').concat(slackTimestamp).concat(':').concat(FLX_PRISTINE_PAYLOAD);

	//be sure to replace with your slack secret
    //It's recommended to store the token as an encrypted provider property but it is not done here for completeness sake
    def hmac = HMAC.generateHmacSHA256(hmacInput, slackSecret);
    match = slackSig.contains(hmac);
}

LOG.fine("Slack provider is a match: ${match}");
return match;