JB的测试之旅-利用jenkins达到提tag自动打包

2,339 阅读14分钟

最近都好忙,需求太多了,干不完,加上最近作息时间的调整,都没时间写博客了,趁着这两天稍微有点时间,想撸一发;

image.png-6.6kB

其实在写的博客有好几篇,但都是陆陆续续的,本来想继续写之前的,突然想起半个月前,同学提出,能不能在开发提tag时自动打包

之前发布过一篇项目流程规范,目前的项目都在遵守这个规范,而研发每次有改动过代码的时候,都会提测,而提测就相当于提tag,这个tag的概念不说了,就是基于当前产品分支重新拉的分支,tag的格式如下图;

image.png-26.2kB

而测试拿到这个tag后,就需要去到jenkins打包,要输入一堆参数,然后等待打包结果;

image.png-73kB

有时候,研发勤奋点,一天提多次tag,测试就需要多次去打包,测试同学觉得太麻烦了,因此就提出一个想法,能不能在开发提tag时自动打包

能,必须能,没有做不到的事情;

image.png-99.3kB

分解

做事之前,要先想想要做什么?

  • 搭建jenkins环境及安装对应插件
  • jenkins打通到gitlab
  • 触发任务

想了下,大致就这3步,那就来一起做吧;

搭建jenkins

为了测试,jb就用了自己的阿里云服务器从头开始搭建一遍,具体搭建过程不说明,之前写的一篇文章有提及到,直接对照操作就好;

安装jenkins

步骤 说明
安装Java sudo yum install java
安装jenkins yum install jenkins
启动jenkins service jenkins start
安装插件

注意

修改jenkins端口 jenkins默认端口是8080,可能会跟别的软件冲突,因此建议修改下端口;

进入jenkins配置文件

vi /etc/sysconfig/jenkins

打开后,找到JENKINS_USERJENKINS_PORT这两项进行修改即可;

image.png-55.7kB

修改成root跟具体端口保存退出即可;

这时候,直接输入主机IP+刚设置的端口就好啦;

阿里云开放端口权限 上面访问ip+端口,有可能打不开链接,因为阿里云对端口做了限制,因此需要开放端口;

登录阿里云,首页右上找到控制台;

image.png-52.4kB

找到云服务器ECS

image.png-24.9kB

点击实例,找到该实例的安装组配置

image.png-103.5kB

点击规则说明,新增即可;

image.png-14kB

如果是按照上面的文档来,安装完插件,创建完用户后,就应该进入到首页,会显示如下内容,至此,jenkins环境搭建就完啦~!

image.png-11.1kB

对了,jenkins的目录在这里:

/var/lib/jenkins

jenkins打通到gitlab

公司的代码仓库是用gitlab,因某种原因,就开放公网啦,那jb就在gitlab新建一个jbtest项目;

image.png-51.5kB

如果有小伙伴说,没有gitlab怎么办?没关系,可以用github代替,至于github相关的配置,可以查看该文章:git介绍及GitHub配置教程

jenkins安装gitlab

既然确定是要用jenkins+gitlab,那就先在jenkins上安装下gitlab插件吧;

点击jenkins首页,有个系统管理按钮;

image.png-48kB

点击后右侧会显示内容,下滑,点击插件管理;

image.png-144.7kB

进入到插件管理界面,就看到updates等4个栏目,那点击Available,右侧输入gitlab,然后找到GitLabGitLab Hook,勾选,点击底部的install即可;

image.png-42.4kB
image.png-140.1kB

test项目有了,gitlab的插件也安装了,那我们就在jenkins新建一个job吧,输入了项目名称,就进入到设置项;

image.png-55.9kB

丢弃旧的构建

这里的丢弃旧的构建,可选,但是习惯选择7天,,最大保留300个; 原因是,jenkins每次构建都会生成一个历史构建记录及对应的产物,如果公司有100个产品,每天自动打包10次,一天就有1000个产物,服务器磁盘空间是个问题,因此设置个7天,设置个最大数,定时删除即可;

  • 持构建的天数:根据你所填写的天数来保存构建记录
  • 保持构建的最大个数:有几条构建记录就保存几条
  • 发布包保留天数:发布的产物保存的天数
  • 发布包最大保留#个构建:发布了多少个产物就保存多少个

