python 自动化创建 confluence 页面三

create_wiki_page.py
from configparser import ConfigParser
import subprocess
from pathlib import Path
import json
from typing import Dict, List, Optional
from datetime import datetime
from collections import defaultdict
import requests
import os
import sys
import glob
from GitLabPipeline import GitLabPipeline
confluence_url = "https://wiki.abc.com"
url = f"{confluence_url}/rest/api/content"
artifactory_url = "https://artifact.abc.com:443/artifactory/"
username = "zhangshan"
api_token = "MTc3MDkxNTUzOTk0OtLLh/239zQ0N/5nZ04UA+bTmzEm"
space_key = "APRICOT"
parent_page_id = 3395495944
# step 查询版本信息,存入version.txt
def query_artifactory(ARTIFACTORY_URL,ARTIFACTORY_TOKEN,SEARCH_PATH):
"""查询Artifactory并保存结果到path.txt"""
cmd = [
"jf", "rt", "search",
SEARCH_PATH,
"--url", ARTIFACTORY_URL,
"--user", "GAYUXIA",
"--password", ARTIFACTORY_TOKEN,
"--insecure-tls=false",
"--recursive"
]
try:
print("正在查询Artifactory...")
result = subprocess.run(
cmd,
check=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
Path("version.txt").write_text(result.stdout, encoding="utf-8")
print("查询结果已保存到 version.txt")
return True
except subprocess.CalledProcessError as e:
print(f"查询失败: {e.stderr}")
raise
def parse_build_paths():
# 读取数据(从 version.txt)
with open("version.txt", "r") as f:
data = json.load(f)
# 初始化结果结构:{ STAR version → build_type → category → set() }
# 这里的结构就是:{ STAR 3.0 -> dev_userdebug -> flat_build/swup -> 去重}
result = defaultdict(lambda: defaultdict(lambda: {"flat_build": set(), "swup": set()}))
def classify_path(path):
# 分类类别:flat_build 或 swup
if "flat_build" in path and path.endswith(".tar.gz"):
category = "flat_build"
elif "swup" in path:
category = "swup"
else:
return None, None, None
# 构建类型分类
if "dev_userdebug" in path or "userdebug" in path:
build_type = "dev_userdebug"
elif "dev" in path or "dev_user" in path:
build_type = "dev_user"
elif "prod-pl_user" in path or "prod-pl" in path:
build_type = "prod-pl_user"
elif "prod-rl_user" in path or "prod-rl" in path:
build_type = "prod-rl_user"
else:
return None, None, None
# 提取 STAR 版本(如 STAR3.0 或 STAR3.5)
if "STAR3.0" in path:
star_version = "STAR3.0"
elif "STAR3.5" in path:
star_version = "STAR3.5"
else:
return None, None, None
return star_version, build_type, category
# 遍历并分类
for entry in data:
path = entry.get("path", "")
star_version, build_type, category = classify_path(path)
if star_version and build_type and category:
if category == "swup":
path = "/".join(path.split("/")[:12]) # 裁剪 swup 路径
result[star_version][build_type][category].add(path)
# 转换为 JSON 可序列化结构(set → list)
clean_result = {
star: {
build: {
kind: sorted([f"https://artifact.abc.com/artifactory/{p}" for p in paths])
for kind, paths in builds.items()
}
for build, builds in star_data.items()
}
for star, star_data in result.items()
}
# 写入结果
with open("classified_builds_by_star.json", "w") as f:
json.dump(clean_result, f, indent=2)
print("分类结果已写入 classified_builds_by_star.json")
# 写入 txt 文件
with open("output.txt", "w", encoding="utf-8") as file:
print_nested_to_file(clean_result, file)
print("已成功导出到 output.txt")
return clean_result
# 导出为纯文本 txt
def print_nested_to_file(data, file, indent=0):
prefix = " " * indent
if isinstance(data, dict):
for key, value in data.items():
file.write(f"{prefix}{key}\n") # 打印当前键(带缩进)
print_nested_to_file(value, file, indent + 1) # 递归处理值(缩进+1)
elif isinstance(data, list):
for item in data:
print_nested_to_file(item, file, indent) # 递归处理列表项(保持缩进)
else:
file.write(f"{prefix}{data}\n")
def create_page():
result_data = parse_build_paths()
# 第2张表格的内容
with open("mr_table.html", "r", encoding="utf-8") as f:
mr_table_html = f.read()
# 第3张表格的内容
with open("test_table.html", "r", encoding="utf-8") as f:
test_table_html = f.read()
# 构建 <tbody> 表格行内容
table_rows = ""
# 计算整个表格总共有多少个 <tr> 行数
total_rows = sum(
sum(len(kinds) for kinds in builds.values())
for builds in result_data.values()
)
first_col_written = False # 控制是否写入第一列
for star, builds in result_data.items():
star_total_rows = sum(len(kinds) for kinds in builds.values())
star_written = False
for build_type, kinds in builds.items():
branches = list(kinds.items())
build_rowspan = len(branches)
for idx, (branch, urls) in enumerate(branches):
archive_urls = "<br>".join([f'<a href="{u}">{u}</a>' for u in urls])
table_rows += "<tr>\n"
# 第一列:仅在第一次写入,并合并所有行
if not first_col_written:
table_rows += f' <td rowspan="{total_rows}">8295_master</td>\n'
first_col_written = True
# VERSION 列
if not star_written:
table_rows += f' <td rowspan="{star_total_rows}">{star}</td>\n'
star_written = True
# BUILD TYPE 列
if idx == 0:
table_rows += f' <td rowspan="{build_rowspan}">{build_type}</td>\n'
# 其余列
table_rows += f" <td>{branch}</td>\n"
table_rows += f" <td>{archive_urls}</td>\n"
table_rows += f" <td></td>\n"
table_rows += f" <td></td>\n"
table_rows += "</tr>\n"
# 构建 HTML 表格内容
page_content = f"""
<h4>构建日期: {BUILD_DATE}</h4>
<h4>@Integration</h4>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>VARSION</th>
<th>ELECTROMECHAINCAL ARCHIECTURE</th>
<th>BUILD TYPE</th>
<th>PACKAGE TYPE</th>
<th>ARCHIVE URL</th>
<th>DECISION</th>
<th>REASON FOR EXTENDING</th>
</tr>
</thead>
<tbody>
{table_rows}
</tbody>
</table>
</div>
<h1 data-nh-numbering="" talk-marker="43" talk-page-id="3221943987" talk-page-version="5" id="Example-MRs:">MRs:</h1>
<p talk-marker="44" talk-page-id="3221943987" talk-page-version="5"><a class="confluence-userlink user-mention userlink-1" data-username="ljinrui" href="/display/~ljinrui" data-linked-resource-id="3010866811" data-linked-resource-version="1" data-linked-resource-type="userinfo" data-base-url="https://wiki.abc.com" title="" data-user-hover-bound="true">li jinrui</a> @integration</p>
{mr_table_html}
<h1 data-nh-numbering="" talk-marker="54" talk-page-id="3407581415" talk-page-version="5" id="Example-Testresults:">Test results:</h1>
<p talk-marker="55" talk-page-id="3407581415" talk-page-version="5"><strong style="letter-spacing: 0.0px;">Test Summary</strong></p>
{test_table_html}
"""
# Confluence API 请求
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_token}"
}
page_data = {
"type": "page",
"title": ESTAND,
"ancestors": [{"id": parent_page_id}],
"space": {"key": space_key},
"body": {
"storage": {
"value": page_content,
"representation": "storage"
}
}
}
response = requests.post(url, headers=headers, data=json.dumps(page_data))
if response.status_code in [200, 201]:
print("页面创建成功")
else:
print(f"页面创建失败: {response.status_code}")
print(response.text)
if __name__ == '__main__':
if len(sys.argv) < 3:
print("\033[1;31m 需要输入3个参数 \033[0m")
print("\033[1;31m 参数一:apricot_rse/i3_RSU/RSE_System_Staging_Build/20250722/E233.0/ & pipeline_url \033[0m")
print("\033[1;31m 参数二:pipeline_url \033[0m")
print("\033[1;31m 参数三:ESTAND \033[0m")
sys.exit(1) # 非零退出码表示错误
# 加载环境变量
config = ConfigParser()
config.read("/home/zhangshan/config.ini")
PRIVATE_TOKEN = config.get("gitlab","token")
GITLAB_URL = config.get("gitlab","url")
ARTIFACTORY_URL = config.get("artifact","url")
ARTIFACTORY_TOKEN = config.get("artifact","token")
# 删除 *.txt 文件
for file_path in glob.glob("*.txt"):
os.remove(file_path)
print(f"已删除 *.txt 文件: {file_path}")
SEARCH_PATH = sys.argv[1]
pipeline_url = sys.argv[2]
ESTAND = sys.argv[3]
query_artifactory(ARTIFACTORY_URL,ARTIFACTORY_TOKEN,SEARCH_PATH)
# now = datetime.now()
# build_date = now.strftime("%Y-%m-%d %H:%M:%S")
fetcher = GitLabPipeline(GITLAB_URL, PRIVATE_TOKEN)
BUILD_DATE = fetcher.get_date_from_pipeline_url(pipeline_url)
create_page()
mr_table.html
<div class="table-wrap">
<table class="wrapped relative-table confluenceTable stickyTableHeaders" style="width: 46.4643%; padding: 0px;">
<thead class="tableFloatingHeaderOriginal">
<tr>
<th scope="col" talk-marker="46" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">
MRS</th>
<th scope="col" talk-marker="47" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">MR
Desicion</th>
</tr>
</thead>
<thead class="tableFloatingHeader" style="display: none;">
<tr>
<th scope="col" talk-marker="46" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">
MRS</th>
<th scope="col" talk-marker="47" talk-page-id="3221943987" talk-page-version="5" class="confluenceTh">MR
Desicion</th>
</tr>
</thead>
<colgroup>
<col style="width: 55.4248%;" />
<col style="width: 44.5844%;" />
</colgroup>
<tbody>
<tr>
<td talk-marker="48" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="37" /></td>
<td rowspan="4" talk-marker="49" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd">
<br talk-br="38" />
</td>
</tr>
<tr>
<td talk-marker="50" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="39" /></td>
</tr>
<tr>
<td talk-marker="51" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="40" /></td>
</tr>
<tr>
<td talk-marker="52" talk-page-id="3221943987" talk-page-version="5" class="confluenceTd"><br
talk-br="41" /></td>
</tr>
</tbody>
</table>
</div>
test_table.html
<div class="table-wrap">
<table class="relative-table wrapped confluenceTable" style="width: 904.0px;" border="1">
<colgroup class="">
<col class="" style="width: 385.0px;" />
<col class="" style="width: 518.0px;" />
</colgroup>
<tbody class="">
<tr class="">
<td style="text-align: left;vertical-align: top;" talk-marker="56" talk-page-id="3407581415"
talk-page-version="5" class="confluenceTd"><br talk-br="43" /></td>
<td style="text-align: left;vertical-align: top;" talk-marker="57" talk-page-id="3407581415"
talk-page-version="5" class="confluenceTd"><br talk-br="44" /></td>
</tr>
</tbody>
</table>
</div>
GitlabPipelineClient.py
import requests
from datetime import datetime
from urllib.parse import urlparse, unquote
class GitlabPipelineClient:
def __init__(self, pipeline_url: str, private_token: str):
"""
GitLab 客户端:自动解析 pipeline_url,简化调用
:param pipeline_url: 完整的 pipeline 页面 URL,例如:
https://git.abc.com/group/project/-/pipelines/123456
:param private_token: GitLab 私有令牌(需要 read_api 权限)
"""
self.pipeline_url = pipeline_url
self.private_token = private_token
self.headers = {"PRIVATE-TOKEN": private_token}
# 解析 URL
self.base_url, self.project_path, self.pipeline_id = self._parse_pipeline_url(pipeline_url)
def _parse_pipeline_url(self, url: str):
"""
解析 pipeline URL,提取 base_url、project_path 和 pipeline_id
"""
parsed = urlparse(url)
path_parts = parsed.path.strip("/").split("/")
if "-/pipelines" not in url:
raise ValueError("URL 不是合法的 GitLab pipeline 链接")
try:
# 提取 pipeline_id(最后一个部分)
pipeline_id = int(path_parts[-1])
# 提取项目路径(去掉最后的 ['-', 'pipelines', '{id}'] 三段)
project_path_parts = path_parts[:-3]
project_path = "/".join(project_path_parts)
base_url = f"{parsed.scheme}://{parsed.netloc}"
except Exception:
raise ValueError("无法解析 pipeline URL")
return base_url, unquote(project_path), pipeline_id
def _get(self, endpoint: str) -> dict:
"""
发送 GET 请求
"""
url = f"{self.base_url}{endpoint}"
response = requests.get(url, headers=self.headers)
if response.status_code == 200:
return response.json()
else:
raise Exception(f"请求失败: {response.status_code}\n{response.text}")
def get_pipeline_info(self) -> dict:
"""
获取 pipeline 的详细信息
"""
from urllib.parse import quote
encoded_project = quote(self.project_path, safe="")
endpoint = f"/api/v4/projects/{encoded_project}/pipelines/{self.pipeline_id}"
return self._get(endpoint)
def get_pipeline_date(self) -> str:
"""
获取 pipeline 的构建日期(如 "2025-07-24")
"""
info = self.get_pipeline_info()
created_at = info.get("created_at")
if not created_at:
raise ValueError("pipeline 中没有 created_at 字段")
dt = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
return dt.date().isoformat()
python 自动化创建 confluence 页面三的更多相关文章
- appium+python自动化46-安装app三种方式
前言 adb安装 1.在app自动化之前,首先手机上有要被测试的app,如何把电脑本地上的app安装到手机上呢?可以在运行自动化代码前,在cmd输入adb指令,把电脑app安装到手机上 adb ins ...
- selenium+python自动化102-登录页面滑动解锁(ActionChains)
前言 登录页面会遇到滑动解锁,滑动解锁的目的就是为了防止别人用代码登录(也就是为了防止你自动化登录),有些滑动解锁是需要去拼图这种会难一点. 有些直接拖到最最右侧就可以了,本篇讲下使用 seleniu ...
- Python 自动化脚本学习(三)
函数 例子 def hello(): print("hello" + "world"); 有参数的函数 def hello(name): print(" ...
- python自动化工具之pywinauto(一个实例)结合pyuserinput
以下是pywinauto使用指南.这个窗口句柄可以在Spy++中查看 (Microsoft Spy++(查看窗口句柄) 10.00.30319 官方最新绿色版) python自动化工具之pywinau ...
- python selenium自动化点击页面链接测试
python selenium自动化点击页面链接测试 需求:现在有一个网站的页面,我希望用python自动化的测试点击这个页面上所有的在本窗口跳转,并且是本站内的链接,前往到链接页面之后在通过后退返回 ...
- RobotFramework + Python 自动化入门 三 (Web自动化)
在<RobotFramwork + Python 自动化入门 一>中,完成了一个Robot环境搭建及测试脚本的创建和执行. 在<RobotFramwork + Python 自动化入 ...
- javascript 中数组的创建 添加 与将数组转换成字符串 页面三种提交请求的方式
创建js数组 var array=new Array(); Java中创建数组 private String[] array=new String[3]; 两个完全不同的,js中是可变长度的 添加内容 ...
- Selenium2+python自动化39-关于面试的题
前言 最近看到群里有小伙伴贴出一组面试题,最近又是跳槽黄金季节,小编忍不住抽出一点时间总结了下, 回答不妥的地方欢迎各位高手拍砖指点. 一.selenium中如何判断元素是否存在? 首先selen ...
- Docker+jenkins 运行 python 自动化
一.实现思路 在 Linux 服务器安装 docker 创建 jenkins 容器 根据自动化项目依赖包构建 python 镜像(构建自动化 python 环境) 运行新的 python 容器,执行 ...
- Selenium Web 自动化 - 项目实战(三)
Selenium Web 自动化 - 项目实战(三) 2016-08-10 目录 1 关键字驱动概述2 框架更改总览3 框架更改详解 3.1 解析新增页面目录 3.2 解析新增测试用例目录 3. ...
随机推荐
- MongoDB从入门到实战之Windows快速安装MongoDB
前言 本章节的主要内容是在 Windows 系统下快速安装 MongoDB 并使用 Navicat 工具快速连接. MongoDB从入门到实战之MongoDB简介 MongoDB从入门到实战之Mong ...
- 浅析鸿蒙(ark runtime)执行动态代码
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- CentOS安装SFTP
1.创建专用用户组 sudo groupadd sftpgroup # 创建组用于统一管理SFTP用户[1,6](@ref) 2.添加用户并限制Shell sudo useradd -m -d /d ...
- 网络编程:poll
原理 和select类似,只是描述fd集合的方式不同,poll使用pollfd结构而非select的fd_set结构. 管理多个描述符也是进行轮询,根据描述符的状态进行处理,但poll没有最大文件描述 ...
- k8s service访问偶发超时问题
问题现象 在某个集群节点上的服务访问service服务:端口,会出现偶发timeout的问题,集群有的节点不会出现访问timeout的问题 问题处理 查看bridge-nf-call-iptables ...
- equals与==与hashCode的区别与联系
equals与hashcode的区别与联系 1."=="与equals的区别与联系 (1)"=="对于基本数据类型,只要值相等,就返回true,否则返回fals ...
- 关于Design Review 的一些思考
开发流程 这篇文章记录一些我对Design Review 的一些思考,下面是我当下对开发流程的理解: 开发流程: 收到需求 需求分析 设计分析 项目排期 项目开发 测试环境测试 线上回归测试 上线观察 ...
- ko在数栈中的应用
引言 一项技术能得以广泛运用,其中的一个关键点在于工程化.前端从最开始的简单写写网页和样式,发展为需要处理复杂的逻辑,伴随而来的是问题是相关文件越来越多,简单在网页中引用已经解决不了问题,需要相关 ...
- Altair官方文档——HyperMesh的使用与帮助
1.1.3 启动 HyperMesh (1) On PC • 从起始菜单,选择 All Programs >Altair HyperWorks (version) > HyperMesh ...
- Linux在线安装JDK1.8+
Linux在线安装JDK1.8+(默认已发布最新版) 命令在线下载jdk: wget --no-check-certificate --no-cookies --header "Cookie ...