一、引言
上一篇文章之后 我们应该已经成功完成的配置了扫描环境并执行了一次基本的本地扫描,但是之前的手动扫描需要我们每一次都手动切换到代码目标并手动执行扫描命令,效率很低。在代码库较大的情况下会占用大量的时间。这一章我们会通过编写python代码的形式来实现自动化的本地扫描。 本篇需要使用的基本工具
- python3
- linux下的shell脚本
- crontab定时任务
二、代码创建扫描项目
sonarqube官方提供了大量的api接口供我们使用,我们可调用这些接口来实现自动化创建。 通过这个地方就能查看当前安装版本支持的各种api接口,这里我们需要调用/api/projects/create
- mainBranch为项目分支,非必填,默认为main分支
- name为项目的名字,必填,可重复
- project为项目的唯一标识,必填,不可重复
- visibility为项目的公开属性,用于项目管理,非必填,默认为公开,即所有sonarqube用户都能查看该项目
代码示范,一次成功的项目创建实现如下:
python
user ='admin'
password = 'admin'
sonarqube_url ='url'
#创建sonarqube项目
def create_sonarqube_project(sonarqube_url, project_name, username, password):
api_endpoint = sonarqube_url + "/api/projects/create"
data = {
"project": project_name,
"name": project_name
}
try:
response = requests.post(api_endpoint, data=data, auth=HTTPBasicAuth(username, password))
response.raise_for_status() # 抛出异常如果响应不是 200 OK
print(f"项目 '{project_name}' 创建成功!")
return True
except requests.exceptions.RequestException as e:
print("创建项目失败:", e)
return False
- 创建项目同样需要授权,这里我们使用了auth=HTTPBasicAuth(username, password) 来实现,这里的账号信息就是对应sonarqube的账号密码,当然你也可以通过生成令牌的方式来实现授权,这里没有列举。
- 这里的代码中为了方便查看,将项目的name和key设置为同样的值
- 如果项目创建成功,会返回200
- 如果认证错误,会返回401
- 如果项目存在,会返回400
三、代码执行本地扫描
python3
#调用sonarqube扫描mvn代码
def exec_mvn_scan(path,project):
command =f'''mvn clean verify sonar:sonar \
-Dsonar.projectKey={project} \
-Dsonar.host.url=url \
-Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
'''
working_dir = path
result = subprocess.run(command, cwd=working_dir, shell=True)
# 检查执行结果
if result.returncode == 0:
print("命令执行成功!")
else:
print("命令执行失败!")
- path的值为项目所在的根目录
- subprocess执行系统命令会输出命令的执行结果,如果失败了 可以分析一下具体的错误原因,多半是由于配置错误导致的
- Dsonar.host.url 的值为sonarqube的访问地址
- Dsonar.login的值为文章二中创建的项目令牌(不是用户令牌)
- Dsonar.projectKey的值为上一步创建项目时输入的唯一key
- 如果命令成功的话,打开sonarqube就会获得执行结果。
- 这里是不需要执行登录操作,因为命令中的项目令牌已经包含了代码扫描权限。 上面是以maven配置的代码来举例的,sonarqube支持的其他几种代码配置环境扫描代码也放在下方 供大家按需拿取。
python3
#调用sonarqube扫描gradle代码
def exec_gradle_scan(path,project):
command =f'''./gradlew sonar \
-Dsonar.projectKey={project} \
-Dsonar.host.url=url \
-Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
'''
working_dir = path
result = subprocess.run(command, cwd=working_dir, shell=True)
# 检查执行结果
if result.returncode == 0:
print("命令执行成功!")
else:
print("命令执行失败!")
#调用sonarqube扫描.net代码
def exec_net_scan(path,project):
command =f'''./gradlew sonar \
-Dsonar.projectKey={project} \
-Dsonar.host.url=url \
-Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
'''
working_dir = path
result = subprocess.run(command, cwd=working_dir, shell=True)
# 检查执行结果
if result.returncode == 0:
print("命令执行成功!")
else:
print("命令执行失败!")
#调用sonarqube扫描非maven、gradle、net类型的代码
def exec_other_scan(path,project):
command =f'''sonar-scanner \
-Dsonar.projectKey={project} \
-Dsonar.sources=. \
-Dsonar.host.url=url \
-Dsonar.login=sqa_2cb36aeb59566dbc633bfc10b27371c732******
'''
working_dir = path
result = subprocess.run(command, cwd=working_dir, shell=True)
# 检查执行结果
if result.returncode == 0:
print("命令执行成功!")
else:
print("命令执行失败!")
四、代码执行批量扫描
前面2步我们已经通过代码实现了项目自动创建和自动扫描的操作,现在我们需要将2者结合起来。 假设这样一个场景:
-
项目备份代码存在本地。
-
项目按照不同的业务线进行分类,不同的业务线分别有各自的文件夹。
文件目录结构如上图所示,我们可以构造一个简单的文件遍历函数并结合前2步的代码来执行一次完整的代码库批量扫描
python
path = '/代码库'
def main(path):
businesses = os.listdir(path)
for business in businesses:
biz_path = os.path.join(path,business)
if os.path.isdir(biz_path):
projects = os.listdir(biz_path)
for project in projects:
pro_path = os.path.join(biz_path,project)
if os.path.isdir(pro_path):
create_sonarqube_project(sonarqube_url, project, user, password)
if os.path.isfile(pro_path+'/pom.xml'):
conf_pom(pro_path+'/pom.xml')
exec_mvn_scan(pro_path,project)
elif os.path.isfile(pro_path+'/build.gradle'):
conf_gradle(pro_path+'/build.gradle')
exec_gradle_scan(pro_path,project)
elif os.path.isfile(pro_path+'/.net'):
exec_net_scan(pro_path,project)
else:
exec_other_scan(pro_path,project)
- path对应的是树状图中的代码库目录地址
- 该函数会检查不同项目中的配置文件名称来选择合适的扫描函数
- 相关的全局变量在之前几步中已包含 这里就不再重复了。
至此一次完整的针对公司业务代码库的批量本地扫描就完成了。后面还会介绍一些其他的一些补充内容,有需要的可以自行观看~
五、pom.xml文件的配置问题
上文中的代码已经可以成功的执行批量的代码扫描了,但是在实际业务中,项目的pom.xml文件并没有正确的配置代码仓库,需要我们手动添加。接下来我们会通过代码来实现这个操作。 在第二篇文章中 我们在配置maven仓库中给出了需要在xml文件中添加的内容。
xml
<repositories>
<repository>
<id>nexus-public</id>
<url>https://url/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
下面是具体的实现代码。
python3
namespace_uri = ''
def traverse_xml(element, depth=0):
# 打印当前元素的标签
#print(' ' * depth + element.tag)
if 'repositories' in element.tag:
global namespace_uri
namespace_uri = element.tag
return
# 遍历当前元素的子元素
for child in element:
traverse_xml(child, depth + 1)
#配置pom.xml文件 增加内部环境配置
def conf_pom(path):
# 解析 POM 文件
tree = ET.parse(path)
root = tree.getroot()
# 检查根元素是否有xmlns属性,这通常用于定义默认命名空间
traverse_xml(root)
print(namespace_uri)
# 查找所有 <repositories> 元素
if namespace_uri != '':
return
else:
# 如果找到至少一个 <repositories> 元素,返回 True
# 创建要插入的 <repositories> 元素及其子元素
repositories = ET.fromstring('''
<repositories>
<repository>
<id>nexus-public</id>
<url>https://nexus.xxx.com/repository/maven-public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
''')
root.append(repositories)
# 移除命名空间前缀
for elem in root.iter():
if '}' in elem.tag:
elem.tag = elem.tag.split('}', 1)[1]
# 创建 ElementTree 对象,并指定默认的命名空间前缀为 None
tree = ET.ElementTree(root)
# 将修改后的 XML 写入新文件中
tree.write(path, encoding='utf-8', xml_declaration=True,default_namespace='')
这段代码演示了如何检测缺少repositories的xml文件,并给他添加需要的参数。 正常情况下我们会通过root.find('.//repositories') 来搜索repositories,但是如果xml文件中声明了命名空间,该函数就无法正常匹配结果。因此这里使用了traverse_xml(element, depth=0) 来遍历所有的xml节点实现查找。 输出如图,可以看出,xml文件中的元素实际被包含在{maven.apache.org/POM/4.0.0}的... 如果想通过自带的正则匹配方法应该选用
lua
root.find('.//{http://maven.apache.org/POM/4.0.0}repositories')
我们也可以注意到代码中还包含移除命名空间前缀的操作,因为我们通过代码直接插入操作之后,会给所有的节点显式ns0的命名空间,需要主动删除。
到这里sonarqube相关的使用介绍就到此结束了,后续如果还有深入操作的话,还会继续进行更新,感谢大家观看~