Docker HTTPS api V2 Manifest V 2, Schema 2 下的免装docker下载镜像的方法

目录

前言

下载镜像代码

 使用方法

原代码中无法适配 Schema 2 的原因浅析

如何解决

相对原代码改动的东西


前言

本文提供代码主要是基于 https://github.com/NotGlop/docker-drag 提供的代码修改的。链接中提供的代码应该是是基于HTTPS api  V2 Manifest V 2, Schema 1实现的,在  Schema 2  下无法正常运行,而开源作者没有进行相应的更新,于是就自己想办法解决了。后续对上述链接里的代码简称为原代码。

下载镜像代码

修改为适配 HTTPS api  V2 Manifest V 2, Schema 2 的代码如下所示

import argparse
import os
import sys
import gzip
from io import BytesIO
import json
import hashlib
import shutil
import requests
import tarfile
import urllib3
urllib3.disable_warnings()if len(sys.argv) < 2 :print('Usage:\n\tdocker_pull.py [registry/][repository/]image[:tag|@digest]\n')exit(1)# Look for the Docker image to download
repo = 'library'
tag = 'latest'parser = argparse.ArgumentParser()
parser.add_argument("--os", type=str,required=False)
parser.add_argument('--digest', type=str,required=False)
parser.add_argument('--architecture', type=str,required=False)
args = parser.parse_known_args()[0]
manifest_os = args.os
manifest_digest = args.digest
manifest_architecture = args.architectureimgparts = sys.argv[1].split('/')
try:img,tag = imgparts[-1].split('@')
except ValueError:try:img,tag = imgparts[-1].split(':')except ValueError:img = imgparts[-1]
# Docker client doesn't seem to consider the first element as a potential registry unless there is a '.' or ':'
if len(imgparts) > 1 and ('.' in imgparts[0] or ':' in imgparts[0]):registry = imgparts[0]repo = '/'.join(imgparts[1:-1])
else:registry = 'registry-1.docker.io'if len(imgparts[:-1]) != 0:repo = '/'.join(imgparts[:-1])else:repo = 'library'
repository = '{}/{}'.format(repo, img)# Get Docker authentication endpoint when it is required
auth_url='https://auth.docker.io/token'
reg_service='registry.docker.io'
resp = requests.get('https://{}/v2/'.format(registry), verify=False)
if resp.status_code == 401:auth_url = resp.headers['WWW-Authenticate'].split('"')[1]try:reg_service = resp.headers['WWW-Authenticate'].split('"')[3]except IndexError:reg_service = ""# Get Docker token (this function is useless for unauthenticated registries like Microsoft)
def get_auth_head(type):resp = requests.get('{}?service={}&scope=repository:{}:pull'.format(auth_url, reg_service, repository), verify=False)access_token = resp.json()['token']auth_head = {'Authorization':'Bearer '+ access_token, 'Accept': type}return auth_head# Docker style progress bar
def progress_bar(ublob, nb_traits):sys.stdout.write('\r' + ublob[7:19] + ': Downloading [')for i in range(0, nb_traits):if i == nb_traits - 1:sys.stdout.write('>')else:sys.stdout.write('=')for i in range(0, 49 - nb_traits):sys.stdout.write(' ')sys.stdout.write(']')sys.stdout.flush()# Fetch manifest v2 and get image layer digests
auth_head = get_auth_head('application/vnd.docker.distribution.manifest.v2+json')
resp = requests.get('https://{}/v2/{}/manifests/{}'.format(registry, repository, tag), headers=auth_head, verify=False)
#resp = requests.get('https://{}/v2/{}/blobs/{}'.format(registry, repository, "sha256:ea0203747a6b779d26ceee879ff9c1d8b70c0d10196a4f969f8ceb4d1e3904bb"), headers=auth_head, verify=False)if (resp.status_code != 200 or not 'layers' in resp.json()):print('[-] Cannot fetch manifest for {} [HTTP {}]'.format(repository, resp.status_code))print(resp.content)auth_head = get_auth_head('application/vnd.docker.distribution.manifest.list.v2+json')resp = requests.get('https://{}/v2/{}/manifests/{}'.format(registry, repository, tag), headers=auth_head, verify=False)opt_manifest_digest = Noneopt_mediaType = Noneif (resp.status_code == 200):manifests = resp.json()['manifests']for manifest in manifests:if manifest_digest == manifest['digest']:opt_manifest_digest = manifest_digestopt_mediaType = manifest['mediaType']breakif manifest["platform"]["architecture"] == manifest_architecture and manifest["platform"]["os"] == manifest_os:opt_manifest_digest = manifest['digest']opt_mediaType = manifest['mediaType']breakfor key, value in manifest["platform"].items():sys.stdout.write('{}: {}, '.format(key, value))print('digest: {},mediaType :{}'.format(manifest["digest"],manifest["mediaType"]))if opt_manifest_digest is None:print('[+] Manifests found for this (use the --digest or --os --architecture to pull the corresponding image):')exit(1)else:auth_head = get_auth_head(opt_mediaType)resp = requests.get('https://{}/v2/{}/manifests/{}'.format(registry, repository, opt_manifest_digest), headers=auth_head,verify=False)layers = resp.json()['layers']# Create tmp folder that will hold the image
imgdir = 'tmp_{}_{}'.format(img, tag.replace(':', '@'))
os.mkdir(imgdir)
print('Creating image structure in: ' + imgdir)config = resp.json()['config']['digest']
confresp = requests.get('https://{}/v2/{}/blobs/{}'.format(registry, repository, config), headers=auth_head, verify=False)
file = open('{}/{}.json'.format(imgdir, config[7:]), 'wb')
file.write(confresp.content)
file.close()content = [{'Config': config[7:] + '.json','RepoTags': [ ],'Layers': [ ]}]
if len(imgparts[:-1]) != 0:content[0]['RepoTags'].append('/'.join(imgparts[:-1]) + '/' + img + ':' + tag)
else:content[0]['RepoTags'].append(img + ':' + tag)empty_json = '{"created":"1970-01-01T00:00:00Z","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false, \"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false, "StdinOnce":false,"Env":null,"Cmd":null,"Image":"", \"Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null}}'# Build layer folders
parentid=''
for layer in layers:ublob = layer['digest']# FIXME: Creating fake layer ID. Don't know how Docker generates itfake_layerid = hashlib.sha256((parentid+'\n'+ublob+'\n').encode('utf-8')).hexdigest()layerdir = imgdir + '/' + fake_layeridos.mkdir(layerdir)# Creating VERSION filefile = open(layerdir + '/VERSION', 'w')file.write('1.0')file.close()# Creating layer.tar filesys.stdout.write(ublob[7:19] + ': Downloading...')sys.stdout.flush()auth_head = get_auth_head('application/vnd.docker.distribution.manifest.v2+json') # refreshing token to avoid its expirationbresp = requests.get('https://{}/v2/{}/blobs/{}'.format(registry, repository, ublob), headers=auth_head, stream=True, verify=False)if (bresp.status_code != 200): # When the layer is located at a custom URLbresp = requests.get(layer['urls'][0], headers=auth_head, stream=True, verify=False)if (bresp.status_code != 200):print('\rERROR: Cannot download layer {} [HTTP {}]'.format(ublob[7:19], bresp.status_code, bresp.headers['Content-Length']))print(bresp.content)exit(1)# Stream download and follow the progressbresp.raise_for_status()unit = int(bresp.headers['Content-Length']) / 50acc = 0nb_traits = 0progress_bar(ublob, nb_traits)with open(layerdir + '/layer_gzip.tar', "wb") as file:for chunk in bresp.iter_content(chunk_size=8192):if chunk:file.write(chunk)acc = acc + 8192if acc > unit:nb_traits = nb_traits + 1progress_bar(ublob, nb_traits)acc = 0sys.stdout.write("\r{}: Extracting...{}".format(ublob[7:19], " "*50)) # Ugly but works everywheresys.stdout.flush()with open(layerdir + '/layer.tar', "wb") as file: # Decompress gzip responseunzLayer = gzip.open(layerdir + '/layer_gzip.tar','rb')shutil.copyfileobj(unzLayer, file)unzLayer.close()os.remove(layerdir + '/layer_gzip.tar')print("\r{}: Pull complete [{}]".format(ublob[7:19], bresp.headers['Content-Length']))content[0]['Layers'].append(fake_layerid + '/layer.tar')# Creating json filefile = open(layerdir + '/json', 'w')# last layer = config manifest - history - rootfsif layers[-1]['digest'] == layer['digest']:# FIXME: json.loads() automatically converts to unicode, thus decoding values whereas Docker doesn'tjson_obj = json.loads(confresp.content)del json_obj['history']try:del json_obj['rootfs']except: # Because Microsoft loves case insensitivenessdel json_obj['rootfS']else: # other layers json are emptyjson_obj = json.loads(empty_json)json_obj['id'] = fake_layeridif parentid:json_obj['parent'] = parentidparentid = json_obj['id']file.write(json.dumps(json_obj))file.close()file = open(imgdir + '/manifest.json', 'w')
file.write(json.dumps(content))
file.close()if len(imgparts[:-1]) != 0:content = { '/'.join(imgparts[:-1]) + '/' + img : { tag : fake_layerid } }
else: # when pulling only an img (without repo and registry)content = { img : { tag : fake_layerid } }
file = open(imgdir + '/repositories', 'w')
file.write(json.dumps(content))
file.close()# Create image tar and clean tmp folder
docker_tar = repo.replace('/', '_') + '_' + img + '.tar'
sys.stdout.write("Creating archive...")
sys.stdout.flush()
tar = tarfile.open(docker_tar, "w")
tar.add(imgdir, arcname=os.path.sep)
tar.close()
shutil.rmtree(imgdir)
print('\rDocker image pulled: ' + docker_tar)

 使用方法

方法1

首先执行如下命令

python docker_pull.py  minio/minio

此时控制台会输出如下日志。可以从中选择自己想要下载版本镜像的Manifest

os 为镜像所属系统

architecture 为镜像所属cpu架构(amd64即x86的)

digest 作为唯一key选择使用的清单

将自己选择的 Manifest 对应的 digest 代入如下命令执行

pyhon docker_pull.py  minio/minio --digest=<digest>

方法二

直接执行如下命令

python docker_pull.py  minio/minio --os=linux --architecture=amd64

上述命令的意思是从 Manifest  列表中选择第一个为 linux 系统且 cpu 架构为 amd64Manifest 进行下载

os 和 architecture 根据自己的需要配置

 按上述方法如果下载成功则可以在 docker_pull.py 问题同目录看到如下 tar  文件

原代码中无法适配 Schema 2 的原因浅析

首先原代码是请求下面的链接

http get /v2/<name>/manifests/<tag>

Schema 1 中上面链接是可以直接返回一个镜像 manifest 的详情,返回结果中会包含镜像对应的图层 layers 字段。然后原代码会拿去这个 layers 字段相关的参数去拉取镜像文件。

Schema 2 中则返回一个 manifest 列表,列表中包含不同平台的及不同 cpu 架构对应的manifest,如下图所示

图中并不包含 layers,因此原开源代码使用出现了如下的错误

上述说法无法确保正确。有兴趣的可以自行看下面两个链接

https://distribution.github.io/distribution/spec/manifest-v2-2/

https://distribution.github.io/distribution/spec/api/

如何解决

Schema 2中 返回的 manifest 列表的每条manifest 会包含 manifest 的唯一识别码 digest mediaType,可以拿 digest mediaType 请求如下链接(mediaType  是放在请求头 Accept 中)

http get  /v2/<name>/manifests/<digest>

从而获取到 manifest 的详情,此处的详情是包含layers的,如下图所示

于是可以根据这个返回结果中的 layers 走原代码之前的逻辑去拉取镜像包。

相对原代码改动的东西

只改了如下两个地方,其他东西都没懂

1.增加了如下代码

2.增加或修改了如下红框中的代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/18711.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面试必备:应对 “为什么离职” 的万能回答

使用PC端的朋友&#xff0c;请将页面缩小到最小比例&#xff0c;阅读最佳&#xff01; 面试官问到你为什么从上一家公司离职时&#xff0c;你会怎么回答&#xff1f;这个问题我觉得很有意思&#xff0c;也很有必要去探讨一下。 很多专业人士都会建议你&#xff0c;最好不要直接…

