python获取dockerhub仓库最新版本的镜像
有些docker仓库的latest镜像仅表示最后上传的镜像版本,不是实际上的最新版本程序(比如gitlab/gitlab-ce),为了自动获取docker仓库里符合我们要求的镜像,写了一个脚本。
思路分析
获取符合要求的镜像,那么很显然要分为三部分实现:
- 获取所有镜像标签
- 筛选符合要求的镜像
- 拉取镜像
其中,获取所有镜像标签简单调用API即可获取,拉取镜像也能通过执行本地命令进行,筛选镜像则需要配置好过滤器。
因此,决定实现两个类:过滤器、仓库获取器及两个过程函数用于加载配置和执行拉取过程。
代码实现
先设计好配置文件,想实现以上功能,需要知道镜像仓库的地址、过滤条件,其中过滤条件包括几种过滤方法,设计如下:
libraries:
- gitlab:
repo: "gitlab/gitlab-ce"
base_host: "" # 基础仓库链接,默认为dockerhub
tag_filter: # 过滤器
exclude: # tag中不出现的词
- "14."
- "latest"
include: # tag中必须包含的词,任选其一
- "15.11"
- "15.10"
py_code: # 作为参数输入后执行结果为true的tag,需要有名字
- "lambda remote_tag: list(map(int, '15.11.0-ce.0'.replace('-ce', '').split('.'))) < list(map(int, remote_tag.replace('-ce', '').split('.')))"
regex: # 符合正则表达式的tag,re.match(regex,str)返回true的str
- ".*ee.*"
- openvas:
repo: "immauss/openvas"
base_host: "" # 基础仓库链接,默认为dockerhub
tag_filter: # 过滤器
exclude:
- "" # tag中不出现的词
include:
- "latest" # tag中必须包含的词
py_code:
- "" # 作为参数输入后执行结果为true的tag
regex:
- "" # 符合正则表达式的tag
对于筛选方式,简单说明如下:
- exclude: tag中不存在的字符串
- include: tag中至少包含1个的字符串
- py_code: 一个参数为获取到的镜像tag的lambda函数,返回true时满足过滤条件;如果有多个lambda函数,返回需要满足全部才能通过筛选。
- regex: 正则表达式,满足所有正则表达式的tag才能通过筛选
过滤器
class Tag_Filter:
exclude: list = [] # tag中不能出现的词,全都不允许出现
include: list = [] # tag中必须包含的词,任一满足即可通过筛选
py_code: list = [] # tag需要满足的python判断方法,满足所有判断方法才能通过筛选
regex_filter: list = [] # 符合正则表达式的tag,需要满足所有正则表达式才能通过筛选
def __init__(self) -> None:
self.exclude = []
self.include = []
self.py_code = []
self.regex_filter = []
return
# 定义一个类的构造函数,它接受一个 `tag_filter` 字典作为参数。
# 在构造函数中,我们从 `tag_filter` 字典中获取了四个列表:`exclude`、`include`、`py_code` 和 `regex`。
# 对于每个列表,我们都使用列表推导式来过滤掉空字符串,并将结果赋值给相应的实例变量。
def __init__(self, tag_filter: dict) -> None:
# 从 tag_filter 字典中获取 exclude 列表,并过滤掉值为空字符串的元素
# Get the exclude list from the tag_filter dictionary and filter out element which is empty strings
self.exclude = [
word
for word in list(tag_filter["exclude"])
if word != ""
]
# 从 tag_filter 字典中获取 include 列表,并过滤掉值为空字符串的元素
# Get the include list from the tag_filter dictionary and filter out element which is empty strings
self.include = [
word
for word in list(tag_filter["include"])
if word != ""
]
# 从 tag_filter 字典中获取 py_code 列表,并过滤掉值为空字符串的元素
# Get the py_code list from the tag_filter dictionary and filter out element which is empty strings
self.py_code = [
code
for code in list(tag_filter["py_code"])
if code != ""
]
# 从 tag_filter 字典中获取 regex 列表,并过滤掉值为空字符串的元素
# Get the regex list from the tag_filter dictionary and filter out element which is empty strings
self.regex_filter = [
regex for regex in list(tag_filter["regex"])
if regex != ""
]
return
仓库获取器
class Image_Getter:
repo: str = ""
base_host: str = ""
tag_filter: Tag_Filter = None
# Constract func by None
def __init__(self) -> None:
self.repo = ""
self.base_host = ""
self.tag_filter = None
return
# Constract func by config(type dict)
def __init__(self, config: dict) -> None:
self.repo = config["repo"]
base_host = config['base_host']
if not base_host.endswith("/") and base_host != "":
base_host += "/"
self.base_host = "https://registry.hub.docker.com/v2/repositories/" if base_host == "" else base_host
self.tag_filter = Tag_Filter(config['tag_filter'])
return
以上是声明部分和构造函数。
接下来定义一个名为 get_remote_tags
的成员函数,它用来获取远程标签并进行过滤。函数首先构造一个 URL,然后使用 requests
模块来获取远程标签。获取到的标签会根据正则表达式进行过滤,只保留符合条件的标签。接下来,函数会获取 include
、exclude
和 py_code
列表。如果 include
和 exclude
都为空,则直接返回过滤后的标签。否则,函数会进一步过滤标签,只保留符合 include
、exclude
和 py_code
条件的标签。
def get_remote_tags(self) -> list:
# 构造获取仓库的标签的 URL
# Construct the URL to get remote tags
url = f"{
self.base_host}{
self.repo}/tags?page_size=50"
# 获取远程仓库的标签并过滤
# Get remote tags and filter them
tags = [
result['name']
for result in requests.get(url).json()['results']
if all(
re.match(regex, str(result['name']))
for regex in self.tag_filter.regex_filter
)
]
# 获取 include 和 exclude 列表以及 py_code 列表
# Get the include and exclude lists and the py_code list
include = self.tag_filter.include
exclude = self.tag_filter.exclude
py_codes = self.tag_filter.py_code
# 如果 include 和 exclude 都为空,则直接返回 tags
# If both include and exclude are empty, return tags directly
if not include and not exclude:
return tags
else:
# 过滤标签
# Filter tags
filtered_tags = []
for tag in tags:
if include and not any(i in tag for i in include):
continue
if exclude and any(e in tag for e in exclude):
continue
if py_codes and not all(eval(f"({
py_code})('{
tag}')") for py_code in py_codes):
continue
filtered_tags.append(tag)
return filtered_tags
return []
配置加载
配置加载很简单,调用yaml库即可
def load_config(config_path: str = 'dockerhub.yaml') -> dict:
with open(config_path, 'r') as f:
config = yaml.load(f, Loader=yaml.FullLoader)
return dict(config)
拉取
定义一个名为 pull_latest_image
的函数,它用来从 Docker Hub 拉取最新的镜像。
函数首先加载配置文件,然后遍历配置文件中指定的每个仓库。对于每个仓库,函数会使用 Image_Getter
类来获取要拉取的镜像列表。
接下来,函数会遍历镜像列表,并使用 subprocess.run
函数来执行 docker pull
命令,拉取每个镜像。
在拉取过程中,函数会输出调试信息,包括正在拉取的镜像名称、命令执行结果以及可能的错误信息。
def pull_latest_image(config_path: str = 'dockerhub.yaml') -> int:
# 加载配置文件
# Load the configuration file
config = load_config(config_path)
# 遍历配置文件中的每个库
# Iterate over each library in the configuration file
for library in config['libraries']:
name = list(library.keys())[0]
getter = Image_Getter(library[name])
# 获取要拉取的镜像列表
# Get the list of images to pull
image_to_pull_list = [
f"{
getter.repo}:{
tag}"
for tag in getter.get_remote_tags()
]
# 拉取镜像
# Pull the images
for image in image_to_pull_list:
print(f"Pulling image: {
image}")
command = ["docker", "pull", image]
result = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 检查命令执行结果并输出调试信息
# Check the command execution result and output debug information
if result.returncode == 0:
print(f"Successfully pulled image: {
image}")
else:
print(
f"Failed to pull image: {
image}. Error: {
result.stderr.decode('utf-8')}")
return result.returncode
return 0
总结
以上代码和配置文件实现了拉取gitlab-ce高于15.11.0的镜像,本代码需要安装pyyaml和requests模块才能顺利运行。
备注
注释部分由new-bing完善。