Skip to content

Instantly share code, notes, and snippets.

@developerck
Last active Apr 1, 2022
Embed
What would you like to do?
Bitbucket API implementation to validate the changefiles in a Pull Request

bit bucket pipeline for and code validation and change pull request status with comment.

  • Replace < BITBUCKET REPO URL > with your bitbucket repo URL in lib.php and pull-build.php
  • This is also validating coding standard as per PHPCS, please change the path accordingly in lib.php
  • pull-build.php get the changelogs and lib.php have the function to accept/decline/post comment on PR
  • PHPCS is optional and maintained with a repository variable, and also some of the variables needed   $BB_USERNAME $BB_PASSWORD $BB_PHPCS $BB_DECLINE
  • Make a user in bitbucket, create an app password and use that username and password for this. that user must have access to the repo and shoudl be able to read and write to accept and decline the Pull request   
# Template PHP Build
# This template allows you to validate your PHP application.
# The workflow allows running tests and code linting on the default branch.
image: php:7.2-cli
pipelines:
pull-requests:
'**':
- step:
name: validate code
script:
- chmod +x ./src/local/codechecker/phpcs/bin/phpcs
- chmod +x ./src/local/codechecker/scripts/build/pull-build.php
- ./src/local/codechecker/scripts/build/pull-build.php $BB_USERNAME $BB_PASSWORD $BITBUCKET_PR_ID $BB_PHPCS $BB_DECLINE
<?php class curl{public $cache=false;public $proxy=false;public $response=array();public $header=array();public $info;public $error;private $options;private $proxy_host='';private $proxy_auth='';private $proxy_type='';private $debug=false;private $cookie=false;public function __construct($options=array()){if(!function_exists('curl_init')){$this->error='cURL module must be enabled!';trigger_error($this->error,E_USER_ERROR);return false;}$this->resetopt();if(!empty($options['debug'])){$this->debug=true;}if(!empty($options['cookie'])){if($options['cookie']===true){$this->cookie='curl_cookie.txt';}else{$this->cookie=$options['cookie'];}}if(!empty($options['cache'])){if(class_exists('curl_cache')){$this->cache=new curl_cache();}}}public function resetopt(){$this->options=array();$this->options['CURLOPT_USERAGENT']='MoodleBot/1.0';$this->options['CURLOPT_HEADER']=0;$this->options['CURLOPT_NOBODY']=0;$this->options['CURLOPT_MAXREDIRS']=10;$this->options['CURLOPT_ENCODING']='';$this->options['CURLOPT_RETURNTRANSFER']=1;$this->options['CURLOPT_BINARYTRANSFER']=0;$this->options['CURLOPT_SSL_VERIFYPEER']=0;$this->options['CURLOPT_SSL_VERIFYHOST']=2;$this->options['CURLOPT_CONNECTTIMEOUT']=30;}public function resetcookie(){if(!empty($this->cookie)){if(is_file($this->cookie)){$fp=fopen($this->cookie,'w');if(!empty($fp)){fwrite($fp,'');fclose($fp);}}}}public function setopt($options=array()){if(is_array($options)){foreach($options as $name=>$val){if(stripos($name,'CURLOPT_')===false){$name=strtoupper('CURLOPT_'.$name);}$this->options[$name]=$val;}}}public function cleanopt(){unset($this->options['CURLOPT_HTTPGET']);unset($this->options['CURLOPT_POST']);unset($this->options['CURLOPT_POSTFIELDS']);unset($this->options['CURLOPT_PUT']);unset($this->options['CURLOPT_INFILE']);unset($this->options['CURLOPT_INFILESIZE']);unset($this->options['CURLOPT_CUSTOMREQUEST']);}public function setHeader($header){if(is_array($header)){foreach($header as $v){$this->setHeader($v);}}else{$this->header[]=$header;}}public function getResponse(){return $this->response;}private function formatHeader($ch,$header){$this->count++;if(strlen($header)>2){list($key,$value)=explode(" ",rtrim($header,"\r\n"),2);$key=rtrim($key,':');if(!empty($this->response[$key])){if(is_array($this->response[$key])){$this->response[$key][]=$value;}else{$tmp=$this->response[$key];$this->response[$key]=array();$this->response[$key][]=$tmp;$this->response[$key][]=$value;}}else{$this->response[$key]=$value;}}return strlen($header);}private function apply_opt($curl,$options){$this->cleanopt();if(!empty($this->cookie)||!empty($options['cookie'])){$this->setopt(array('cookiejar'=>$this->cookie,'cookiefile'=>$this->cookie));}if(!empty($this->proxy)||!empty($options['proxy'])){$this->setopt($this->proxy);}$this->setopt($options);curl_setopt($curl,CURLOPT_HEADERFUNCTION,array(&$this,'formatHeader'));if(empty($this->header)){$this->setHeader(array('User-Agent: MoodleBot/1.0','Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7','Connection: keep-alive'));}curl_setopt($curl,CURLOPT_HTTPHEADER,$this->header);if($this->debug){echo '<h1>Options</h1>';var_dump($this->options);echo '<h1>Header</h1>';var_dump($this->header);}foreach($this->options as $name=>$val){if(is_string($name)){$name=constant(strtoupper($name));}curl_setopt($curl,$name,$val);}return $curl;}public function download($requests,$options=array()){$options['CURLOPT_BINARYTRANSFER']=1;$options['RETURNTRANSFER']=false;return $this->multi($requests,$options);}protected function multi($requests,$options=array()){$count=count($requests);$handles=array();$results=array();$main=curl_multi_init();for($i=0;$i<$count;$i++){$url=$requests[$i];foreach($url as $n=>$v){$options[$n]=$url[$n];}$handles[$i]=curl_init($url['url']);$this->apply_opt($handles[$i],$options);curl_multi_add_handle($main,$handles[$i]);}$running=0;do{curl_multi_exec($main,$running);}while($running>0);for($i=0;$i<$count;$i++){if(!empty($options['CURLOPT_RETURNTRANSFER'])){$results[]=true;}else{$results[]=curl_multi_getcontent($handles[$i]);}curl_multi_remove_handle($main,$handles[$i]);}curl_multi_close($main);return $results;}protected function request($url,$options=array()){$curl=curl_init($url);$options['url']=$url;$this->apply_opt($curl,$options);if($this->cache&&$ret=$this->cache->get($this->options)){return $ret;}else{$ret=curl_exec($curl);if($this->cache){$this->cache->set($this->options,$ret);}}$this->info=curl_getinfo($curl);$this->error=curl_error($curl);if($this->debug){echo '<h1>Return Data</h1>';var_dump($ret);echo '<h1>Info</h1>';var_dump($this->info);echo '<h1>Error</h1>';var_dump($this->error);}curl_close($curl);if(empty($this->error)){return $ret;}else{return $this->error;}}public function head($url,$options=array()){$options['CURLOPT_HTTPGET']=0;$options['CURLOPT_HEADER']=1;$options['CURLOPT_NOBODY']=1;return $this->request($url,$options);}function format_array_postdata_for_curlcall($arraydata,$currentdata,&$data){foreach($arraydata as $k=>$v){$newcurrentdata=$currentdata;if(is_object($v)){$v=(array) $v;}if(is_array($v)){$newcurrentdata=$newcurrentdata.'['.urlencode($k).']';$this->format_array_postdata_for_curlcall($v,$newcurrentdata,$data);}else{$data[]=$newcurrentdata.'['.urlencode($k).']='.urlencode($v);}}}function format_postdata_for_curlcall($postdata){if(is_object($postdata)){$postdata=(array) $postdata;}$data=array();foreach($postdata as $k=>$v){if(is_object($v)){$v=(array) $v;}if(is_array($v)){$currentdata=urlencode($k);$this->format_array_postdata_for_curlcall($v,$currentdata,$data);}else{$data[]=urlencode($k).'='.urlencode($v);}}$convertedpostdata=implode('&',$data);return $convertedpostdata;}public function post($url,$params='',$options=array()){$options['CURLOPT_POST']=1;if(is_array($params)){$params=$this->format_postdata_for_curlcall($params);}$options['CURLOPT_POSTFIELDS']=$params;return $this->request($url,$options);}public function get($url,$params=array(),$options=array()){$options['CURLOPT_HTTPGET']=1;if(!empty($params)){$url.=(stripos($url,'?')!==false)?'&':'?';$url.=http_build_query($params,'','&');}return $this->request($url,$options);}public function put($url,$params=array(),$options=array()){$file=$params['file'];if(!is_file($file)){return null;}$fp=fopen($file,'r');$size=filesize($file);$options['CURLOPT_PUT']=1;$options['CURLOPT_INFILESIZE']=$size;$options['CURLOPT_INFILE']=$fp;if(!isset($this->options['CURLOPT_USERPWD'])){$this->setopt(array('CURLOPT_USERPWD'=>'anonymous: noreply@moodle.org'));}$ret=$this->request($url,$options);fclose($fp);return $ret;}public function delete($url,$param=array(),$options=array()){$options['CURLOPT_CUSTOMREQUEST']='DELETE';if(!isset($options['CURLOPT_USERPWD'])){$options['CURLOPT_USERPWD']='anonymous: noreply@moodle.org';}$ret=$this->request($url,$options);return $ret;}public function trace($url,$options=array()){$options['CURLOPT_CUSTOMREQUEST']='TRACE';$ret=$this->request($url,$options);return $ret;}public function options($url,$options=array()){$options['CURLOPT_CUSTOMREQUEST']='OPTIONS';$ret=$this->request($url,$options);return $ret;}public function get_info(){return $this->info;}}class curl_cache{public $dir='';function __construct(){$this->dir='/tmp/';if(!file_exists($this->dir)){mkdir($this->dir,0700,true);}$this->ttl=1200;}public function get($param){$this->cleanup($this->ttl);$filename='u_'.md5(serialize($param));if(file_exists($this->dir.$filename)){$lasttime=filemtime($this->dir.$filename);if(time()-$lasttime>$this->ttl){return false;}else{$fp=fopen($this->dir.$filename,'r');$size=filesize($this->dir.$filename);$content=fread($fp,$size);return unserialize($content);}}return false;}public function set($param,$val){$filename='u_'.md5(serialize($param));$fp=fopen($this->dir.$filename,'w');fwrite($fp,serialize($val));fclose($fp);}public function cleanup($expire){if($dir=opendir($this->dir)){while(false!==($file=readdir($dir))){if(!is_dir($file)&&$file!='.'&&$file!='..'){$lasttime=@filemtime($this->dir.$file);if(time()-$lasttime>$expire){@unlink($this->dir.$file);}}}}}public function refresh(){if($dir=opendir($this->dir)){while(false!==($file=readdir($dir))){if(!is_dir($file)&&$file!='.'&&$file!='..'){if(strpos($file,'u_')!==false){@unlink($this->dir.$file);}}}}}}
<?php
/**
* code checking at build
* @author Chandra K <developerck@gmail.com>
* @since aug 2021
*/
function build_hr() {
echo "=====================================================".PHP_EOL;
}
function build_log($message){
print($message.PHP_EOL);
}
function build_cmd($command){
//build_log($command);
exec($command, $output, $exit_code);
//build_log(implode(PHP_EOL, $output));
return array($exit_code,implode(PHP_EOL,$output));
}
function build_post_approve ($username, $password, $pr_id){
build_hr();
build_log("Approving PR");
// https://api.bitbucket.org/2.0/repositories/chandra/web/
$url = "<BITBUCKET REPO URL>pullrequests/" . $pr_id."/approve";
$curl = new curl();
$curl->setopt(array('CURLOPT_USERPWD'=>"$username:$password",'CURLOPT_HTTPHEADER'=> array('Content-Type:application/json')));
$post_object = new stdClass();
@$post_object->approved= 1;
@$post_object->state= 'approved';
$json = json_encode($post_object);
$out = $curl->post($url, $json);
if (!empty($curl->error)) {
build_log($curl->error);
return (array('1','could not approve'));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
build_log("Failed ". $info['http_code']);
build_log($out);
return (array('1','could not approve'));
}
build_log("PR Approved!");
build_hr();
return array(0, "PR Approved!");
}
function build_post_request_change ($username, $password, $pr_id){
build_hr();
build_log("Requesting Change ");
// https://api.bitbucket.org/2.0/repositories/chandra/web/
$url = "<BITBUCKET REPO URL>pullrequests/" . $pr_id."/request-changes";
$curl = new curl();
$curl->setopt(array('CURLOPT_USERPWD'=>"$username:$password",'CURLOPT_HTTPHEADER'=> array('Content-Type:application/json')));
$post_object = new stdClass();
@$post_object->approved= 0;
@$post_object->state= 'changes_requested';
$json = json_encode($post_object);
$out = $curl->post($url, $json);
if (!empty($curl->error)) {
build_log($curl->error);
return (array('1','could not request change '));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
build_log("Failed ". $info['http_code']);
build_log($out);
return (array('1','could not request change '));
}
build_log("Changes Requested!");
build_hr();
return array(0, "Changes Requested!");
}
function build_post_decline ($username, $password, $pr_id){
build_hr();
build_log("Declining PR");
// https://api.bitbucket.org/2.0/repositories/chandra/web/
$url = "<BITBUCKET REPO URL>pullrequests/" . $pr_id."/decline";
$curl = new curl();
$curl->setopt(array('CURLOPT_USERPWD'=>"$username:$password",'CURLOPT_HTTPHEADER'=> array('Content-Type:application/json')));
$post_object = new stdClass();
@$post_object->approved= 0;
@$post_object->state= 'declined';
$json = json_encode($post_object);
$out = $curl->post($url, $json);
if (!empty($curl->error)) {
build_log($curl->error);
return (array('1','could not decline'));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
build_log("Failed ". $info['http_code']);
build_log($out);
return (array('1','could not decline'));
}
build_log("PR Declined!");
build_hr();
return array(0, "PR Declined!");
}
function build_post_comment_on_pr ($username, $password, $pr_id, $comment){
build_hr();
build_log("Posting Comment");
// https://api.bitbucket.org/2.0/repositories/chandra/web/
$url = "<BITBUCKET REPO URL>pullrequests/" . $pr_id."/comments";
$curl = new curl();
$curl->setopt(array('CURLOPT_USERPWD'=>"$username:$password",'CURLOPT_HTTPHEADER'=> array('Content-Type:application/json')));
//print_R($curl->get($url));die;
$post_object = new stdClass();
$comment = trim(preg_replace('/'.PHP_EOL.'/', ' ___eol___', $comment));
@$post_object->content->raw = $comment;
$json = json_encode($post_object);
$json = trim(preg_replace('/___eol___/', ' \n', $json));
$out = $curl->post($url, $json);
if (!empty($curl->error)) {
build_log($curl->error);
return (array('1','could not post pr comment'));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
build_log("Failed ". $info['http_code']);
build_log($out);
return (array('1','could not post pr comment'));
}
build_log("Comment Posted!");
build_hr();
return array(0, "Comment Posted!");
}
function build_run_phpcs($files){
if(empty($files)) return ;
build_cmd('chmod +x ./src/local/codechecker/phpcs/bin/phpcs');
$command = './src/local/codechecker/phpcs/bin/phpcs --standard=moodle --encoding=utf-8 -n ' . implode(" ", $files);
return build_cmd($command);
}
function build_rule_checkcore($files){
// md5 :: 999cc71a6e7d86f613e404d3d76b285c
$core_files = file_get_contents(dirname(dirname(__FILE__))."/data/core_files.txt");
$in_core_files = [];
foreach($files as $f){
if(strpos($core_files, $f) !== FALSE){
$in_core_files[] = $f;
}
}
$exit_code = 0;
if(!empty($in_core_files)){
$exit_code = 1;
$out['msg'] = '===========RULE:CHECK CORE : FAILED===========';
$in_core_files = $out + $in_core_files;
}
return array($exit_code,implode(PHP_EOL, $in_core_files));
}
#!/usr/bin/env php
<?php
/**
* code checking at build
* @author Chandra K <developerck@gmail.com>
* @since aug 2021
*/
$BASE_PATH = dirname(dirname(dirname(dirname(dirname(dirname(__FILE__))))));
require($BASE_PATH."/src/local/codechecker/scripts/build/lib.php");
require($BASE_PATH."/src/local/codechecker/scripts/build/curl.php");
function build($username, $password, $pr_id, $phpcs=0, $decline=0){
if(!$pr_id){
echo "Missing Pull Request ID";
exit(2);
}
build_hr();
build_log("Starting");
build_log("It will run only for first 500 files. try consider branch merge check");
build_log("Get the PR changed files");
build_hr();
$exit_code = 0;
$output = '';
// https://api.bitbucket.org/2.0/repositories/chandra/web/
$url = "<BITBUCKET REPO URL>pullrequests/" . $pr_id;
$curl = new curl();
$curl->setopt(array('CURLOPT_USERPWD'=>"$username:$password"));
$out = $curl->get($url);
if (!empty($curl->error)) {
return (array('1','could not get the pr'));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
return (array('1','could not get the pr'));
}
$data = json_decode($out, true);
build_log("parsing Diffstat url");
$diffstat_url = $data['links']['diffstat']['href'];
$out = $curl->get($diffstat_url);
if (!empty($curl->error)) {
return (array('1','could not get the pr diffstat'));
}
$info = $curl->get_info();
if ($info['http_code'] != 200){
return (array('1','could not get the pr diffstat'));
}
$data = json_decode($out, true);
$changed_files = [];
foreach($data['values'] as $fileobj){
$file = $fileobj['new']['path'];
if(pathinfo($file, PATHINFO_EXTENSION) == 'php'){
$changed_files[] = $file;
}
}
// changing directory for execution
chdir(dirname(dirname(dirname(dirname(dirname(dirname(__FILE__))))) ));
//$changed_files[] = 'src/local/codechecker/test.php';
if(!empty($changed_files)){
if($phpcs){
build_hr();
build_log("Starting PHP Code Scan");
build_hr();
list( $exit_code_cs,$output_cs) = build_run_phpcs($changed_files);
build_log($output_cs);
build_hr();
build_log("End PHP Code Scan");
build_hr();
}
build_hr();
build_log("Starting Rule Checks");
build_hr();
list( $exit_code_core,$output_core) = build_rule_checkcore($changed_files);
build_log( $output_core);
build_hr();
build_log("End Rule Checks");
build_hr();
$exit_code = $exit_code_cs + $exit_code_core;
$output = $output_cs . $output_core;
if($exit_code){
// post comment
build_post_comment_on_pr($username, $password, $pr_id, $output);
if($decline){
build_post_decline($username, $password, $pr_id);
}else{
build_post_request_change($username, $password, $pr_id);
}
}else{
$output = "**BUILD APPROVED**".PHP_EOL;
build_post_comment_on_pr($username, $password, $pr_id, $output);
build_post_approve($username, $password, $pr_id);
}
}
return array($exit_code, $output);
}
$exit_code = 0;
if(count($argv) >= 3){
list($exit_code, $output) = build($argv[1], $argv[2], $argv[3], $argv[4], $argv[5]);
}else{
$exit_code = 1;
echo 'example: <bb_username> <bb_apppassword> <pr_id> <phpcs=0|1>';
}
exit($exit_code);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment