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

news2024/11/18 3:40:19

目录

前言

下载镜像代码

 使用方法

原代码中无法适配 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.architecture

imgparts = 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 = None
	opt_mediaType = None
	if (resp.status_code == 200):
		manifests = resp.json()['manifests']
		for manifest in manifests:
			if manifest_digest == manifest['digest']:
				opt_manifest_digest = manifest_digest
				opt_mediaType = manifest['mediaType']
				break
			if manifest["platform"]["architecture"] == manifest_architecture and manifest["platform"]["os"] == manifest_os:
				opt_manifest_digest = manifest['digest']
				opt_mediaType = manifest['mediaType']
				break
			for 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 it
	fake_layerid = hashlib.sha256((parentid+'\n'+ublob+'\n').encode('utf-8')).hexdigest()
	layerdir = imgdir + '/' + fake_layerid
	os.mkdir(layerdir)

	# Creating VERSION file
	file = open(layerdir + '/VERSION', 'w')
	file.write('1.0')
	file.close()

	# Creating layer.tar file
	sys.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 expiration
	bresp = 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 URL
		bresp = 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 progress
	bresp.raise_for_status()
	unit = int(bresp.headers['Content-Length']) / 50
	acc = 0
	nb_traits = 0
	progress_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 + 8192
				if acc > unit:
					nb_traits = nb_traits + 1
					progress_bar(ublob, nb_traits)
					acc = 0
	sys.stdout.write("\r{}: Extracting...{}".format(ublob[7:19], " "*50)) # Ugly but works everywhere
	sys.stdout.flush()
	with open(layerdir + '/layer.tar', "wb") as file: # Decompress gzip response
		unzLayer = 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 file
	file = open(layerdir + '/json', 'w')
	# last layer = config manifest - history - rootfs
	if layers[-1]['digest'] == layer['digest']:
		# FIXME: json.loads() automatically converts to unicode, thus decoding values whereas Docker doesn't
		json_obj = json.loads(confresp.content)
		del json_obj['history']
		try:
			del json_obj['rootfs']
		except: # Because Microsoft loves case insensitiveness
			del json_obj['rootfS']
	else: # other layers json are empty
		json_obj = json.loads(empty_json)
	json_obj['id'] = fake_layerid
	if parentid:
		json_obj['parent'] = parentid
	parentid = 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.coloradmin.cn/o/1712194.html

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

相关文章

【算法实战】每日一题:统计一个序列向某个方向的比他小的数的个数(非暴力)

题目 统计一个序列向某个方向的比他小的数的个数 思路 用单调栈&#xff0c;虽然这里说的是统计比他小的&#xff0c;但是是求和&#xff0c;所以我们可以用在用单调栈的时候统计里面所有比他大的元素 这两个级别上是一样的 伪代码 声明变量 n、num 和 sum 为整数。 声明…

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

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

vue组件的基本使用方法

组件 【1】组件是什么&#xff1f; 组件就是&#xff1a;扩展 HTML 元素&#xff0c;封装可重用的代码&#xff0c;目的是复用例如&#xff1a;有一个轮播图&#xff0c;可以在很多页面中使用&#xff0c;一个轮播有js&#xff0c;css&#xff0c;html组件把js&#xff0c;cs…

使用 Django 连接 MySQL 数据库

文章目录 步骤一&#xff1a;安装必要的库和驱动步骤二&#xff1a;配置数据库连接步骤三&#xff1a;执行数据库迁移步骤四&#xff1a;开始使用 MySQL 数据库创建一个模型迁移模型到数据库使用模型进行数据操作创建新记录&#xff1a;查询记录&#xff1a;更新记录&#xff1…

264 基于matlab的自适应语音盲分离

基于matlab的自适应语音盲分离&#xff0c;当a和b同时对着传声器A,B说话且传声器靠得很近时&#xff0c;传声器A,B会同时接受到a和b的声音&#xff0c;即a和b产生了混叠干扰&#xff0c;此时通过自适应语音盲分离系统可以将a,b的声音分离开&#xff0c;使得一个信道只有一个人的…

【并发程序设计】11.进程间通信

11.进程间通信 &#xff08;IPC&#xff0c;InterProcess Communication&#xff09;进程和进程之间交换信息。 常用通信方式 无名管道&#xff08;pipe&#xff09;有名管道 &#xff08;fifo&#xff09;信号&#xff08;signal&#xff09;共享内存(mmap)套接字&#xff0…

平衡二叉树的构建(理论,部分函数代码)

平衡二叉树是二叉排序树的一种特殊情况&#xff0c;平衡二叉树的出现是为了在最坏情况下的时间复杂度仍然是对数级别O(logn)&#xff0c;从而保证了高效的搜索、插入和删除操作。 举个例子&#xff0c;如果有一个数组是&#xff1a;1&#xff0c;2&#xff0c;3。如果只简单的…

数据库多表查询

多表查询&#xff1a; SELECT *FROM stu_table,class WHERE stu_table.c_idclass.c_id; 多表查询——内连接 查询两张表交集部分。 隐式内连接&#xff1a; #查询学生姓名&#xff0c;和班级名称&#xff0c;隐式调用 SELECT stu_table.s_name,class.c_name FROM stu_table…

vs code 中使用SSH 连接远程的Ubuntu系统

如下图&#xff0c;找到对应的位置 在电脑上找到以下位置 打开配置如下&#xff0c;记住&#xff0c;那个root为你的用户名&#xff0c;这个用户名&#xff0c;具体根据你的用户名来设置&#xff0c;对应的密码就是你登录Ubuntu时的密码 Host root192.168.0.64User rootHostNa…

文件跨境传输法律管控越来越严格,企业该如何有效应对?

文件跨境传输已经成为非常普遍的业务需求&#xff0c;企业在世界各地的总分支处、合作伙伴&#xff0c;客户间开展业务时&#xff0c;必须基于数据的跨境流转而展开。 但随着世界各国对数据安全的重视&#xff0c;文件跨境传输也日趋严格&#xff0c;各国在法律法规上均出具了更…

RP2040 SPI DMA驱动ST7735

SPI DMA演示 一、源码 #include <stdio.h> #include <stdlib.h> #include "pico/stdlib.h" #include "pico/binary_info.h" #include "hardware/spi.h" #include "hardware/dma.h" #include "font.h"#define X…

Ownips+Coze海外社媒数据分析实战指南

目录 一、引言二、ISP代理简介三、应用实践——基于Ownips和coze的社媒智能分析助手3.1、Twitter趋势数据采集3.1.1、Twitter趋势数据接口分析3.1.2、Ownips原生住宅ISP选取与配置3.1.3、数据采集 3.2、基于Ownips和Coze的社媒智能助手3.2.1、Ownips数据采集插件集成3.2.2、创建…

Ubuntu配置Git

安装git sudo apt install git 查看是否安装成功 git --version 配置git 用github上注册的用户名和邮箱地址&#xff0c;配置git git config --global user.name "username" git config --global user.email "usernameemail.com" 重启ubuntu查看…

首搭第五代DM技术,秦L DM-i正式上市,仅售9.98万元起

5月28日&#xff0c;比亚迪王朝重磅新车秦L DM-i在西安震撼上市&#xff0c;首搭第五代DM技术&#xff0c;百公里亏电油耗达到划时代的2.9L&#xff0c;“一箱油”满油满电综合续航达2100公里&#xff0c;引领中级&#xff0c;创下了百公里油耗的历史新低&#xff0c;开创油耗2…

机顶盒也可以跑pcdn--上机指南(贰)

机顶盒能跑PCDN&#xff0c;以下是相关上机指南操作步骤&#xff1a; 1.申请PCDN服务&#xff1a;登录PCDN控制台&#xff0c;申请开通PCDN服务。 2.后台开通PCDN服务&#xff1a;工作人员与用户沟通业务详细需求&#xff0c;用户确定使用PCDN&#xff0c;后台为用户开通PCDN…

VolWeb:集中式增强型数字取证内存分析平台

关于VolWeb VolWeb是一款最新开发的集中式增强型数字取证内存分析平台&#xff0c;该平台基于Volatility 3框架实现其功能&#xff0c;该工具旨在辅助广大研究人员执行安全分析和事件应急响应等任务。 VolWeb可以提供集中式、可视化的增强型网络应用程序&#xff0c;并提高安全…

Java基础:类的详细说明

Java是一门面向对象的编程语言&#xff0c;所谓的面向对象&#xff0c;简单的说&#xff0c;就是在软件开发过程中&#xff0c;用各种各样的对象实现所需功能。 对象就好像是现实世界中不计其数的物体&#xff0c;根据物体的性质可以将其进行分门别类&#xff1a;石头、锤子、…

深入分析 Android Activity (十一)

文章目录 深入分析 Android Activity (十一)1. Activity 的内存管理和优化1.1 内存泄漏的常见原因1.2 避免内存泄漏的方法1.3 内存泄漏检测工具 2. Activity 的配置变更处理2.1 处理配置变更2.2 保存和恢复状态2.3 使用 ViewModel 3. Activity 的测试3.1 单元测试3.2 UI 测试 4…

编译安装Apache httpd服务(LAMP1)

目录 1.初始化设置&#xff0c;将Apache所需软件包传到 /opt 目录下 &#xff08;1&#xff09;关闭防火墙 &#xff08;2&#xff09;上传软件包到/opt目录 2.安装环境依赖包 3.配置软件模块 4.编译及安装 5.优化配置文件路径&#xff0c;并把httpd服务的可执行程序文件…

DDR5芯片系统框图详解

DDR5 SDRAM(双倍数据率五代同步动态随机存取存储器)的功能框图详细展现了其内部结构和各个关键模块,这些模块协同工作以实现高速数据传输和存储管理。以下是主要组成部分的详细介绍: Controller Logic (控制器逻辑)这是DDR5内存系统的大脑,负责接收来自CPU或SoC的指令,解…