Sample Webhook Providers and Functions
This page contains examples of provider match and function scripts for some common providers.
GitHub
Provider Match Script- 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.
// 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).
// 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 script contains only one important line, the GITHUB.buildProjects function call which accepts the GitHub payload, query params and a boolean to indicate if missing streams should be created.
//Execute FLEXDEPLOY functions on an incoming webhook message def functionName = "BuildGitHub"; LOG.info("Running function: ${functionName}"); //Find and build projects from the payload //If the QUERY_PARAMS include a projectId that single project will be built instead //Lastly, the 3rd argument, when true, will create streams on the projects when the branch does not exist GITHUB.buildProjects(PAYLOAD, QUERY_PARAMS, true);
Function Script - Create SCM Instance and Project
This sample GitHub function script creates a FlexDeploy SCM instance and project with the assumption it will be triggered only from a GitHub create repo event. The function will first use the repository name to create an SCM Instance with the given name and git url. Next the function creates and configures the project to use the newly created SCM Instance. Finally the function will update the necessary properties on the project. In this example we are creating an EBS project. You will need to replace certain values in the function with specific Ids from your FlexDeploy environment.
import flexagon.fd.model.pojos.rest.topology.integrations.SCMInstancePojo; import flexagon.fd.model.pojos.rest.properties.PropertyValuePojo; import flexagon.fd.model.pojos.rest.project.*; import flexagon.fd.core.enums.SCMTypeEnum; import flexagon.fd.core.enums.ProjectTypeEnum; import flexagon.fd.model.pojos.rest.properties.PropertyValuePojo; //Execute FLEXDEPLOY functions on an incoming webhook message def functionName = "myFunction"; LOG.info("Running function: ${functionName}"); def CEMLI_ID= PAYLOAD.repository.name; LOG.info("CEMLI_ID: " + CEMLI_ID); def scmInstance = new SCMInstancePojo(); scmInstance.setInstanceCode(CEMLI_ID); scmInstance.setInstanceName(CEMLI_ID); scmInstance.setScmType("GIT"); def props = [new PropertyValuePojo("FDGIT_URL", PAYLOAD.repository.git_url), new PropertyValuePojo("FDGIT_USER", "flexagon9"), new PropertyValuePojo("FDGIT_PASSWORD", null, 12345L)]; scmInstance.setProperties(props); LOG.info(scmInstance.toString()); def scmId = FLEXDEPLOY.createSCMInstance(scmInstance); LOG.info(scmId.toString()); def proj = new ProjectPojo(); proj.setProjectName(CEMLI_ID); proj.setApplicationId(880708L); proj.setPartialDeployment(true); proj.setProjectType(ProjectTypeEnum.EBS); proj.setScmType(SCMTypeEnum.GIT); def buildInfo = new ProjectBuildInfo(); def deployInfo = new ProjectDeployInfo(); buildInfo.setWorkflowId(566261L); buildInfo.setInstanceId(566269L); deployInfo.setWorkflowId(566260L); deployInfo.setInstanceIds([566269L]); proj.setBuildInfo(buildInfo); proj.setDeployInfo(deployInfo); proj.setMainStreamName("master"); def projectSCMPojo = new ProjectSCMPojo(); def projectSCMConfig = new ProjectSCMConfig(); projectSCMConfig.setInstanceId(scmId); def scmConfigs = [new ProjectSCMConfigValue("BranchScript", "StreamName"), new ProjectSCMConfigValue("TagScript", "ProjectVersion"), new ProjectSCMConfigValue("SparseCheckoutFoldersScript", "\"XXHR\""), new ProjectSCMConfigValue("CheckoutFolderScript", "ProjectName")]; projectSCMConfig.setConfigValues(scmConfigs); projectSCMConfig.setSourceNumber(1); projectSCMPojo.setSources([projectSCMConfig]); proj.setScmConfiguration(projectSCMPojo); def projId = FLEXDEPLOY.createProject(proj); LOG.info(projId.toString()); LOG.info("Updating Project Properties."); def properties = [ new PropertyValuePojo("FDEBS_APPLICATION_SHORT_NAME", "XXHR"), new PropertyValuePojo("FDEBS_JAVA_ROOT_SOURCE_DIR", "java"), new PropertyValuePojo("FDEBS_JAVA_ROOT_DESTINATION_DIR", "\$XXHR_TOP/java"), new PropertyValuePojo("FDEBS_LOAD_JAVA_ROOT_DESTINATION_DIR", "\$XXHR_TOP/java"), new PropertyValuePojo("FDEBS_AOL_ROOT_DESTINATION_DIR", "\$XXHR_TOP/patch/115/import/us"), new PropertyValuePojo("FDEBS_FILE_PERMISSIONS", "755"), new PropertyValuePojo("FD_PARTIAL_FILE_EXCLUDES", "*/misc/*") ]; FLEXDEPLOY.updateProjectProperties(projId, properties);
Bitbucket
Provider Match Script- 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.
LOG.fine("Evaluating Bitbucket for incoming message"); def match = false; //Pass this token in a query parameter called 'token' def bitbucketToken = 'REPLACE_ME'; // 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(bitbucketToken)) { 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.
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.
//Execute FLEXDEPLOY functions on an incoming webhook message def functionName = "BitbucketBuild"; LOG.info("Running function: ${functionName}"); //Find and build projects from the payload //If the QUERY_PARAMS include a projectId that single project will be built instead //The 3rd argument indicates to create any missing streams on the projects //The last two arguments are for additional Bitbucket API calls to get change log information BITBUCKET.buildProjects(PAYLOAD, null, true, BITBUCKET_USER ,BITBUCKET_PASSWORD);
Azure
@Since 5.4.0.4
Provider Match Script- Token
This provider match script for Azure validates based on a token sent as a query parameter.
// perform checks and functions to ensure an incoming message is valid and matches this provider LOG.fine("Evaluating Azure for incoming message"); def match = false; // validating based on token def token = QUERY_PARAMS.get('token'); if (token) { // It's recommended to store the token as an encrypted provider property but it is not done here for completeness sake LOG.fine("Token: ${token}"); if (token.equals('custom_token')) { match = true; } } LOG.fine("Azure provider is a match: ${match}"); return match;
Function Script - Build Projects
This function finds projects affected by a push from Azure Repos, gets the change logs, and builds relevant projects. The Azure push event includes branch created/deleted events, but Azure allows filtering webhooks to send by branch. In this case, webhooks will be received from Azure only for push events from main branches.
Azure Repos does not send changed files in the push event, but that information is available via their commits API. The getChangeLogs function will use that API and as such requires a valid user/personal access token to make the API call. It is recommended that provider properties are created for passing to this function.
AZURE.buildProjects
the AZURE object also includes a simplified buildProjects method like the other providers above which can be used in replacement of the below sample.
def functionName = "azurePush"; LOG.info("Running function: ${functionName}"); def pollingResult = AZURE.getChangeLogs(PAYLOAD, AZURE_USER, AZURE_TOKEN, true); def branch = PAYLOAD.resource.refUpdates[0].name - 'refs/heads/'; def repoName = PAYLOAD.resource.repository.name; def projects = FLEXDEPLOY.findProjectsForChange(repoName, branch, pollingResult); if (projects.size() == 0) { LOG.info("No projects found for change"); } else { for (def project in projects) { def streamId = FLEXDEPLOY.findStreamId(project, branch); LOG.info("Building project ${project}"); FLEXDEPLOY.buildProject(streamId, project, "DEV", pollingResult); } }
Jira
Provider Match Script- 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.
// 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.
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;
Microsoft Teams
See the Teams plugin configuration pages for information on how to set up FlexDeploy and Microsoft Teams.
Configuring FlexDeploy for Teams Webhook Operations
and
Configuring FlexDeploy for Teams Operations using User Credentials
This page only shows how to set up the Webhooks, not how to set up the integration itself.
Provider Match Script - Expected Teams Headers - Available in 5.5.0.2
This provider match script for Microsoft Teams validates that it is a message from a card that FlexDeploy created.
Security checks are performed after match and before completing processing of approval tasks.
return HTTP_HEADERS.containsKey("flex-sync-webhook") && "TeamsTask".equals(HTTP_HEADERS.get("flex-sync-webhook"));
Function Script - Update Tasks from Teams Messages - Available in 5.5.0.2
Microsoft Teams Incoming Webhooks are validated and processed Synchronously. Behind the scenes, the JWT token is validated and the message is checked for unauthorized modification. The user that clicked the button is matched to a FlexDeploy user account behind the scenes using the JWT Token found in the HTTP Headers. If the matched user is authorized to approve/reject the linked FlexDeploy task, the task is approved or rejected and the card is updated to indicate that the task was processed.
MICROSOFTTEAMS.updateTask(QUERY_PARAMS,HTTP_HEADERS,"<Teams Messaging Integration Account Code>");
- style