files: // Process the github webhook and trigger tests or builds depending on the payload data
  
  node {
      stage "Check PR Action"
          // Parse json payload
          def test_key = "[test]"
          def slurper = new groovy.json.JsonSlurper()
          def webhook = slurper.parseText(payload)
          def trigger = ""
  
          // The following are the actions we should test:
          // "opened", "reopened", "synchronize"
          // We should build if the action is "closed" and the "merged" flag is true
          // The "edited" action is when a comment is edited, we don't care about that.
  
          // Additionally, to support re-testing via a comment made on the PR, we should test
          // on the following issue-only actions:
          // created, edited
          // These will requier some additional verification, as tests should only commence if
          // the comment was made on an open pull request and includes a certain phrase.
  
          def action = webhook.action
          echo "Webhook payload action: ${action}"
          if (action == "opened" || action == "reopened" || action == "synchronize") {
              echo "Pull request has been opened or modified, testing..."
              trigger = "test"
          } else if (action == "closed" && webhook.pull_request.merged) {
              echo "Pull request has been merged, running builds..."
              trigger = "build"
          } else if (action == "created" || action == "edited") {
              if (webhook.issue && webhook.issue.containsKey("pull_request")) {
                  body = webhook.comment.body
                  if (body.toLowerCase().contains(test_key)) {
                      echo "Pull request comment contains '${test_key}', running tests..."
                      trigger = "test"
                  } else {
                      echo "Pull request comment does not contain '${test_key}'. Ignoring..."
                  }
              } else {
                  echo "Comment made on issue, not pull request. Ignoring..."
              }
          }
          echo "Trigger: ${trigger}"
          // These variables must be nullified as they are not serializable
          // See http://stackoverflow.com/questions/37864542/jenkins-pipeline-notserializableexception-groovy-json-internal-lazymap
          slurper = null
          webhook = null
  
      if (trigger != "") {
          if (trigger == "test") {
              stage "Test Changes"
                  echo "Starting Tests"
                  openshiftBuild buildConfig: 'openshift-tools-test', env: [[ name: 'GITHUB_WEBHOOK_PAYLOAD', value: payload ], [ name: 'BUILD_URL', value: env.BUILD_URL ]], showBuildLogs: true
          } else if (trigger == "build") {
              stage "Build RPMS"
                  echo "Starting Build"
                  // TODO this is not yet implemented
              stage "Deploy Updates"
                  echo "Deploying updated RPMs"
                  // TODO this is not yet implemented
          } else {
              echo "Trigger ${trigger} not recognized"
              currentBuild.result = 'FAILURE'
          }
      } else {
          echo "Webhook action, ${action}, does not justify running any jobs."
          currentBuild.result = 'SUCCESS'
      }
  }
  
files: jenkins/Jenkinsfile
files: // Process the github webhook and trigger tests or builds depending on the payload data
  
  node {
      stage "Check PR Action"
          def slurper = new groovy.json.JsonSlurper()
          def webhook = slurper.parseText(payload)
          def trigger = ""
          // The following are the actions we should test:
          // "opened", "reopened", "synchronize"
          // We should build if the action is "closed" and the "merged" flag is true
          // The "edited" action is when a comment is edited, we don't care about that.
          echo webhook.action
          if (webhook.action == "opened" || webhook.action == "reopened" || webhook.action == "synchronize") {
              trigger = "test"
          } else if (webhook.action == "closed" && webhook.pull_request.merged) {
              trigger = "build"
          }
          // These variables must be nullified as they are not serializable
          // See http://stackoverflow.com/questions/37864542/jenkins-pipeline-notserializableexception-groovy-json-internal-lazymap
          slurper = null
  
      if (trigger != "") {
          if (trigger == "test") {
              stage "Test Changes"
                  echo "Starting Tests"
                  openshiftBuild buildConfig: 'openshift-tools-test', env: [[ name: 'GITHUB_WEBHOOK_PAYLOAD', value: payload ], [ name: 'BUILD_URL', value: env.BUILD_URL ]], showBuildLogs: true
          } else if (trigger == "build") {
              stage "Build RPMS"
                  echo "Starting Build"
                  // TODO this is not yet implemented
  						stage "Deploy Updates"
  								echo "Deploying updated RPMs"
                  // TODO this is not yet implemented
          } else {
              echo "Trigger ${trigger} not recognized"
              currentBuild.result = 'FAILURE'
          }
      } else {
          echo "Pull request action, ${webhook.action}, does not justify running any jobs."
          currentBuild.result = 'SUCCESS'
      }
  }
  
