Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求

  • 环境配置

  1. Ubuntu版本:Ubuntu 20.04.2 LTS
  2. Jenkins版本:2.303.3
  3. Gogs版本:0.12.3
  • 安装Jenkins必要插件

      1.安装Generic Webhook Trigger Plugin插件,方便监听Gogs的webhook。

      2.安装Python Plugin插件,方便在构建时执行shell脚本(间接执行python脚本)

  • 构建一个git push能触发的jenkins任务

  • 新建Jenkins任务并配置参数。

        在Jenkins首页,点击“新建任务”,输入任务名称,一般用“项目名+操作命名”,例如“project_name-push”表示仓库在push时触发此任务,点击“构建一个自由风格的软件项目”,然后确定即可创建一个新的任务,进入到任务配置界面,到”构建触发器”项的设置,勾选“Generic Webhook Trigger”,在“Post content parameters中,设置Variable为ref,Expression设置为@.ref ,如下图所示:

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第1张图片

然后在Header parameters设置中,设置Request header为X-Gogs-Event,在Request parameters设置Token,这个token在gogs的web hook中有用。如下图所示:

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第2张图片

在Optional filter设置项,设置任务相应的事件,此处只响应仓库master分支的push事件,可根据实际情况修改。

具体设置为,Expression设置成:push_refs/heads/master,Text设置成:$x_gogs_event_$ref。如下图所示:

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第3张图片

最后保存。

  • 设置Gogs仓库Web 钩子。

        Gogs登录后,在项目代码界面,点击右上角的“仓库设置”—“管理Web钩子”—“添加新的Web钩子”,选择“Gogs”类型。在配置界面,推送地址设置成:http://jenkins_ip:port/generic-webhook-trigger/invoke?token=xxx

将地址中的jenkins_ip:port和xxx替换为实际使用的jenkins地址和端口,以及在上述中jenkins任务中设置的Token。数据格式为:application/json,密钥文本不填,web钩子时间设置成:只推送push事件,勾选是否激活,最后点击“添加Web钩子”按钮。配置项如下图所示:

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第4张图片

  • Ubuntu配置Python运行环境

    • 安装Python脚本运行需要的selenium库和chromedriver

        Ubuntu自带Python 3.8.10,只要安装脚本运行需要的的Selenium库和谷歌浏览器以及chromedriver即可。

1、安装chrome,我安装的版本是95.0.4638.69,以下命令即可安装最新版,命令如下

sudo apt-get install libxss1 libappindicator1 libindicator7

wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb

sudo dpkg -i google-chrome*.deb

google-chrome --version

当查看版本没问题,说明安装成功了。

2、下载chromedriver

#执行如下命令(第一条用于获取最新版本号,第二条用于下载):LATEST=$(wget -q -O - http://chromedriver.storage.googleapis.com/LATEST_RELEASE)wget http://chromedriver.storage.googleapis.com/$LATEST/chromedriver_linux64.zip# 解压unzip chromedriver_linux64.zip# 修改权限chmod +x chromedriver# 可以移动driver 省的指定位置sudo mv chromedriver /usr/bin/# 查看版本./chromedriver --version

如果最后一条命令显示如下说明没问题了。

ChromeDriver 95.0.4638.69 (6a1600ed572fedecd573b6c2b90a22fe6392a410-refs/branch-heads/4638@{#984})

3、安装selenium

1) 安装python3-pip,命令: sudo apt install python3-pip;

2) 安装selenium,命令:pip install selenium

若提示ERROR: pyopenssl 21.0.0 has requirement cryptography>=3.3, but you'll have cryptography 2.8 which,则去https://pypi.org/simple/pyopenssl/地址下载pyOpenSSL-21.0.0-py2.py3-none-any.whl文件,cd到文件目录,并执行以下命令安装:

pip install pyOpenSSL-21.0.0-py2.py3-none-any.whl

  • 测试测试selenium库和chromedriver是否正常工作

      输入以下命令:

      python3 #进入python命令行模式

      from selenium import webdriver #导入selenium的webdriver库

      driver = webdriver.Chrome() #使用谷歌浏览器,会自动打开谷歌浏览器

      driver.get("https://www.baidu.com/") #打开网页,浏览器打开百度首页

      print(driver.title) #显示网页标题,即“百度一下,你就知道”