上面的丢弃旧构建不是要点,继续;

继续下滑,会发现Source Code Management,中文是源码管理,而我们的代码是存放到gitlab的,因此就选择git了,点击后如下图展开;

image.png-76.5kB

这里有小伙伴可能有疑问,既然是放gitlab,为啥不是选择gitlab,而是选择git?

git、github、gitlab

这里就花点时间说明下git、github、gitlab;

git是一款免费的、开源的分布式版本管理控制系统(工具);

gitHub是一个面向开源及私有软件项目的托管平台,
只支持git作为唯一的版本库格式进行托管;

gitlab,是一款基于Git的项目管理软件,拥有GitHub拥有的一切,但它还具备让团队对它们的repositories进行控制的功能;  

希望经过这么一说,能让你稍微清楚下;

源码管理

点击git,把项目的地址copy到这里;

image.png-21.6kB
image.png-47.4kB

没权限是肯定的,那我们就点击add创建账户呗,选择jenkins;

image.png-37.9kB

输入gitlab/github的账号密码,但是输入完后发现还是报错;

image.png-31.1kB

别着急,那去服务器上看看,git安装了吗?

image.png-13.7kB

没安装,那就安装罗;

yum install git

等待安装完,再输入git --versionwhere is git来看版本及安装位置;

image.png-28.1kB

安装完后,就顺便试试能不能clone项目吧;

image.png-54.8kB

不出所料,果然不行,因为还要配置密钥啊;

$ ssh-keygen -t rsa -C "your_email@youremail.com"
//注意,双引号里面是你的邮箱。填你注册github的邮箱就行了。按enter执行。

一路回车就行,密码一般不用设置~

image.png-95.7kB

上图红框里的就是地址,cd过去;

id_rsaid_rsa.pub两个文件,这两个就是SSH Key的秘钥对, id_rsa是私钥,不能泄露, id_rsa.pub是公钥,可以公开;

配置ssh 在git bash 执行 cat id_rsa.pub,把输出的内容copy出来,然后打开gitlab/github网站,点击右上角自己的图标,点击Setting->ssh key 页面,点击add ssh key~

image.png-66.5kB

这里还有一个特殊的问题: 因开放公网的原因,每次git clone的下载代码的时候是连接的https://而不是git@git (ssh)的形式,导致每次pull/push等远程操作的时候,都需要输入账号密码,非常麻烦;

解决办法:

在项目根目录输入:
git config --global credential.helper store
git pull/git push(第一次要再输入一次,以后就不用)

会在用户目录下生成文件.git-credential记录用户名密码的信息;

那回到jenkins,打开测试job的设置,再看是否正常;

image.png-46.1kB

发现已经好了,没问题,当然也会有小伙伴依然有问题,那就需要这样做;

  • 打开jenkins首页
  • 点击系统管理
  • 全局工具设置
  • 修改git地址

image.png-73.4kB

打开后,滑动到git栏,如果有问题的话,可能会看到错误提示:

There's no such executable git in PATH: /sbin, /usr/sbin, /bin, /usr/bin.

image.png-69kB

在出错的地方填入:

"whereis git"的地址 + "/bin/git" 
(如上面"whereis git"的地址为"/usr/bin/git",则应该填入 "/usr/bin/git/bin/git") 并保存

然后再回到job的源码管理中添加git地址,就会好了;

image.png-32kB

既然可以连接到gitlab了,那我们就直接下滑到build,选择add build step,因为是服务器,所以直接选择执行shell,如果是Windows系统,也可以选择执行Windows批处理命令

image.png-18.1kB

因jb的项目里面有个test.py文件,那项目clone下来肯定是想执行这个文件啦,因此内容就是输入python test.py,然后点击保存;

回到job的首页,点击立即构建;

test.py的作用是向钉钉发一条消息,因此成功的话,钉钉就会收到一条信息;

image.png-138.4kB

到这里,说明jenkins是对接到gitlab是通的了;

触发任务

看看标题,目的是:提tag自动打包

那先看看,tag是怎么生成的? 具体看git命令行第11章,这里有说明;

