mirror of
https://github.com/tnb-labs/panel.git
synced 2026-09-19 10:03:36 +08:00
- 依赖注入换到 libtnb/wire v0.3.0,各层包导出 Module 并逐类型 Export, wire.Struct 改用 Struct[T](),cleanup 签名改为 func() error - 测试断言换到 libtnb/assert 的 must/check,20 个 testify suite 拆成标准测试函数 - mock 改用 matryer 模板输出到 internal/mocks,wire 走 go.mod tool 指令, mockery 用 go run 调用,避免它的依赖树污染 go.mod - 引入 .golangci.yml 并修完全部告警,darwin 与 linux 双平台 0 issues - 工作流改用 go-version-file、govulncheck 官方 action, wire 校验移入 test.yml,mockery 由自动提交改为 PR 校验 顺带修复:pkg/db 四处漏查 rows.Err()(DatabaseExists 查询出错会被当成库不存在)、 websitestat 负值转 uint64 污染流量统计、pty 终端尺寸超 uint16 截断、 文件权限位超 32 位静默截断、面板与 ACME challenge 服务器缺 ReadHeaderTimeout、 GetDefault 丢失操作人、三处对结构体值恒成立的假断言、两处带空格而失效的 nolint Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
143 lines
3.5 KiB
Go
143 lines
3.5 KiB
Go
package data
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/moby/moby/api/types/image"
|
|
"github.com/moby/moby/api/types/registry"
|
|
"github.com/moby/moby/client"
|
|
"github.com/samber/lo"
|
|
|
|
"github.com/acepanel/panel/v3/internal/biz"
|
|
"github.com/acepanel/panel/v3/internal/request"
|
|
"github.com/acepanel/panel/v3/pkg/tools"
|
|
"github.com/acepanel/panel/v3/pkg/types"
|
|
)
|
|
|
|
type containerImageRepo struct{}
|
|
|
|
func NewContainerImageRepo() biz.ContainerImageRepo {
|
|
return &containerImageRepo{}
|
|
}
|
|
|
|
// List 列出镜像
|
|
func (r *containerImageRepo) List(sock string) ([]types.ContainerImage, error) {
|
|
apiClient, err := getDockerClient(sock)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
|
|
|
resp, err := apiClient.ImageList(context.Background(), client.ImageListOptions{
|
|
All: true,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
images := lo.Map(resp.Items, func(item image.Summary, _ int) types.ContainerImage {
|
|
return types.ContainerImage{
|
|
ID: item.ID,
|
|
Containers: item.Containers,
|
|
RepoTags: item.RepoTags,
|
|
RepoDigests: item.RepoDigests,
|
|
Size: tools.FormatBytes(float64(item.Size)),
|
|
Labels: types.MapToKV(item.Labels),
|
|
CreatedAt: time.Unix(item.Created, 0),
|
|
}
|
|
})
|
|
|
|
slices.SortFunc(images, func(a types.ContainerImage, b types.ContainerImage) int {
|
|
return strings.Compare(a.ID, b.ID)
|
|
})
|
|
|
|
return images, nil
|
|
}
|
|
|
|
// Exist 检查镜像是否存在
|
|
func (r *containerImageRepo) Exist(sock string, name string) (bool, error) {
|
|
apiClient, err := getDockerClient(sock)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
|
|
|
_, err = apiClient.ImageInspect(context.Background(), name)
|
|
if err != nil {
|
|
if cerrdefs.IsNotFound(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Pull 拉取镜像
|
|
func (r *containerImageRepo) Pull(sock string, req *request.ContainerImagePull) error {
|
|
apiClient, err := getDockerClient(sock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
|
|
|
options := client.ImagePullOptions{}
|
|
if req.Auth {
|
|
authConfig := registry.AuthConfig{
|
|
Username: req.Username,
|
|
Password: req.Password,
|
|
}
|
|
encodedJSON, err := json.Marshal(authConfig) //nolint:gosec
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
|
options.RegistryAuth = authStr
|
|
}
|
|
|
|
out, err := apiClient.ImagePull(context.Background(), req.Name, options)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func(out client.ImagePullResponse) { _ = out.Close() }(out)
|
|
|
|
return out.Wait(context.Background())
|
|
}
|
|
|
|
// Remove 删除镜像
|
|
func (r *containerImageRepo) Remove(sock string, id string) error {
|
|
apiClient, err := getDockerClient(sock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
|
|
|
_, err = apiClient.ImageRemove(context.Background(), id, client.ImageRemoveOptions{
|
|
Force: true,
|
|
PruneChildren: true,
|
|
})
|
|
return err
|
|
}
|
|
|
|
// Prune 清理未使用的镜像
|
|
func (r *containerImageRepo) Prune(sock string) error {
|
|
apiClient, err := getDockerClient(sock)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func(apiClient *client.Client) { _ = apiClient.Close() }(apiClient)
|
|
|
|
_, err = apiClient.ImagePrune(context.Background(), client.ImagePruneOptions{
|
|
Filters: make(client.Filters).
|
|
Add("dangling", "false").
|
|
Add("label", "created_by!=acepanel"),
|
|
})
|
|
return err
|
|
}
|