以上命令正确执行后,表示selenium库正确安装,且chromedriver正常使用。

  • Jenkins配置执行python脚本需要的参数

  1. 任务参数修改。

        打开上述第三点中创建的jenkins任务,并进入配置界面,在General选项卡中,勾选“参数化构建过程”,添加五个选项参数,分别是projectName、projectOwner、sourceBranch、targetBranch、codeReviewer,表示仓库名称、仓库拥有者名(创建仓库时的 “拥有者”名称)、要合并的源分支,合并的目标分支、审核者(合并请求中的指派成员),例如projectName配置如下图所示。

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第5张图片

为了让合并请求的标题和内容都设置成仓库的提交信息,在“构建触发器”—Post content parameters中再添加两个参数Variable分别是pullRequestTitle、pullRequestContent,Expression都设置成$.commits[0].message,格式为:JSONPath,表示合并请求标题、合并请求内容(创建者的评论),配置如下图所示。

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第6张图片

在“构建”选项卡中增加构建步骤,选择增加执行shell的步骤,配置如下图所示:

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第7张图片

内容:python3 /home/当前用户名/gogs/AutoCreateGogsPullRequest.py $projectName $projectOwner $sourceBranch $targetBranch $codeReviewer "$pullRequestTitle" "$pullRequestContent" 192.168.197.129:3303

命令共8个参数,每个参数之间空格分开,若是参数值有空格,则用双引号将字符串引起来,最后的地址为gogs的访问地址,参数顺序不能变,参数值中最好不要有换行。

2. python脚本执行相关的修改。

1) 将脚本AutoCreateGogsPullRequest.py文件放到/home/当前用户名/gogs/目录下。

2) 修改脚本文件权限,使用Gogs的ubuntu用户需要有执行权限,执行命令:sudo chmod -R 777 /home/当前用户名/gogs/AutoCreateGogsPullRequest.py。

3) 为了防止jenkins在执行脚本时,报找不到python的第三方库错误:ModuleNotFoundError: No module named 'selenium',原因是jenkins的用户是jenkins,python库是当前用户,属于权限问题导致,需要修改python安装的第三方包的用户和所属组,执行命令:sudo chown jenkins:jenkins /home/gerrit/.local/lib/python3.8/site-packages,其中site-packages是python第三方所在位置,注意:修改site-packages用户后,在系统命令行执行脚本将出现找不到模块问题。

4) 确保AutoCreateGogsPullRequest.py文件中chromedriver path,正确,如下图所示,若按本文章安装的chromedriver,则无需修改。这么设置是为了防止jenkins执行脚本时出现:selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: crashed.错误。

Ubuntu上实现Gogs+Jenkins在代码提交时自动创建合并请求_第8张图片

5.修改脚本中登录函数中的用户名和密码,该用户有管理员权限,有创建所有仓库的合并请求权限 。

3. AutoCreateGogsPullRequest.py代码。

import webbrowser
import time
import sys
import os

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains

#判断是否已经登录
def isLogin(driver):
	if driver is not None :
		try:
			driver.find_element(By.ID, "user_name")
			print("isLogin() return false")
			return False
		except NoSuchElementException as e:
			print("isLogin() return true")
			return True
	return False
#去登录 drive 在登录界面
def login(drive):
	if driver is not None :
		try:
			#填登录的用户名,带管理员权限
			driver.find_element(By.ID, "user_name").send_keys(u"admin")
			#填登录的密码
			driver.find_element(By.ID, "password").send_keys(u"12345678")
			# name带空格,则空格用.代替即可,全名为:ui green button
			# see https://blog.csdn.net/yunmengdan/article/details/111614913
			driver.find_element(By.CLASS_NAME, "ui.green.button").click()
			print("Login operate finished")
		except:
			print("login operate error")
	return

#项目名
projectName = str(sys.argv[1]).replace("\n", "")
#项目拥有者
projectOwner = str(sys.argv[2]).replace("\n", "")
#要合并的源分支
sourceBranch = str(sys.argv[3]).replace("\n", "")
#合并的目标分支
targetBranch = str(sys.argv[4]).replace("\n", "")
#代码审核的人名(登录名)
codeReviewer = str(sys.argv[5]).replace("\n", "")
#合并请求的标题,因为这个值是commit message,带\n,所以必须替换掉制表符\n,否则会出现selenium.common.exceptions.UnexpectedAlertPresentException:错误
pullRequestTitle = str(sys.argv[6]).replace("\n", "")
#合并请求的内容,因为这个值是commit message,带\n,所以必须替换掉制表符\n,否则会出现selenium.common.exceptions.UnexpectedAlertPresentException:错误
pullRequestContent = str(sys.argv[7]).replace("\n", "")
#gogs服务器地址
gogsUrl = str(sys.argv[8]).replace("\n", "")
#若driver send_keys 方法出现莫名其妙的错误,则可能是send_keys参数中的值带特殊符号,比如\n