这里就不bb了,直接贴:

 git tag -a '3.4.0/1811111212' -m 'jbtest tag'
 git push --tags
  • 创建一个含附注类型的标签非常简单,用-a指定标签名字即可;
  • -m 选项则指定了对应的标签说明;
  • git push --tags就是一次推送所有本地新增的标签;

然后再输入git tag查看当前tag信息;

image.png-15.2kB

既然知道tag是怎么生成的,那就看看有没有办法知道tag被创建了?

第一时间当然是想到钩子-hook啦;

image.png-7.7kB

来看下官方解释:

钩子(hooks)是一些在"$GIT-DIR/hooks"目录的脚本, 在被特定的事件(certain points)触发后被调用。
当"git init"命令被调用后, 一些非常有用的示例钩子文件(hooks)被拷到新仓库的hooks目录中; 但是在默认情况下这些钩子(hooks)是不生效的。 把这些钩子文件(hooks)的".sample"文件名后缀去掉就可以使它们生效了。

简单地来说有点类似回调,就是特定事情完成后回调执行事件

那这钩子,在jenkinsgitlab上怎么搞?gitlab支持吗?

gitlab钩子

打开gitlab对应的项目,点击settings-Integrations(集成)

image.png-34.3kB

打开后,就是hook的内容啦,简单瞄下,发现有个选项叫Tag push events,看了下描述,好像蛮符合我们的要求,界面显示,需要输入URLSecret Token,这两玩意怎么来?

image.png-84.1kB

jenkins获取URL跟Secret Token

打开jenkins,打开对应的job,点击设置,查找Build when a change is pushed to GitLab,gitlab上的URL就是该处的链接,如下图红框,复制过去即可;

同时点击右下的Advanced(高级)按钮;

image.png-107.6kB

然后点击右下的Generate按钮;

image.png-89kB

然后会生成一串Secret token,复制到gitlab那;

image.png-11.9kB

那我们再试试,直接在服务器打个tag且push看钉钉会不会收到信息?

image.png-123.6kB

啧啧啧,没问题啊,搞定啦,另外看了下hook,支持的内容蛮多的,tag pushpush eventmerge requests等;

image.png-39.5kB

gitlab没有setting权限

上面说的条条是道,但实际现实是残忍的,打开实际的项目,会发现自己没有setting权限:

image.png-34.2kB

遇到这种情况,不是说不能处理,只能看门槛变高了,有2个方法:

尝试跟对应的负责人沟通,看能否给一个带master权限的账号;

如果有足够的数据让老大认可这个功能,就会给一个带master权限的账号,就可以为所欲为啦;

但如果给的理由不让人信服或者老大觉得没卵用,但依然要做这个功能,那只能换种方式了,但至少要求依然有仓库的可读权限; 换个角度,连仓库权限都没有,就别想做了。。

image.png-30.5kB

大致的思路就是,利用定时功能,不断检测该项目的tag,当发现原tag不是最新tag时,就认为有最新tag,此时再通知jenkins处理;

如果是这样,那jenkins上的选项会有点不同,构建的时候就要选择脚本调用,指定TOKEN_NAME,然后可以通过连接JENKINS_URL/job/JOBNAME/build?token=TOKEN_NAME来启动build

image.png-87.1kB

对了,做这个功能,还有两种形式:

  • 检测脚本放到产品项目根目录下;
  • 检测脚本单独一个项目;

第一种方式的好处时,省事,但是一般不允许,因为产品项目一般存放跟该产品相关的内容,而这个脚本典型是通用的,后面如果多个产品想复用就麻烦了

第二种方式的就是独立项目,可复用,把检测项目跟产品项目放到同一台jenkins机器上,这样它们都在jenkins/workspace目录下,通过传参的方式切换不同目录即可;

而jb采用的是第二种方式;

检测脚本

这个脚本的目的是什么? 就是定时检测tag是不是最新的,如果不是,则执行相应操作;

那难度就在于,怎么判断tag是不是最新的? Google下,有例子:

git describe --tags `git rev-list --tags --max-count=1`

尝试下,好像没问题,那就暂且相信吧;

image.png-21.8kB

好奇问下,这命令到底是啥意思?

其实这是两条git命令;

git describe --tags
git rev-list --tags --max-count=1

