修复离线镜像小细节

This commit is contained in:
user123456
2025-06-13 10:09:30 +08:00
parent dd6f5675e4
commit 0e7740c391
2 changed files with 78 additions and 48 deletions

View File

@@ -160,48 +160,9 @@ func (is *ImageStreamer) StreamImageToGin(ctx context.Context, imageRef string,
// streamMultiArchImage 处理多架构镜像
func (is *ImageStreamer) streamMultiArchImage(ctx context.Context, desc *remote.Descriptor, writer io.Writer, options *StreamOptions, remoteOptions []remote.Option, imageRef string) error {
index, err := desc.ImageIndex()
img, err := is.selectPlatformImage(desc, options)
if err != nil {
return fmt.Errorf("获取镜像索引失败: %w", err)
}
manifest, err := index.IndexManifest()
if err != nil {
return fmt.Errorf("获取索引清单失败: %w", err)
}
// 选择合适的平台
var selectedDesc *v1.Descriptor
for _, m := range manifest.Manifests {
if m.Platform == nil {
continue
}
if options.Platform != "" {
platformParts := strings.Split(options.Platform, "/")
if len(platformParts) == 2 &&
m.Platform.OS == platformParts[0] &&
m.Platform.Architecture == platformParts[1] {
selectedDesc = &m
break
}
} else if m.Platform.OS == "linux" && m.Platform.Architecture == "amd64" {
selectedDesc = &m
break
}
}
if selectedDesc == nil && len(manifest.Manifests) > 0 {
selectedDesc = &manifest.Manifests[0]
}
if selectedDesc == nil {
return fmt.Errorf("未找到合适的平台镜像")
}
img, err := index.Image(selectedDesc.Digest)
if err != nil {
return fmt.Errorf("获取选中镜像失败: %w", err)
return err
}
return is.streamImageLayers(ctx, img, writer, options, imageRef)
@@ -427,7 +388,28 @@ func (is *ImageStreamer) streamSingleImageForBatch(ctx context.Context, tarWrite
switch desc.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
return nil, nil, fmt.Errorf("批量下载暂不支持多架构镜像")
// 处理多架构镜像,复用单个下载的逻辑
img, err := is.selectPlatformImage(desc, options)
if err != nil {
return nil, nil, fmt.Errorf("选择平台镜像失败: %w", err)
}
layers, err := img.Layers()
if err != nil {
return nil, nil, fmt.Errorf("获取镜像层失败: %w", err)
}
configFile, err := img.ConfigFile()
if err != nil {
return nil, nil, fmt.Errorf("获取镜像配置失败: %w", err)
}
log.Printf("镜像包含 %d 层", len(layers))
err = is.streamDockerFormatWithReturn(ctx, tarWriter, img, layers, configFile, imageRef, &manifest, &repositories)
if err != nil {
return nil, nil, err
}
case types.OCIManifestSchema1, types.DockerManifestSchema2:
img, err := desc.Image()
if err != nil {
@@ -477,6 +459,55 @@ func (is *ImageStreamer) streamSingleImageForBatch(ctx context.Context, tarWrite
return manifest, repositories, nil
}
// selectPlatformImage 从多架构镜像中选择合适的平台镜像
func (is *ImageStreamer) selectPlatformImage(desc *remote.Descriptor, options *StreamOptions) (v1.Image, error) {
index, err := desc.ImageIndex()
if err != nil {
return nil, fmt.Errorf("获取镜像索引失败: %w", err)
}
manifest, err := index.IndexManifest()
if err != nil {
return nil, fmt.Errorf("获取索引清单失败: %w", err)
}
// 选择合适的平台
var selectedDesc *v1.Descriptor
for _, m := range manifest.Manifests {
if m.Platform == nil {
continue
}
if options.Platform != "" {
platformParts := strings.Split(options.Platform, "/")
if len(platformParts) == 2 &&
m.Platform.OS == platformParts[0] &&
m.Platform.Architecture == platformParts[1] {
selectedDesc = &m
break
}
} else if m.Platform.OS == "linux" && m.Platform.Architecture == "amd64" {
selectedDesc = &m
break
}
}
if selectedDesc == nil && len(manifest.Manifests) > 0 {
selectedDesc = &manifest.Manifests[0]
}
if selectedDesc == nil {
return nil, fmt.Errorf("未找到合适的平台镜像")
}
img, err := index.Image(selectedDesc.Digest)
if err != nil {
return nil, fmt.Errorf("获取选中镜像失败: %w", err)
}
return img, nil
}
var globalImageStreamer *ImageStreamer
// initImageStreamer 初始化镜像下载器
@@ -569,17 +600,16 @@ func handleSimpleBatchDownload(c *gin.Context) {
options := &StreamOptions{
Platform: req.Platform,
Compression: true,
Compression: false,
}
ctx := c.Request.Context()
log.Printf("批量下载 %d 个镜像 (平台: %s)", len(req.Images), formatPlatformText(req.Platform))
filename := fmt.Sprintf("batch_%d_images.docker.gz", len(req.Images))
filename := fmt.Sprintf("batch_%d_images.tar", len(req.Images))
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
c.Header("Content-Encoding", "gzip")
if err := globalImageStreamer.StreamMultipleImages(ctx, req.Images, c.Writer, options); err != nil {
log.Printf("批量镜像下载失败: %v", err)

View File

@@ -513,7 +513,7 @@
<!-- 页面头部 -->
<div class="header">
<h1 class="title">Docker离线镜像下载</h1>
<p class="subtitle">即点即下无需等待打包完全符合docker load标准</p>
<p class="subtitle">即点即下无需等待打包完全符合docker load加载标准</p>
<div class="features">
<div class="feature">
@@ -582,7 +582,7 @@
<form id="batchForm">
<div class="form-group">
<label class="form-label" for="imagesTextarea">镜像列表,每行一个,会将多个镜像合并为tar格式完全兼容docker load</label>
<label class="form-label" for="imagesTextarea">镜像列表,每行一个,会将多个镜像自动合并,符合官方标准完全兼容docker load</label>
<textarea
id="imagesTextarea"
class="textarea"
@@ -756,7 +756,7 @@
if (response.ok) {
// 获取文件名
const contentDisposition = response.headers.get('Content-Disposition');
let filename = `batch_${images.length}_images.docker.gz`;
let filename = `batch_${images.length}_images.tar`;
if (contentDisposition) {
const matches = contentDisposition.match(/filename="(.+)"/);