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. ...
随机推荐
- 鸿蒙NEXT实践(二):公共事件通信实践-智能节电案例
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- AI法律助手:打造普惠法律服务的未来
当法律服务遇见人工智能,普通人的维权之路将不再艰难 当法律服务成为奢侈品,AI或许是唯一出路 2025年的一个深夜,我刷着手机,一条新闻让我停下了滑动的手指: "某平台家装工人因合同纠纷讨薪 ...
- Web前端入门第 56 问:JavaScript 变量声明 var、let、const 区别
曾经 var 如帝王一般统治过 JS 的变量声明,直到后来大佬们实在是忍不了 var 那稀烂的声明规则,便引入了 let 和 const 这两大刀斧手,var 被轻轻松松的斩落马下,如今,再难看见 v ...
- Git 查看 tag 标签详解
摘要:介绍git中tag标签的使用方法,包括创建标签.提交标签.查询标签和删除标签等. 我们拿到一个即将投产的标签后,需要确认标签是否打在了正确的分支,故需要查看标签的详情信息,保证顺利上线.基于此背 ...
- Mysql数据类型TINYINT(1)与布尔型的坑
需求背景:从MySQL数据库读取1.2.3.4等阿拉伯数字定义的状态,并转换成Java中Integer类型的数据,但是转换失败了. 问题分析:布尔型 bool 或者 boolean 在My ...
- Leangoo助力医药行业项目降本增效
医药行业痛点诸多,制药研发周期长.生物技术创新协同难.医疗器械研发生产衔接不畅.医疗保健服务流程繁琐.Leangoo 可化解困境,促各领域信息共享.流程优化.协同增效,提升效率与质量,推动医药行业整体 ...
- SAP UI类标准导出XML格式Excel
DATA: gt_fieldcatalog TYPE lvc_t_fcat, gs_fieldcatalog TYPE lvc_s_fcat, lr_data TYPE REF TO data, r_ ...
- 直播预告丨《Flink提交流程&如何debug和跟踪流程(on yarn)》
4月20日晚19点30分,袋鼠云数栈技术研发团队开发工程师--莫问,将会为大家直播分享<Flink提交流程&如何debug和跟踪流程(on yarn)>. 课程内容主要包括以下三点 ...
- 大数据计算引擎 EasyMR 如何简单高效管理 Yarn 资源队列
设想一下,作为一个开发人员,你现在所在的公司有一套线上的 Hadoop 集群.A部门经常做一些定时的 BI 报表,B部门则经常使用软件做一些临时需求.那么他们肯定会遇到同时提交任务的场景,这个时候到底 ...
- 4-torchvision数据集使用
1. torchvision数据集介绍 ① torchvision中有很多数据集,当我们写代码时指定相应的数据集指定一些参数,它就可以自行下载. ② CIFAR-10数据集包含60000张32×32的 ...