git describe --tags 显示当前离当前提交最近的tag,实际发现,带不带参数都一样?

image.png-12.8kB

不加任何参数的情况下,git describe 只会列出带有注释的tag

实际,该命令是可以带3个参数的,tagsalwaysdirty,这一般用的比较少,不展开说明;

git rev-list --tags --max-count=1

git-rev-list :按反向时间顺序列出提交对象 意思就是把倒序方式展示最新提交内容;

image.png-13.6kB

max-count=1,也很好理解,只获取1个,如果写2,则获取2个;

image.png-20.4kB

那命令实际就变成了这样:

git describe 9c7246b2026cec052af6a3b6297995e494c7b398

image.png-9.4kB

最终输出的就是tag,有种奇淫怪招的感觉;

image.png-6.9kB

当然,不会用git的话,也有另外一种方式达到获取最终一个tag的效果:

    #获取tag信息,转list
    content = os.popen("git tag" ).read().split("\n")
    for i in content:
        if ( i != ''):
            tag_list.append(i)

    print(tag_list[-1])

就是输入git tag,获取所有tag信息,再重新存在一个list,只拿最后一个,也可以再做下判断,只保存最后一条,方式很多,类似,但这种效果不好是,假如一个项目经过多轮迭代,有几千条tag,就需要时间了,因此还是推荐第一种;

为了模拟真实场景,就在跟目录下新建一个脚本,跟项目同一根目录;

image.png-12.3kB

脚本的内容如下:

# _*_ coding:utf-8 _*_
import re
import os
import sys
import json
import time
import requests
from apscheduler.schedulers.blocking import BlockingScheduler

tag = ""
scheduler = BlockingScheduler()
base_dir = os.getcwd()

def postDingDing(content):
    dingdingurl = ["https://oapi.dingtalk.com/robot/send?access_token=你的钉钉token"]
    headers = {'Content-Type': 'application/json'}
    String_textMsg = {
        "msgtype": "markdown",
        "markdown": {
            "title" : "有新tag啦",
            "text":   "新tag:  "+content
        }
    }
    requests.packages.urllib3.disable_warnings()
    String_textMsg = json.dumps(String_textMsg)
    for i in dingdingurl:
        requests.post(i, data=String_textMsg, headers=headers, verify=False)


def getProjectsName():
    if len(sys.argv) > 1:
        return sys.argv[1]
    else:
        print("没有传项目参数,请重新输入,例子:python check_gittag.py jbtest")

def check_tag(dir):
    os.chdir(base_dir+"/"+dir)
    # 原型:git describe --tags 'git rev-list --tags --max-count=1'
    id = os.popen("git rev-list --tags --max-count=1").read()
    content = os.popen("git describe --tags "+ id).read()
    return content


def start_task():
    global tag
    projects_name = getProjectsName()
    last_tag = check_tag(projects_name)

    if (tag != last_tag):
        print("有新tag啦:  "+last_tag)
        postDingDing(last_tag)
        tag = last_tag
    # else:
    #     print("没有新tag,继续轮训")

def get_time():
    """
    获取当前时间
    """
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


if __name__ == "__main__":
    print(get_time() + " 魔法仪式已启动!")
    scheduler.add_job(start_task, "interval", seconds=6, id="check_tag")
    scheduler.start()

因为是用来测试,因此jb是做了通知钉钉的功能,执行上面的代码,会发现有一个问题:push了新tag,没有通知!!!

后来想了下,哦,是没有,因为本地的代码还是旧的,比如研发本地push到仓库,但是检测脚本是在服务器A,那肯定是不会知道有代码更新啦;

image.png-14kB

解决方案,马上想到,那push后就通知啊,结果想起,没钩子权限。。

image.png-37.9kB

那没问题,在上面的脚本加多一个更新命令就好啦;

#更新远程代码到本地仓库
git fetch origin

流程通了,那怎么对接到jenkins?

再接jenkins

看回jenkins,按照要求试试,输入以下命令:

 curl -i jenkins_url/job/job_name/build?token=yourtoken

image.png-61.4kB

结果出现403.。。

image.png-91.1kB

两种解决方案:

  • 使用其他插件代替
  • jenkins全局安全点击下图红框内容,jenkins首页-系统管理-全局安全设置;