#创建合并请求的url
newPullRequestUrl = "http://" + gogsUrl + "/" + projectOwner + "/" + projectName + "/compare/" + targetBranch + "..." + projectOwner + ":" + sourceBranch
print("newPullRequestUrl=" + str(newPullRequestUrl) + ", pullRequestTitle=" + pullRequestTitle + ", pullRequestContent=" + pullRequestContent)

#解决selenium.common.exceptions.WebDriverException: Message: unknown error: Chrome failed to start: crashed.
#参照 https://www.cnblogs.com/jingzaixin/p/11570444.html
chrome_options = webdriver.ChromeOptions()   #为驱动加入无界面配置
chrome_options.add_argument('--headless')   #–headless”参数是不用打开图形界面
chrome_options.add_argument('--no-sandbox')  #“–no - sandbox”参数是让Chrome在root权限下跑
#chrome_options.set_capability("unhandledPromptBehavior","accept")
path = r"/usr/bin/chromedriver"  #配置驱动路径
print("chromedriver path:%s" % path)
driver = webdriver.Chrome(executable_path=path,chrome_options=chrome_options)
driver.maximize_window()   #窗口最大化


try:
	# 打开新建合并请求界面,若用户未登录,会自动跳转到登录界面
	driver.get(newPullRequestUrl)
	isLogin = isLogin(driver)
	if isLogin == False:
		login(driver)
	print("user has login")
	# 登录后
	# 判断是否已经有合并请求,或者两分支是否已经同步
	try:
		# 名字全词匹配,使用这种方法,否则会按文档顺序去找到第一个包含名字的元素
		driver.find_element(By.XPATH, "//*[@class='ui segment']")
		print("has created pull request, can not new a pull request")
		#关闭浏览器
		driver.close()
		# 已经有合并请求,或不需要合并操作,则不做其他操作,直接退出
		sys.exit(0)
	except NoSuchElementException as e:
		print("no has created pull request, can new a pull request")
	# 设置合并请求的“标题”
	driver.find_element(By.NAME, "title").send_keys(pullRequestTitle)
	# 设置“内容编辑”的内容
	driver.find_element(By.XPATH, "//*[@class='ui bottom attached active tab segment']/textarea").send_keys(pullRequestContent)
	# 点击“指派的成员”按钮,弹出可选的评审人员(必须要此步骤,否则下一步的c.click()操作将失败)
	#driver.find_element(By.XPATH, "//*[@class='ui  floating jump select-assignee dropdown']/span").click()
	selectReviewerParent = driver.find_element(By.XPATH, "//*[@class='ui  floating jump select-assignee dropdown']")
	#selectReviewerParent = driver.find_elements(By.CLASS_NAME, "select-assignee")[0]
	selectReviewerParent.find_element(By.XPATH, "./span").click()
	# 找到可以评审的成员列表控件的父元素
	listParent = selectReviewerParent.find_element(By.XPATH, "./div")
	# 找到所有可评审成员
	childrens = listParent.find_elements(By.XPATH, "./*")
    #控件操作类
	actionChainsDriver = ActionChains(driver)
	for c in childrens:
		#标签中夹着的内容,包含可用评审人员名称
		divInnerContent = str(c.get_attribute("innerHTML"))
		#print("c innerHTML=" + divInnerContent)
		# 跟想要的评审人员名字对比,相同,则选择。
		if divInnerContent.find(codeReviewer) >= 0:
            #移动到要选择的审核者位置
			actionChainsDriver.move_to_element(c)
			actionChainsDriver.perform()
			c.click()
	# 点击 “创建合并请求”按钮
	driver.find_element(By.XPATH, "//*[@class='ui green button']").click()
	print("create pull request finished")
except NoSuchElementException as e:
	print("create pull request error=" + e.msg)
#关闭浏览器
driver.close()
sys.exit(0)

2021.11.15,修改python代码,解决有时候无法选择审核者问题。 

参考文章

https://blog.csdn.net/xiaosongluo/article/details/80151474

https://www.jerrycoding.com/article/linux-selenium/

https://www.cnblogs.com/jingzaixin/p/11570444.html

https://blog.csdn.net/longhtml/article/details/102861343

你可能感兴趣的:(Gogs,jenkins,ubuntu,git)