分享

windows+php+shell环境下利用git diff进行线上代码自动化部署

近期团队自建了git服务进行代码的版本管理,由于开发过程中遇到代码上线或者代码更新到测试环境比较繁琐,第一个想到的方案就是在测环境上直接进行版本控制,但是我们团队时不时的也会进测试环境直接修改代码文件(别问我怎么这么神操作,我也没有办法,一切为了方便,效率…),另外就是我们很少登录测试服务器远程继续操作,所以这个方案不太理想也不太合理(其实就是麻烦),第二个方案就是,在团队本地推送后,只抽取(自动)更新修改的文件进行更新(可自动也可手动),这个方法影响和范围比较小,也近乎合理,所以暂时就采用了这个方法,这里自动化部署就是利用推送触发gitwebhook的推送事件功能

大概流程如下:本地提交代码到远程git仓库,git的webhook触发推送事件,我已经建了一个hook钩子,为我测试环境上的自动部署脚本,脚本接收到推送事件,拉取远程仓库代码,如果已经clone过则进行pull合并更新,在用diff进行版本比对,抽取最新的更新文件,覆盖到测试环境目录,更新代码(这里也可以只生成更新包,由开发者手动覆盖更新)。

在linux上开发脚本和做相应的自动化部署服务想必大家也知道处理起来应该比较简单,但是天杀的公司的服务器环境是windows的,部署功能开发过程中就遇到各种问题,比如脚本执行权限问题,找不到执行命令(git:command not found或者git不是内部或外部命令,也不是可运行的程序或批处理文件),再后面还碰到执行git遇到证书问题,我主要用的是php执行shell的方式进行部署。

折腾好久总结以下问题和解决方法:

1、git:command not found或者git不是内部或外部命令,也不是可运行的程序或批处理文件,在win+php层面,会遇到各种权限问题,这个就是经典中的经典。

显而易见,找不到命令可执行程序,因为php并不像gui工具一样能直接读取系统环境变量进行命令的执行,所以这里的命令执行程序就要带上程序所在的路径,比如我们执行git、xargs、cp(copy)这些命令就要把路径带上,如:

exec("C:/Program Files/Git/cmd/"git clone git@gitee.com:xxx/xxx,$res,$status);

如果还提示错误,再检查下命令所在目录的执行权限。

2、证书问题,Host key verification failed、Please make sure you have the correct access rights等错误

主要是证书访问错误,检查下以下目录是否有.ssh\known_hosts,里面记录的是访问的公钥信息:

C:\Windows\System32\config\systemprofile\
C:\Windows\SysWOW64\config\systemprofile\

修改git安装目录下的etc\ssh\ssh_config,添加以下信息:

Host gitee.com
	HostName gitee.com
	User xxxx@qq.com

	PreferredAuthentications publickey
	IdentityFile C:\Users\Public\.ssh\gitea #确保该密钥文件iis服务对应的运行账户有读取权限

以上的前提是你已经在git里添加了你自己的公钥信息。如果是第一次使用公钥和密钥请在服务器环境里先执行下git登录,如git clone,到此为止,脚本执行问题基本解决。

下面就是自动化部署的脚本的编写, 我们团队使用的是gitea部署的git服务,它的webhook的请求体数据大概如下:

{
  "ref": "refs/heads/dev",
  "before": "cf3ccec65ca2316e54f4c7b7bc43e8d594fdcaeb",
  "after": "2372a2d670ed06710e2b1a4a3813859703171c62",
  "compare_url": "http://10.0.0.xxxx:3000/xxxx/xxx/compare/cf3ccec65ca2316e54f4c7b7bc43e8d594fdcaeb...2372a2d670ed06710e2b1a4a3813859703171c62",
  "commits": [
    {
      "id": "2372a2d670ed06710e2b1a4a3813859703171c62",
      "message": "优化webhook接口\n",
      "url": "http://10.0.0.xxxx:3000/xxxx/xxxx/commit/2372a2d670ed06710e2b1a4a3813859703171c62",
      "author": {
        "name": "zhang",
        "email": "zhang@xxxxx.com",
        "username": "zhang"
      },
      "committer": {
        "name": "zhang",
        "email": "zhang@xxxxx.com",
        "username": "zhang"
      },
      "verification": null,
      "timestamp": "2023-06-07T20:32:57+08:00",
      "added": [],
      "removed": [],
      "modified": [
        "apis/v1/gitwebhook.php"
      ]
    },
    {
      "id": "4d43af0ba6dab5306bbd47bc2167c728d1da0c14",
      "message": "优化webhook接口\n",
      "url": "http://10.0.0.xxx:3000/xxxxx/xxx/commit/4d43af0ba6dab5306bbd47bc2167c728d1da0c14",
      "author": {
        "name": "zhang",
        "email": "zhang@xxxxx.com",
        "username": "zhang"
      },
      "committer": {
        "name": "zhang",
        "email": "zhang@xxxxx.com",
        "username": "zhang"
      },
      "verification": null,
      "timestamp": "2023-06-07T20:29:51+08:00",
      "added": [
        "apis/v1/gitwebhook.php",
        "apis/v1/gitwebhook.sh",
        "apis/v1/gitwebhook_shell.bat",
        "apis/v1/inc/file.func.php"
      ],
      "removed": [],
      "modified": [
        "apis/v1/Base.php",
        "apis/v1/inc/helper.php"
      ]
    }
  ],
  "total_commits": 2,
  "head_commit": {
    "id": "2372a2d670ed06710e2b1a4a3813859703171c62",
    "message": "优化webhook接口\n",
    "url": "http://10.0.0.xxx:3000/xxxxx/xxxx/commit/2372a2d670ed06710e2b1a4a3813859703171c62",
    "author": {
      "name": "zhang",
      "email": "zhang@xxxxx.com",
      "username": "zhang"
    },
    "committer": {
      "name": "zhang",
      "email": "zhang@xxxxx.com",
      "username": "zhang"
    },
    "verification": null,
    "timestamp": "2023-06-07T20:32:57+08:00",
    "added": [],
    "removed": [],
    "modified": [
      "apis/v1/gitwebhook.php"
    ]
  },
  "repository": {
    "id": 1,
    "owner": {
      "id": 2,
      "login": "xxxxx",
      "login_name": "",
      "full_name": "xxx开发团队",
      "email": "",
      "avatar_url": "http://10.0.0.xxx:3000/avatars/1fece0cb254f1f45891bc9ee9fed72a9",
      "language": "",
      "is_admin": false,
      "last_login": "0001-01-01T00:00:00Z",
      "created": "2023-06-04T13:59:12+08:00",
      "restricted": false,
      "active": false,
      "prohibit_login": false,
      "location": "",
      "website": "",
      "description": "xxx开发团队",
      "visibility": "public",
      "followers_count": 0,
      "following_count": 0,
      "starred_repos_count": 0,
      "username": "xxxxx"
    },
    "name": "crm",
    "full_name": "xxx/xxxx",
    "description": "xxx系统",
    "empty": false,
    "private": true,
    "fork": false,
    "template": false,
    "parent": null,
    "mirror": false,
    "size": 121353,
    "language": "",
    "languages_url": "http://10.0.0.xxx:3000/api/v1/repos/xxx/xxx/languages",
    "html_url": "http://10.0.0.xxx:3000/xxx/xxx",
    "link": "",
    "ssh_url": "SYSTEM@10.0.0.xxx:xxx/xxx.git",
    "clone_url": "http://10.0.0.xxx:3000/xxx/xxx.git",
    "original_url": "",
    "website": "",
    "stars_count": 0,
    "forks_count": 0,
    "watchers_count": 3,
    "open_issues_count": 0,
    "open_pr_counter": 0,
    "release_counter": 0,
    "default_branch": "dev",
    "archived": false,
    "created_at": "2023-06-04T14:22:57+08:00",
    "updated_at": "2023-06-07T09:35:05+08:00",
    "permissions": {
      "admin": true,
      "push": true,
      "pull": true
    },
    "has_issues": true,
    "internal_tracker": {
      "enable_time_tracker": true,
      "allow_only_contributors_to_track_time": true,
      "enable_issue_dependencies": true
    },
    "has_wiki": true,
    "has_pull_requests": true,
    "has_projects": true,
    "ignore_whitespace_conflicts": false,
    "allow_merge_commits": true,
    "allow_rebase": true,
    "allow_rebase_explicit": true,
    "allow_squash_merge": true,
    "allow_rebase_update": true,
    "default_delete_branch_after_merge": false,
    "default_merge_style": "merge",
    "default_allow_maintainer_edit": false,
    "avatar_url": "",
    "internal": false,
    "mirror_interval": "",
    "mirror_updated": "0001-01-01T00:00:00Z",
    "repo_transfer": null
  },
..........忽略其他部分

php自动化脚本部署代码部分(#前面解密接收参数部分省略):

$repname=$decoded['repository']['name'];
    $branch=$_GET['branch']?:$decoded['repository']['default_branch'];
    $gitRunPath='"C:/Program Files/Git/cmd/"';
    $binRunPath='"C:/Program Files/Git/usr/bin/"';
    $patch_dir=ROOTPATH.'/patch';
    $work_path=$patch_dir.'/work';
    $head_commit_id=$decoded['head_commit']['id'];
    $commits=json_decode(file_read('patch/commits.json'),true);
    $status=-1;

    $oldVersion = $commits['id']?:$decoded['before'];
    $newVersion = $head_commit_id;
    mkdirs($work_path);
    $reppath=$work_path.'/'.$repname;
    if(is_dir($reppath.'/.git')){
        chdir($reppath);
        $command="${gitRunPath}git pull 2>&1";
        exec($command,$output,$status);
        print_r($result.$status.$command);
    }
    else{
        chdir($work_path);
        $projectUrl = 'SYSTEM@git.xxxx.com:xxx/xxx.git';
        $command="${gitRunPath}git clone ${projectUrl} --branch $branch $repname  2>&1";
        exec($command,$output,$status);
        $result = iconv('GBK', 'UTF-8',var_export($output,true));
        print_r($result.$status.$command);
    }

    if($status==0){
        $updatepath=$patch_dir.'/'.$newVersion;
        mkdirs($updatepath);
        chdir($work_path.'/'.$repname);
        $command="${gitRunPath}git diff $oldVersion $newVersion --name-only --diff-filter=ACM | ${binRunPath}xargs -i ${binRunPath}cp '{}' $updatepath --parents  2>&1";
        exec($command,$output,$status);
        $result = iconv('GBK', 'UTF-8',var_export($output,true));
        print_r($result.$status.$command);
    }
    if($status==0&&$_GET['update']==1){
       $copyed=file_copy($updatepath,ROOTPATH,true);
    }
    if($status==0)print_r('脚本执行成功');

留言