image.png-65.5kB

jb选择的是第二种方案,勾选后,再次运行上面的命令,没问题,并且可以收到啦~

image.png-27.4kB
image.png-62.4kB

再看钩子

其实故事到上面就完啦,但是呢,虽然知道hook怎么用,但还是想了解更多,因此就有了这篇啦;

什么是钩子

Git Hooks就是在Git执行特定事件(如commit、push、receive等)后触发运行的脚本。

按照Git Hooks脚本所在的位置可以分为两类:

  • 本地Hooks,触发事件如commit、merge等;
  • 服务端Hooks,触发事件如receive等;

钩子能做啥

基本上,用上钩子都是定制化的脚本程序,在实际工作中,基本钩子是万能的,如:

  • post-receive:每当有新的提交的时候就通知项目成员;
  • pre-commit:检查每次的commit message是否符合编码规范;
  • post-merge:当merge成功后,通知项目成员;

钩子放哪里

$project_name/.git/hooks

image.png-81.1kB
.git/hooks/文件夹下。当你init一个仓库的时候,下边会有一些钩子的例子,以.sample结尾。

当然也可以在这个目录下自由定制hooks的功能,当触发一些git行为的时候,就会自动触发执行的hooks功能;

常见钩子

本地钩子:

名称 功能
pre-commit 当执行commit动作时先执行此hook,可以用此hook做一些检查,比如代码风格检查,或者先跑测试;
prepare-commit-msg 当commit时需要输入message前会触发此hook,可以用此hook来定制自己的default message信息;
commit-msg 当用户输入commitmessage后被触发,可以用此hook校验message的信息,比如是否符合规定,有没有cr等;
post-commit 在整个提交过程完成之后会被调用,可以用于发送new commit通知;
post-checkout git checkout命令调用,可以用于为自己的项目设置合适的工作区,比如自动生成文档、移动一些大型二进制文件等,也可以用于检查版本库的有效性;
pre-rebase git rebase命令调用,可以用来拒绝所有的已经push的commits进行rebase操作;
post-merge git merge调用,在merge成功后执行,可以用于merge后的消息通知;
post-receive 在成功推送后被调用,适合用于发送通知;
pre-receive git push调用,向仓库推送代码时会被执行;
update pre-receive之后被调用,分别被每个推送上来的引用分别调用;
pre-push 当push时,remote refs被更新,但是在所有的objects传输前被触发;

服务端钩子:

名称 功能
pre-receive 当收到push动作之前会被执行;
update 收到push动作之前被执行,但是有可能被执行多次,每个branch一次;
post-receive 当push动作已经完成的时候会被触发,可以用此hook来push notification等,比如发邮件,通知持续构建服务器等;

自定义钩子

如果想自定义钩子怎么办? 比如现在新增一个jb钩子,用于发送common通知的;

#!/usr/bin/env python

import smtplib
from email.mime.text import MIMEText
from subprocess import check_output

# 获得新提交的git log --stat输出
log = check_output(['git', 'log', '-1', '--stat', 'HEAD'])

# 创建一个纯文本的邮件内容
msg = MIMEText("Look, I'm actually doing some work:\n\n%s" % log)

msg['Subject'] = 'Git post-commit hook notification'
msg['From'] = 'mary@example.com'
msg['To'] = 'boss@example.com'

# 发送信息
SMTP_SERVER = 'smtp.example.com'
SMTP_PORT = 587

session = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
session.ehlo()
session.starttls()
session.ehlo()
session.login(msg['From'], 'secretPassword')

session.sendmail(msg['From'], msg['To'], msg.as_string())
session.quit()

当然,记得给权限给你钩子,chmod

小结

好啦,本章就到此接触啦,基础内容有点,但是不多,都是环境配置,本章主要讲述以下内容:

  • 搭建jenkins
  • git配置
  • jenkins打通git
  • gitlab hook
  • 检测脚本
  • jenkins通过url触发任务
  • git tag命令部分讲解

大致是这样,还有些细节,差不多就的啦;

好啦,如果有什么疑问,欢迎一起沟通;

最后,谢谢大家~

1-140R3154U8.jpg-9kB