C++---迭代器介绍

迭代器的介绍 使用迭代器需要引用头文件,但一般的容器都引用了这个头文件。 这五种迭代器的声明如下: struct output_iterator_tag { };//输出迭代器 struct input_iterator_tag{ };//输入迭代器 struct forward_iterator_tag : public input_iterator_tag {};//向前迭代器 …

基于序列深度学习模型的向量海岸线形状分类方法 2024.05

本文中提出了一个数据驱动的方法来分类的形状矢量海岸线&#xff0c;该方法利用基于序列的深度学习算法对海岸线矢量分段进行建模和分类。具体而言&#xff0c;首先将复杂的海岸线划分为一系列弯曲&#xff0c;并进一步提出了一组不同的特征来描述每个弯曲的形态特征。然后&…

强化学习——学习笔记2

在上一篇文章中对强化学习进行了基本的概述&#xff0c;在此篇文章中将继续深入强化学习的相关知识。 一、什么是DP、MC、TD&#xff1f; 动态规划法&#xff08;DP&#xff09;&#xff1a;动态规划法离不开一个关键词&#xff0c;拆分 &#xff0c;就是把求解的问题分解成若…

gif帧数修改怎么操作?一键掌握GIF帧数修改技巧!

gif帧数修改怎么操作&#xff1f;在数字化信息爆炸的时代&#xff0c;GIF动图因其生动有趣的特性而备受广大网友喜爱。然而&#xff0c;很多时候我们可能会遇到GIF动图帧数过多或过少&#xff0c;导致动画效果不尽如人意的情况。那么&#xff0c;如何对GIF动图的帧数进行修改呢…

探索微软Edge开发者工具:优化前端开发的艺术与科学

探索微软Edge开发者工具&#xff1a;优化前端开发的艺术与科学 引言&#xff1a;Edge开发者工具概览一、基础操作&#xff1a;步入DevTools的大门1.1 启动与界面布局1.2 快速导航与定制 二、元素审查与样式调整2.1 精准元素选取2.2 实时CSS编辑2.3 自动完成与内联文档 三、Java…

YOLOv10最详细全面讲解1- 目标检测-准备自己的数据集(YOLOv5,YOLOv8均适用)

YOLOv10没想到出来的如此之快&#xff0c;作为一名YOLO的爱好者&#xff0c;以YOLOv5和YOLOv8的经验&#xff0c;打算出一套从数据集装备->环境配置->训练->验证->目标追踪全系列教程。请大家多多点赞和收藏&#xff01;&#xff01;&#xff01;YOLOv5和YOLOv8亲测…

Redis中的数据结构与内部编码

本篇文章主要是对 Redis 常见的数据结构进行讲解&#xff0c;同时还对其所对应的不同的内部编码进行讲解。希望本篇文章会对你有所帮助。 文章目录 一、五大数据结构 二、数据结构对应的编码方式 String hash list set zset &#x1f64b;‍♂️ 作者&#xff1a;Ggggggtm &…

js 面试题学习笔记一

1、什么是防抖和节流&#xff1f;有什么区别&#xff1f;如何实现&#xff1f; 防抖&#xff1a;触发高频事件后N秒内函数只会执行一次&#xff0c;如果N秒高频事件再次被触发&#xff0c;则重新计算时间。&#xff08;a时间触发&#xff0c;5秒内执行一次&#xff0c;但是第4…

10G UDP协议栈 (9)UDP模块

目录 一、UDP协议简单介绍 二、UDP功能实现 三、仿真 一、UDP协议简单介绍 UDP协议和TCP协议同位于传输层&#xff0c;介于网络层&#xff08;IP&#xff09;和应用层之间&#xff1a;UDP数据部分为应用层报文&#xff0c;而UDP报文在IP中承载。 UDP 报文格式相对于简单&am…