files: jenkins/Jenkinsfile
files: """ Run all tests for openshift-tools repository """
  # This script expects a single environment variable to be defined with a myriad of json data:
  #    GITHUB_WEBHOOK_PAYLOAD
  #
  # The data expected from this payload is that generated by the pull reqeust edit webhook,
  # defined here:
  #    https://developer.github.com/v3/activity/events/types/#pullrequestevent
  #
  # The script will parse the JSON and define a list of environment variables for consumption by
  # the validation scripts. Then, each *.py file in ./validators/ (minus specified exclusions)
  # will be run. The list of variables defined is below:
  #  Github stuff
  #    PRV_TITLE         Title of the pull request
  #    PRV_BODY          Description of the pull request
  #    PRV_PULL_ID       ID of the pull request
  #    PRV_PULL_URL      URL of the pull request
  #
  #  Base info
  #    PRV_BASE_SHA      SHA of the target being merged into
  #    PRV_BASE_REF      ref (usually branch name) of the base
  #    PRV_BASE_LABEL    Base label
  #    PRV_BASE_NAME     Full name of the base 'namespace/reponame'
  #
  #  Remote (or "head") info
  #    PRV_REMOTE_SHA    SHA of the branch being merged
  #    PRV_REMOTE_REF    ref (usually branch name) of the remote
  #    PRV_REMOTE_LABEL  Remote label
  #    PRV_REMOTE_NAME   Full name of the remote 'namespace/reponame'
  #    PRV_CURRENT_SHA   The SHA of the merge commit
  #
  
  import os
  import json
  import subprocess
  import sys
  import requests
  
  EXCLUDES = [
      "common.py",
      ".pylintrc"
  ]
  # The relative path to the testing validator scripts
  VALIDATOR_PATH = "jenkins/test/validators/"
  # The github API base url
  GITHUB_API_URL = "https://api.github.com"
  # The string to accept in PR comments to initiate testing by a whitelisted user
  TEST_STRING = "[test]"
  
  def run_cli_cmd(cmd, exit_on_fail=True):
      '''Run a command and return its output'''
      proc = subprocess.Popen(cmd, bufsize=-1, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
                              shell=False)
      stdout, stderr = proc.communicate()
      if proc.returncode != 0:
          print "Unable to run " + " ".join(cmd) + " due to error: " + stderr
          if exit_on_fail:
              sys.exit(proc.returncode)
          else:
              return False, stdout
      else:
          return True, stdout
  
  def assign_env(pull_request):
      '''Assign environment variables base don github webhook payload json data'''
      # Github environment variables
      os.environ["PRV_TITLE"] = pull_request["title"]
      # Handle pull request body in case it is empty
      os.environ["PRV_BODY"] = (pull_request["body"] if pull_request["body"] is not None else "")
      os.environ["PRV_PULL_ID"] = pull_request["number"]
      os.environ["PRV_URL"] = pull_request["url"]
  
      # Base environment variables
      base = pull_request["base"]
      os.environ["PRV_BASE_SHA"] = base["sha"]
      os.environ["PRV_BASE_REF"] = base["ref"]
      os.environ["PRV_BASE_LABEL"] = base["label"]
      os.environ["PRV_BASE_NAME"] = base["repo"]["full_name"]
  
      # Remote environment variables
      head = pull_request["head"]
      os.environ["PRV_REMOTE_SHA"] = head["sha"]
      os.environ["PRV_REMOTE_REF"] = head["ref"]
      os.environ["PRV_REMOTE_LABEL"] = head["label"]
      os.environ["PRV_REMOTE_NAME"] = head["repo"]["full_name"]
  
  def merge_changes(pull_request):
      """ Merge changes into current repository """
      pull_id = pull_request["number"]
  
      run_cli_cmd(['/usr/bin/git', 'fetch', "--tags", "origin", "+refs/head/*:refs/remotes/origin/*",
                   "+refs/pull/*:refs/remotes/origin/pr/*"])
      _, output = run_cli_cmd(['/usr/bin/git', 'rev-parse',
                               'refs/remotes/origin/pr/'+pull_id+'/merge^{commit}'])
      current_rev = output.rstrip()
      run_cli_cmd(['/usr/bin/git', 'config', 'core.sparsecheckout'], exit_on_fail=False)
      run_cli_cmd(['/usr/bin/git', 'checkout', '-f', current_rev])
      os.environ["PRV_CURRENT_SHA"] = current_rev
  
  def run_validators():
      """ Run all test validators """
      failure_occured = False
      validators = [validator for validator in os.listdir(VALIDATOR_PATH) if
                    os.path.isfile(os.path.join(VALIDATOR_PATH, validator))]
      for validator in validators:
          skip = False
          for exclude in EXCLUDES:
              if validator == exclude:
                  skip = True
          if skip:
              continue
          validator_abs = os.path.join(VALIDATOR_PATH, validator)
          executer = ""
          _, ext = os.path.splitext(validator)
          if ext == ".py":
              executer = "/usr/bin/python"
          elif ext == ".sh":
              executer = "/bin/sh"
          # If the ext is not recongized, try to just run the file
          print "Executing validator: " + executer + " " + validator_abs
          success, output = run_cli_cmd([executer, validator_abs], exit_on_fail=False)
          print output
          if not success:
              print validator + " failed!"
              failure_occured = True
  
      if failure_occured:
          return False
      return True
  
  def submit_pr_comment(text, pull_id, repo):
      """ Submit a comment on a pull request or issue in github """
      github_username, oauth_token = get_github_credentials()
      payload = {'body': text}
      comment_url = "%s/repos/%s/issues/%s/comments" % (GITHUB_API_URL, repo, pull_id)
      response = requests.post(comment_url, json=payload, auth=(github_username, oauth_token))
      # Raise an error if the request fails for some reason
      response.raise_for_status()
  
  def submit_pr_status_update(state, text, remote_sha, repo):
      """ Submit a commit status update with a link to the build results """
      target_url = os.getenv("BUILD_URL")
      github_username, oauth_token = get_github_credentials()
      payload = {'state': state,
                 'description': text,
                 'target_url': target_url,
                 'context': "jenkins-ci"}
      status_url = "%s/repos/%s/statuses/%s" % (GITHUB_API_URL, repo, remote_sha)
      response = requests.post(status_url, json=payload, auth=(github_username, oauth_token))
      # Raise an error if the request fails for some reason
      response.raise_for_status()
  
  def get_github_credentials():
      """ Get credentials from mounted secret volume """
      secret_dir = os.getenv("OPENSHIFT_BOT_SECRET_DIR")
      if secret_dir == "":
          print "ERROR: $OPENSHIFT_BOT_SECRET_DIR undefined. This variable should exist and" + \
              " should point to the mounted volume containing the openshift-ops-bot username" + \
              " and password"
          sys.exit(2)
      username_file = open(os.path.join("/", secret_dir, "username"), 'r')
      token_file = open(os.path.join("/", secret_dir, "token"), 'r')
      username = username_file.read()
      username_file.close()
      token = token_file.read()
      token_file.close()
      return username, token
  
  def check_user_whitelist(payload):
      """ Get and check the user whitelist for testing from mounted secret volume """
      # Get user from payload
      user = ""
      comment_made = False
      if "pull_request" in payload:
          user = payload["pull_request"]["user"]["login"]
      elif "comment" in payload:
          user = payload["comment"]["user"]["login"]
          comment_made = True
      else:
          print "Webhook payload does not include pull request user or issue comment user data"
          sys.exit(1)
  
      if comment_made:
          body = payload["comment"]["body"]
          if not "[test]" in body.split(" "):
              print "Pull request coment does not include test string \"" + TEST_STRING +"\""
              # Exit success here so that the jenkins job is marked as  a success,
              # since no actual error occurred, the expected has happened
              sys.exit(0)
  
      # Get secret informatino from env variable
      secret_dir = os.getenv("WHITELIST_SECRET_DIR")
      if secret_dir == "":
          print "ERROR: $WHITELIST_SECRET_DIR undefined. This variable should exist and" + \
              " should point to the mounted volume containing the admin whitelist"
          sys.exit(2)
      # Extract whitelist from secret volume
      whitelist_file = open(os.path.join("/", secret_dir, "whitelist"), "r")
      whitelist = whitelist_file.read()
      whitelist_file.close()
      if whitelist == "" or user not in whitelist.split(","):
          print "WARN: User " + user + " not in admin whitelist."
          # exit success here so that the jenkins job is marked as a success,
          # since no actual error occured, the expected has happened
          sys.exit(0)
  
  def get_pull_request_info(payload):
      """ Get the relevant pull request details for this webhook payload """
      if "pull_request" in payload:
          return payload["pull_request"]
  
      if not "issue" in payload:
          print "Webhook payload does not include pull request or issue data"
          sys.exit(1)
      if not "pull_request" in payload["issue"]:
          print "Webhook payload is for an issue comment, not pull request."
          sys.exit(1)
  
      pull_request_url = payload["issue"]["pull_request"]["url"]
      response = requests.get(pull_request_url)
      response.raise_for_status()
      pull_request_json = response.text
      try:
          pull_request = json.loads(pull_request_json, parse_int=str, parse_float=str)
      except ValueError as error:
          print "Unable to load JSON data from " + pull_request_url
          print error
          sys.exit(1)
      return pull_request
  
  def main():
      """ Get the payload, merge changes, assign env, and run validators """
      # Get the github webhook payload json from the defined env variable
      payload_json = os.getenv("GITHUB_WEBHOOK_PAYLOAD", "")
      if payload_json == "":
          print 'No JSON data provided in $GITHUB_WEBHOOK_PAYLOAD'
          sys.exit(1)
      try:
          payload = json.loads(payload_json, parse_int=str, parse_float=str)
      except ValueError as error:
          print "Unable to load JSON data from $GITHUB_WEBHOOK_PAYLOAD:"
          print error
          sys.exit(1)
  
      # Check to ensure the user submitting the changes is on the whitelist
      check_user_whitelist(payload)
  
      # Extract or get the pull request information from the payload
      pull_request = get_pull_request_info(payload)
  
      remote_sha = pull_request["head"]["sha"]
      pull_id = pull_request["number"]
      repo = pull_request["base"]["repo"]["full_name"]
  
      # Update the PR to inform users that testing is in progress
      submit_pr_status_update("pending", "Automated tests in progress", remote_sha, repo)
  
      # Merge changes from pull request
      merge_changes(pull_request)
  
      # Assign env variables for validators
      assign_env(pull_request)
  
      # Run validators
      success = run_validators()
  
      # Determine and post result of tests
      if not success:
          submit_pr_comment("Tests failed!", pull_id, repo)
          submit_pr_status_update("failure", "Automated tests failed", remote_sha, repo)
          sys.exit(1)
      submit_pr_comment("Tests passed!", pull_id, repo)
      submit_pr_status_update("success", "Automated tests passed", remote_sha, repo)
  
  if __name__ == '__main__':
      main()
  
