mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-12 08:00:34 +00:00
Update pending Go and frontend dependencies, including gRPC, OpenTelemetry and SVGO security fixes. Raise the minimum Go version to 1.26.6 and update x/image and x/mod to patched versions. Use moby/go-archive directly for image build contexts so its vulnerable v0.1.0 replacement can be removed and v0.3.0 used. Regenerate Go module checksums with go mod tidy and refresh the npm lockfile. Validation: Linux amd64 builds, existing Go tests and go mod verify pass for core and agent; frontend production build passes; npm audit reports zero vulnerabilities. Frontend type-check reports the same nine errors with the original and updated lockfiles. Go scanning still flags two advisories in the legacy Docker SDK, which has no patched release on its current module path. A separate SDK migration and host Docker daemon updates remain necessary.
548 lines
14 KiB
Go
548 lines
14 KiB
Go
package service
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/app/task"
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
"github.com/1Panel-dev/1Panel/agent/i18n"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/common"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
"github.com/docker/docker/api/types/build"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/image"
|
|
"github.com/docker/docker/api/types/registry"
|
|
"github.com/moby/go-archive"
|
|
)
|
|
|
|
type ImageService struct{}
|
|
|
|
type IImageService interface {
|
|
Page(req dto.PageImage) (int64, interface{}, error)
|
|
List() ([]dto.Options, error)
|
|
ListAll() ([]dto.ImageInfo, error)
|
|
ImageBuild(req dto.ImageBuild) error
|
|
ImagePull(req dto.ImagePull) error
|
|
ImageLoad(req dto.ImageLoad) error
|
|
ImageSave(req dto.ImageSave) error
|
|
ImagePush(req dto.ImagePush) error
|
|
ImageRemove(req dto.BatchDelete) error
|
|
ImageTag(req dto.ImageTag) error
|
|
}
|
|
|
|
func NewIImageService() IImageService {
|
|
return &ImageService{}
|
|
}
|
|
func (u *ImageService) Page(req dto.PageImage) (int64, interface{}, error) {
|
|
var (
|
|
list []image.Summary
|
|
records []dto.ImageInfo
|
|
backDatas []dto.ImageInfo
|
|
)
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
defer client.Close()
|
|
list, err = client.ImageList(context.Background(), image.ListOptions{})
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
|
if len(req.Name) != 0 {
|
|
length, count := len(list), 0
|
|
for count < length {
|
|
hasTag := false
|
|
for _, tag := range list[count].RepoTags {
|
|
if strings.Contains(tag, req.Name) {
|
|
hasTag = true
|
|
break
|
|
}
|
|
}
|
|
if !hasTag {
|
|
list = append(list[:count], list[(count+1):]...)
|
|
length--
|
|
} else {
|
|
count++
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, image := range list {
|
|
records = append(records, dto.ImageInfo{
|
|
ID: image.ID,
|
|
Tags: image.RepoTags,
|
|
IsUsed: checkUsed(image.ID, containers),
|
|
CreatedAt: time.Unix(image.Created, 0),
|
|
Size: image.Size,
|
|
})
|
|
}
|
|
switch req.OrderBy {
|
|
case "size":
|
|
sort.Slice(records, func(i, j int) bool {
|
|
if req.Order == constant.OrderAsc {
|
|
return records[i].Size < records[j].Size
|
|
}
|
|
return records[i].Size > records[j].Size
|
|
})
|
|
case "isUsed":
|
|
sort.Slice(records, func(i, j int) bool {
|
|
if req.Order == constant.OrderAsc {
|
|
return !records[i].IsUsed
|
|
}
|
|
return records[i].IsUsed
|
|
})
|
|
case "tags":
|
|
sort.Slice(records, func(i, j int) bool {
|
|
if len(records[i].Tags) == 0 || len(records[j].Tags) == 0 {
|
|
return true
|
|
}
|
|
if req.Order == constant.OrderAsc {
|
|
return records[i].Tags[0] < records[j].Tags[0]
|
|
}
|
|
return records[i].Tags[0] > records[j].Tags[0]
|
|
})
|
|
default:
|
|
sort.Slice(records, func(i, j int) bool {
|
|
if req.Order == constant.OrderAsc {
|
|
return records[i].CreatedAt.Before(records[j].CreatedAt)
|
|
}
|
|
return records[i].CreatedAt.After(records[j].CreatedAt)
|
|
})
|
|
}
|
|
|
|
imageDescriptions, _ := settingRepo.GetDescriptionList(repo.WithByType("image"))
|
|
for i := 0; i < len(list); i++ {
|
|
for _, desc := range imageDescriptions {
|
|
if "sha256:"+desc.ID == records[i].ID {
|
|
records[i].Description = desc.Description
|
|
records[i].IsPinned = desc.IsPinned
|
|
}
|
|
}
|
|
}
|
|
sort.Slice(records, func(i, j int) bool {
|
|
return records[i].IsPinned && !records[j].IsPinned
|
|
})
|
|
total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
|
if start > total {
|
|
backDatas = make([]dto.ImageInfo, 0)
|
|
} else {
|
|
if end >= total {
|
|
end = total
|
|
}
|
|
backDatas = records[start:end]
|
|
}
|
|
|
|
return int64(total), backDatas, nil
|
|
}
|
|
|
|
func (u *ImageService) ListAll() ([]dto.ImageInfo, error) {
|
|
var records []dto.ImageInfo
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer client.Close()
|
|
list, err := client.ImageList(context.Background(), image.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true})
|
|
for _, image := range list {
|
|
records = append(records, dto.ImageInfo{
|
|
ID: image.ID,
|
|
Tags: image.RepoTags,
|
|
IsUsed: checkUsed(image.ID, containers),
|
|
CreatedAt: time.Unix(image.Created, 0),
|
|
Size: image.Size,
|
|
})
|
|
}
|
|
return records, nil
|
|
}
|
|
|
|
func (u *ImageService) List() ([]dto.Options, error) {
|
|
var (
|
|
list []image.Summary
|
|
backDatas []dto.Options
|
|
)
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer client.Close()
|
|
list, err = client.ImageList(context.Background(), image.ListOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, image := range list {
|
|
for _, tag := range image.RepoTags {
|
|
backDatas = append(backDatas, dto.Options{
|
|
Option: tag,
|
|
})
|
|
}
|
|
}
|
|
return backDatas, nil
|
|
}
|
|
|
|
func (u *ImageService) ImageBuild(req dto.ImageBuild) error {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
fileName := "Dockerfile"
|
|
if req.From == "edit" {
|
|
dir := fmt.Sprintf("%s/docker/build/%s", global.Dir.DataDir, strings.ReplaceAll(req.Name, ":", "_"))
|
|
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
|
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
pathItem := fmt.Sprintf("%s/Dockerfile", dir)
|
|
file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
write := bufio.NewWriter(file)
|
|
_, _ = write.WriteString(string(req.Dockerfile))
|
|
write.Flush()
|
|
req.Dockerfile = dir
|
|
} else {
|
|
fileName = path.Base(req.Dockerfile)
|
|
req.Dockerfile = path.Dir(req.Dockerfile)
|
|
}
|
|
tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
opts := build.ImageBuildOptions{
|
|
Dockerfile: fileName,
|
|
Tags: []string{req.Name},
|
|
Remove: true,
|
|
Labels: stringsToMap(req.Tags),
|
|
BuildArgs: stringsToMap2(req.Args),
|
|
}
|
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskBuild, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("new task for image build failed, err: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
defer tar.Close()
|
|
taskItem.AddSubTask(i18n.GetMsgByKey("ImageBuild"), func(t *task.Task) error {
|
|
dockerCli := docker.NewClientWithExist(client)
|
|
if err := dockerCli.BuildImageWithProcessAndOptions(taskItem, tar, opts); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, nil)
|
|
|
|
_ = taskItem.Execute()
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImagePull(req dto.ImagePull) error {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
taskItem, err := task.NewTaskWithOps(strings.Join(req.ImageName, ","), task.TaskPull, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("new task for image pull failed, err: %v", err)
|
|
}
|
|
|
|
for _, item := range req.ImageName {
|
|
itemName := strings.ReplaceAll(path.Base(item), ":", "_")
|
|
taskItem.AddSubTask(i18n.GetWithName("ImagePull", itemName), func(t *task.Task) error {
|
|
taskItem.Logf("----------------- %s -----------------", itemName)
|
|
if req.RepoID == 0 {
|
|
pullErr := pullImages(taskItem, client, item)
|
|
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), pullErr)
|
|
return pullErr
|
|
}
|
|
|
|
options := image.PullOptions{}
|
|
imageName := item
|
|
repo, repoErr := imageRepoRepo.Get(repo.WithByID(req.RepoID))
|
|
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), repoErr)
|
|
if repoErr != nil {
|
|
return repoErr
|
|
}
|
|
if repo.Auth {
|
|
authConfig := registry.AuthConfig{
|
|
Username: repo.Username,
|
|
Password: repo.Password,
|
|
}
|
|
encodedJSON, err := json.Marshal(authConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
|
options.RegistryAuth = authStr
|
|
}
|
|
imageName = repo.DownloadUrl + "/" + item
|
|
dockerCli := docker.NewClientWithExist(client)
|
|
pullErr := dockerCli.PullImageWithProcessAndOptions(taskItem, imageName, options)
|
|
taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), pullErr)
|
|
if pullErr != nil {
|
|
return pullErr
|
|
}
|
|
return nil
|
|
}, nil)
|
|
}
|
|
go func() {
|
|
_ = taskItem.Execute()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImageLoad(req dto.ImageLoad) error {
|
|
taskItem, err := task.NewTaskWithOps(strings.Join(req.Paths, ","), task.TaskImport, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("new task for image import failed, err: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
taskItem.Log("Failed to create Docker client: " + err.Error())
|
|
return
|
|
}
|
|
defer client.Close()
|
|
|
|
for _, itemPath := range req.Paths {
|
|
currentPath := itemPath
|
|
itemName := path.Base(currentPath)
|
|
taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error {
|
|
taskItem.Logf("----------------- %s -----------------", itemName)
|
|
file, err := os.Open(currentPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
res, err := client.ImageLoad(context.TODO(), file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer res.Body.Close()
|
|
content, err := io.ReadAll(res.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if strings.Contains(string(content), "Error") {
|
|
return errors.New(string(content))
|
|
}
|
|
return nil
|
|
}, nil)
|
|
}
|
|
_ = taskItem.Execute()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImageSave(req dto.ImageSave) error {
|
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskExport, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("new task for image export failed, err: %v", err)
|
|
}
|
|
taskItem.AddSubTask(i18n.GetWithName("TaskExport", req.Name), func(t *task.Task) error {
|
|
taskItem.Log(req.TagName)
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
|
|
out, err := client.ImageSave(context.TODO(), []string{req.TagName})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
file, err := os.OpenFile(fmt.Sprintf("%s/%s.tar", req.Path, req.Name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, constant.FilePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
if _, err = io.Copy(file, out); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, nil)
|
|
go func() {
|
|
_ = taskItem.Execute()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImageTag(req dto.ImageTag) error {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
|
|
imageItem, err := client.ImageInspect(context.Background(), req.SourceID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, tag := range req.Tags {
|
|
isNew := true
|
|
for _, tagOld := range imageItem.RepoTags {
|
|
if tag == tagOld {
|
|
isNew = false
|
|
break
|
|
}
|
|
}
|
|
if isNew {
|
|
if err := client.ImageTag(context.TODO(), req.SourceID, tag); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, tagOld := range imageItem.RepoTags {
|
|
isDel := true
|
|
for _, tag := range req.Tags {
|
|
if tag == tagOld {
|
|
isDel = false
|
|
break
|
|
}
|
|
}
|
|
if isDel {
|
|
if _, err := client.ImageRemove(context.TODO(), tagOld, image.RemoveOptions{}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImagePush(req dto.ImagePush) error {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
|
|
taskItem, err := task.NewTaskWithOps(req.Name, task.TaskPush, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("new task for image push failed, err: %v", err)
|
|
}
|
|
|
|
go func() {
|
|
options := image.PushOptions{All: true}
|
|
var imageRepo model.ImageRepo
|
|
newName := ""
|
|
taskItem.AddSubTask(i18n.GetMsgByKey("ImagePush"), func(t *task.Task) error {
|
|
imageRepo, err = imageRepoRepo.Get(repo.WithByID(req.RepoID))
|
|
newName = fmt.Sprintf("%s/%s", imageRepo.DownloadUrl, req.Name)
|
|
taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
options = image.PushOptions{All: true}
|
|
authConfig := registry.AuthConfig{
|
|
Username: imageRepo.Username,
|
|
Password: imageRepo.Password,
|
|
}
|
|
encodedJSON, _ := json.Marshal(authConfig)
|
|
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
|
options.RegistryAuth = authStr
|
|
return nil
|
|
}, nil)
|
|
taskItem.AddSubTask(i18n.GetMsgByKey("ImageRenameTag"), func(t *task.Task) error {
|
|
taskItem.Log(i18n.GetWithName("ImageNewTag", newName))
|
|
if newName != req.TagName {
|
|
if err := client.ImageTag(context.TODO(), req.TagName, newName); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}, nil)
|
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskPush"), func(t *task.Task) error {
|
|
dockerCli := docker.NewClientWithExist(client)
|
|
if err := dockerCli.PushImageWithProcessAndOptions(taskItem, newName, options); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}, nil)
|
|
_ = taskItem.Execute()
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *ImageService) ImageRemove(req dto.BatchDelete) error {
|
|
client, err := docker.NewDockerClient()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer client.Close()
|
|
taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, ","), task.TaskDelete, task.TaskScopeImage, req.TaskID, 1)
|
|
if err != nil {
|
|
global.LOG.Errorf("new task for create container failed, err: %v", err)
|
|
return err
|
|
}
|
|
|
|
for _, id := range req.Names {
|
|
taskItem.AddSubTask(i18n.GetMsgByKey("TaskDelete")+id, func(t *task.Task) error {
|
|
imageItem, err := client.ImageInspect(context.TODO(), id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil {
|
|
if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") {
|
|
if strings.Contains(id, "sha256:") {
|
|
return buserr.New("ErrObjectInUsed")
|
|
}
|
|
return buserr.WithDetail("ErrInUsed", id, nil)
|
|
}
|
|
if strings.Contains(err.Error(), "image has dependent") {
|
|
return buserr.New("ErrObjectBeDependent")
|
|
}
|
|
return err
|
|
}
|
|
taskItem.Log(i18n.GetMsgWithMap("ImageRemoveHelper", map[string]interface{}{"name": id, "size": common.LoadSizeUnit2F(float64(imageItem.Size))}))
|
|
return nil
|
|
}, nil)
|
|
}
|
|
if len(req.TaskID) == 0 {
|
|
return taskItem.Execute()
|
|
}
|
|
go func() {
|
|
_ = taskItem.Execute()
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func checkUsed(imageID string, containers []container.Summary) bool {
|
|
for _, container := range containers {
|
|
if container.ImageID == imageID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|