电脑出现:excel词典(xllex.dll)文件丢失或损坏的错误提示怎么办?有效的将丢失的xllex.dll修复

当遇到 Excel 提示“词典 (xllex.dll) 文件丢失或损坏”的问题时&#xff0c;通常意味着该动态链接库文件&#xff08;Dynamic Link Library&#xff0c;DLL&#xff09;&#xff0c;它与拼写检查功能相关联的&#xff0c;无法被正确找到或者合适地使用。那么有什么办法可以解决…

LLVM技术在GaussDB等数据库中的应用

目录 LLVM和数据库 LLVM适用场景 LLVM对所有类型的SQL都会有收益吗&#xff1f; LLVM在OLTP中就一定没有收益吗&#xff1f; GaussDB中的LLVM 1. LLVM在华为应用于数据库的时间线 2. GaussDB LLVM实现简析 3. GaussDB LLVM支持加速的场景 支持LLVM的表达式&#xff1a…

vue项目出现多次ElMessage

问题&#xff1a; 解决方法&#xff1a; let message null if (message null) { message ElMessage.error(“登录过期,请重新登录”); } 最终效果&#xff1a;只出现一个弹框

Orange AIpro Color triangle帧率测试

OpenGL概述 OpenGL ES是KHRNOS Group推出的嵌入式加速3D图像标准&#xff0c;它是嵌入式平台上的专业图形程序接口&#xff0c;它是OpenGL的一个子集&#xff0c;旨在提供高效、轻量级的图形渲染功能。现推出的最新版本是OpenGL ES 3.2。OpenGL和OpenCV OpenCL不同&#xff0c;…

实操专区-第15周-课堂练习专区-漏斗图与金字塔图

实操专区-第15周-课堂练习专区-漏斗图 下载安装ECharts&#xff0c;完成如下样式图形。 代码和截图上传 基本要求&#xff1a;下图3选1&#xff0c;完成代码和截图 完成 3.1.3.16 漏斗图中的任务点 基本要求&#xff1a;2个选一个完成&#xff0c;多做1个加2分。 请用班级学号姓…

【Java EE】网络原理——HTTP请求

目录 1.认识URL 2.认识“方法&#xff08;method&#xff09;” 2.1GET方法 2.1.1使用Fiddler观察GET请求 2.1.2 GET请求的特点 2.2 POST方法 2.2.1 使用FIddler观察POST方法 2.2.2 POST请求的特点 3.认识请求“报头”&#xff08;header&#xff09; 3.1 Host 3.2 C…

Spring MVC 工作流程源码分析

前言&#xff1a; 我们知道 Spring MVC 的核心是前端控制器 DispatcherServlet&#xff0c;客户端所有的请求都会交给 DispatcherServlet 来处理&#xff0c;本篇我我们来分析 Spring MVC 处理客户端请求的流程&#xff0c;也就是工作流程。 Sping MVC 只是储备传送门&#x…

Java整合EasyExcel实战——3(上下列相同合并单元格策略)

参考&#xff1a;https://juejin.cn/post/7322156759443095561?searchId202405262043517631094B7CCB463FDA06https://juejin.cn/post/7322156759443095561?searchId202405262043517631094B7CCB463FDA06 准备条件 依赖 <dependency><groupId>com.alibaba</gr…

邻接矩阵广度优先遍历

关于图的遍历实际上就两种 广度优先和深度优先&#xff0c;一般关于图的遍历都是基于邻接矩阵的&#xff0c;考试这些&#xff0c;用的也是邻接矩阵。 本篇文章先介绍广度优先遍历的原理&#xff0c;和代码实现 什么是图的广度优先遍历&#xff1f; 这其实和二叉树的层序遍…

数据结构——二叉树的基本应用

在此之前我们已经初步了解了二叉树&#xff0c;在介绍堆的基本应用时&#xff0c;我们已经具体介绍了完全二叉树的基本应用&#xff0c;本章我们介绍二叉树的基本应用&#xff0c;这个不止指的是完全二叉树&#xff0c;而是指泛型的二叉树。 二叉树的基本应用&#xff0c;由于…