files: jenkins/test/run_tests.py
files: """ Run all tests for openshift-tools repository """
  # This script expects a single environment variable to be defined with a myriad of json data:
  #    GITHUB_WEBHOOK_PAYLOAD
  #
  # The data expected from this payload is that generated by the pull reqeust edit webhook,
  # defined here:
  #    https://developer.github.com/v3/activity/events/types/#pullrequestevent
  #
  # The script will parse the JSON and define a list of environment variables for consumption by
  # the validation scripts. Then, each *.py file in ./validators/ (minus specified exclusions)
  # will be run. The list of variables defined is below:
  #  Github stuff
  #    PRV_TITLE         Title of the pull request
  #    PRV_BODY          Description of the pull request
  #    PRV_PULL_ID       ID of the pull request
  #    PRV_PULL_URL      URL of the pull request
  #
  #  Base info
  #    PRV_BASE_SHA      SHA of the target being merged into
  #    PRV_BASE_REF      ref (usually branch name) of the base
  #    PRV_BASE_LABEL    Base label
  #    PRV_BASE_NAME     Full name of the base 'namespace/reponame'
  #
  #  Remote (or "head") info
  #    PRV_REMOTE_SHA    SHA of the branch being merged
  #    PRV_REMOTE_REF    ref (usually branch name) of the remote
  #    PRV_REMOTE_LABEL  Remote label
  #    PRV_REMOTE_NAME   Full name of the remote 'namespace/reponame'
  #    PRV_CURRENT_SHA   The SHA of the merge commit
  #
  
  import os
  import json
  import subprocess
  import sys
  import requests
  
  EXCLUDES = [
      "common.py",
      ".pylintrc"
  ]
  VALIDATOR_PATH = "jenkins/test/validators/"
  GITHUB_API_URL = "https://api.github.com"
  
  def run_cli_cmd(cmd, exit_on_fail=True):
      '''Run a command and return its output'''
      proc = subprocess.Popen(cmd, bufsize=-1, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
                              shell=False)
      stdout, stderr = proc.communicate()
      if proc.returncode != 0:
          print "Unable to run " + " ".join(cmd) + " due to error: " + stderr
          if exit_on_fail:
              sys.exit(proc.returncode)
          else:
              return False, stdout
      else:
          return True, stdout
  
  def assign_env(pull_request):
      '''Assign environment variables base don github webhook payload json data'''
      # Github environment variables
      os.environ["PRV_TITLE"] = pull_request["title"]
      # Handle pull request body in case it is empty
      os.environ["PRV_BODY"] = (pull_request["body"] if pull_request["body"] is not None else "")
      os.environ["PRV_PULL_ID"] = pull_request["number"]
      os.environ["PRV_URL"] = pull_request["url"]
  
      # Base environment variables
      base = pull_request["base"]
      os.environ["PRV_BASE_SHA"] = base["sha"]
      os.environ["PRV_BASE_REF"] = base["ref"]
      os.environ["PRV_BASE_LABEL"] = base["label"]
      os.environ["PRV_BASE_NAME"] = base["repo"]["full_name"]
  
      # Remote environment variables
      head = pull_request["head"]
      os.environ["PRV_REMOTE_SHA"] = head["sha"]
      os.environ["PRV_REMOTE_REF"] = head["ref"]
      os.environ["PRV_REMOTE_LABEL"] = head["label"]
      os.environ["PRV_REMOTE_NAME"] = head["repo"]["full_name"]
  
  def merge_changes(pull_request):
      """ Merge changes into current repository """
      pull_id = pull_request["number"]
  
      run_cli_cmd(['/usr/bin/git', 'fetch', "--tags", "origin", "+refs/head/*:refs/remotes/origin/*",
                   "+refs/pull/*:refs/remotes/origin/pr/*"])
      _, output = run_cli_cmd(['/usr/bin/git', 'rev-parse',
                               'refs/remotes/origin/pr/'+pull_id+'/merge^{commit}'])
      current_rev = output.rstrip()
      run_cli_cmd(['/usr/bin/git', 'config', 'core.sparsecheckout'], exit_on_fail=False)
      run_cli_cmd(['/usr/bin/git', 'checkout', '-f', current_rev])
      os.environ["PRV_CURRENT_SHA"] = current_rev
  
  def run_validators():
      """ Run all test validators """
      failure_occured = False
      validators = [validator for validator in os.listdir(VALIDATOR_PATH) if
                    os.path.isfile(os.path.join(VALIDATOR_PATH, validator))]
      for validator in validators:
          skip = False
          for exclude in EXCLUDES:
              if validator == exclude:
                  skip = True
          if skip:
              continue
          validator_abs = os.path.join(VALIDATOR_PATH, validator)
          executer = ""
          _, ext = os.path.splitext(validator)
          if ext == ".py":
              executer = "/usr/bin/python"
          elif ext == ".sh":
              executer = "/bin/sh"
          # If the ext is not recongized, try to just run the file
          print "Executing validator: " + executer + " " + validator_abs
          success, output = run_cli_cmd([executer, validator_abs], exit_on_fail=False)
          print output
          if not success:
              print validator + " failed!"
              failure_occured = True
  
      if failure_occured:
          return False
      return True
  
  def submit_pr_comment(text, pull_id, repo):
      """ Submit a comment on a pull request or issue in github """
      github_username, oauth_token = get_github_credentials()
      payload = {'body': text}
      comment_url = "%s/repos/%s/issues/%s/comments" % (GITHUB_API_URL, repo, pull_id)
      response = requests.post(comment_url, json=payload, auth=(github_username, oauth_token))
      # Raise an error if the request fails for some reason
      response.raise_for_status()
  
  def submit_pr_status_update(state, text, remote_sha, repo):
      """ Submit a commit status update with a link to the build results """
      target_url = os.getenv("BUILD_URL")
      github_username, oauth_token = get_github_credentials()
      payload = {'state': state,
                 'description': text,
                 'target_url': target_url,
                 'context': "jenkins-ci"}
      status_url = "%s/repos/%s/statuses/%s" % (GITHUB_API_URL, repo, remote_sha)
      response = requests.post(status_url, json=payload, auth=(github_username, oauth_token))
      # Raise an error if the request fails for some reason
      response.raise_for_status()
  
  def get_github_credentials():
      """ Get credentials from mounted secret volume """
      secret_dir = os.getenv("OPENSHIFT_BOT_SECRET_DIR")
      if secret_dir == "":
          print "ERROR: $OPENSHIFT_BOT_SECRET_DIR undefined. This variable should exist and" + \
              " should point to the mounted volume containing the openshift-ops-bot username" + \
              " and password"
          sys.exit(2)
      username_file = open(os.path.join("/", secret_dir, "username"), 'r')
      token_file = open(os.path.join("/", secret_dir, "token"), 'r')
      username = username_file.read()
      username_file.close()
      token = token_file.read()
      token_file.close()
      return username, token
  
  def get_user_whitelist():
      """ Get the user whitelist for testing from mounted secret volume """
      secret_dir = os.getenv("WHITELIST_SECRET_DIR")
      if secret_dir == "":
          print "ERROR: $WHITELIST_SECRET_DIR undefined. This variable should exist and" + \
              " should point to the mounted volume containing the admin whitelist"
          sys.exit(2)
      whitelist_file = open(os.path.join("/", secret_dir, "whitelist"), "r")
      whitelist = whitelist_file.read()
      whitelist_file.close()
      return whitelist
  
  def main():
      """ Get the payload, merge changes, assign env, and run validators """
      # Get the github webhook payload json from the defined env variable
      payload_json = os.getenv("GITHUB_WEBHOOK_PAYLOAD", "")
      if payload_json == "":
          print 'No JSON data provided in $GITHUB_WEBHOOK_PAYLOAD'
          sys.exit(1)
      try:
          payload = json.loads(payload_json, parse_int=str, parse_float=str)
      except ValueError as error:
          print "Unable to load JSON data from $GITHUB_WEBHOOK_PAYLOAD:"
          print error
          sys.exit(1)
      pull_request = payload["pull_request"]
  
      # Check to ensure the user submitting the changes is on the whitelist
      user = pull_request["user"]["login"]
      whitelist = get_user_whitelist()
      if whitelist == "" or user not in whitelist.split(","):
          print "WARN: User " + user + " not in admin whitelist."
          # exit success here so that the jenkins job is marked as a success,
          # since no actual error occured
          sys.exit(0)
  
      remote_sha = pull_request["head"]["sha"]
      pull_id = pull_request["number"]
      repo = pull_request["base"]["repo"]["full_name"]
  
      # Update the PR to inform users that testing is in progress
      submit_pr_status_update("pending", "Automated tests in progress", remote_sha, repo)
  
      # Merge changes from pull request
      merge_changes(pull_request)
  
      # Assign env variables for validators
      assign_env(pull_request)
  
      # Run validators
      success = run_validators()
  
      # Determine and post result of tests
      if not success:
          submit_pr_comment("Tests failed!", pull_id, repo)
          submit_pr_status_update("failure", "Automated tests failed", remote_sha, repo)
          sys.exit(1)
      submit_pr_comment("Tests passed!", pull_id, repo)
      submit_pr_status_update("success", "Automated tests passed", remote_sha, repo)
  
  if __name__ == '__main__':
      main()
  
files: jenkins/test/run_tests.py