From beb271b3b94ba817f9bbe20a21fb667426cb8351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sun, 27 Oct 2024 18:12:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=B9=E5=99=A8=E9=95=9C=E5=83=8F?= =?UTF-8?q?=E4=BD=BF=E7=94=A8cli=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/container_image.go | 5 +- internal/data/container.go | 7 +- internal/data/container_image.go | 103 +++++++++++++++++----------- internal/service/container.go | 16 +---- internal/service/container_image.go | 20 +----- pkg/types/container_image.go | 13 ++++ 6 files changed, 85 insertions(+), 79 deletions(-) create mode 100644 pkg/types/container_image.go diff --git a/internal/biz/container_image.go b/internal/biz/container_image.go index 9cb3497a..c7c98084 100644 --- a/internal/biz/container_image.go +++ b/internal/biz/container_image.go @@ -1,13 +1,12 @@ package biz import ( - "github.com/docker/docker/api/types/image" - "github.com/TheTNB/panel/internal/http/request" + "github.com/TheTNB/panel/pkg/types" ) type ContainerImageRepo interface { - List() ([]image.Summary, error) + List() ([]types.ContainerImage, error) Pull(req *request.ContainerImagePull) error Remove(id string) error Prune() error diff --git a/internal/data/container.go b/internal/data/container.go index a0776200..a6640894 100644 --- a/internal/data/container.go +++ b/internal/data/container.go @@ -40,7 +40,7 @@ func (r *containerRepo) ListAll() ([]types.Container, error) { var containers []types.Container for _, line := range lines { if line == "" { - continue // 跳过空行 + continue } var item struct { @@ -60,11 +60,10 @@ func (r *containerRepo) ListAll() ([]types.Container, error) { Status string `json:"Status"` } if err = json.Unmarshal([]byte(line), &item); err != nil { - return nil, err + return nil, fmt.Errorf("unmarshal failed: %w", err) } createdAt, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", item.CreatedAt) - containers = append(containers, types.Container{ ID: item.ID, Name: item.Names, @@ -97,7 +96,7 @@ func (r *containerRepo) ListByName(names string) ([]types.Container, error) { // Create 创建容器 func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) { - sb := strings.Builder{} + var sb strings.Builder sb.WriteString(fmt.Sprintf("%s create --name %s --image %s", r.cmd, req.Name, req.Image)) for _, port := range req.Ports { diff --git a/internal/data/container_image.go b/internal/data/container_image.go index ee4fec79..a80c9f5f 100644 --- a/internal/data/container_image.go +++ b/internal/data/container_image.go @@ -1,78 +1,103 @@ package data import ( - "context" - "encoding/base64" "encoding/json" - "io" + "fmt" + "strings" + "time" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/client" + "github.com/spf13/cast" "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/internal/http/request" + "github.com/TheTNB/panel/pkg/shell" + "github.com/TheTNB/panel/pkg/types" ) type containerImageRepo struct { - client *client.Client + cmd string } -func NewContainerImageRepo(sock ...string) biz.ContainerImageRepo { - if len(sock) == 0 { - sock = append(sock, "/run/podman/podman.sock") +func NewContainerImageRepo(cmd ...string) biz.ContainerImageRepo { + if len(cmd) == 0 { + cmd = append(cmd, "docker") } - cli, _ := client.NewClientWithOpts(client.WithHost("unix://"+sock[0]), client.WithAPIVersionNegotiation()) return &containerImageRepo{ - client: cli, + cmd: cmd[0], } } // List 列出镜像 -func (r *containerImageRepo) List() ([]image.Summary, error) { - return r.client.ImageList(context.Background(), image.ListOptions{ - All: true, - }) +func (r *containerImageRepo) List() ([]types.ContainerImage, error) { + output, err := shell.ExecfWithTimeout(10*time.Second, "%s images -a --format '{{json .}}'", r.cmd) + if err != nil { + return nil, err + } + lines := strings.Split(output, "\n") + + var images []types.ContainerImage + for _, line := range lines { + if line == "" { + continue + } + + var item struct { + ID string `json:"ID"` + Containers string `json:"Containers"` + Repository string `json:"Repository"` + Tag string `json:"Tag"` + Digest string `json:"Digest"` + CreatedAt string `json:"CreatedAt"` + Size string `json:"Size"` + SharedSize string `json:"SharedSize"` + VirtualSize string `json:"VirtualSize"` + } + if err = json.Unmarshal([]byte(line), &item); err != nil { + return nil, fmt.Errorf("unmarshal failed: %w", err) + } + + createdAt, _ := time.Parse("2006-01-02 15:04:05 -0700 MST", item.CreatedAt) + images = append(images, types.ContainerImage{ + ID: item.ID, + Containers: cast.ToInt64(item.Containers), + Tag: item.Tag, + CreatedAt: createdAt, + Size: item.Size, + }) + } + + return images, nil } // Pull 拉取镜像 func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error { - options := image.PullOptions{} + var sb strings.Builder + if req.Auth { - authConfig := registry.AuthConfig{ - Username: req.Username, - Password: req.Password, + sb.WriteString(fmt.Sprintf("%s login -u %s -p %s", r.cmd, req.Username, req.Password)) + if _, err := shell.ExecfWithTimeout(1*time.Minute, sb.String()); err != nil { + return fmt.Errorf("login failed: %w", err) } - encodedJSON, err := json.Marshal(authConfig) - if err != nil { - return err - } - authStr := base64.URLEncoding.EncodeToString(encodedJSON) - options.RegistryAuth = authStr + sb.Reset() } - out, err := r.client.ImagePull(context.Background(), req.Name, options) - if err != nil { - return err - } - defer out.Close() + sb.WriteString(fmt.Sprintf("%s pull %s", r.cmd, req.Name)) - _, err = io.Copy(io.Discard, out) - return err + if _, err := shell.ExecfWithTimeout(20*time.Minute, sb.String()); err != nil { // nolint: govet + return fmt.Errorf("pull failed: %w", err) + } + + return nil } // Remove 删除镜像 func (r *containerImageRepo) Remove(id string) error { - _, err := r.client.ImageRemove(context.Background(), id, image.RemoveOptions{ - Force: true, - PruneChildren: true, - }) + _, err := shell.ExecfWithTimeout(30*time.Second, "%s rmi %s", r.cmd, id) return err } // Prune 清理未使用的镜像 func (r *containerImageRepo) Prune() error { - _, err := r.client.ImagesPrune(context.Background(), filters.NewArgs()) + _, err := shell.ExecfWithTimeout(30*time.Second, "%s image prune -f", r.cmd) return err } diff --git a/internal/service/container.go b/internal/service/container.go index bd6c7652..ed5d651b 100644 --- a/internal/service/container.go +++ b/internal/service/container.go @@ -27,24 +27,10 @@ func (s *ContainerService) List(w http.ResponseWriter, r *http.Request) { } paged, total := Paginate(r, containers) - items := make([]any, 0) - for _, item := range paged { - items = append(items, map[string]any{ - "id": item.ID, - "name": item.Name, - "image": item.Image, - "command": item.Command, - "created_at": item.CreatedAt, - "ports": item.Ports, - "labels": item.Labels, - "state": item.State, - "status": item.Status, - }) - } Success(w, chix.M{ "total": total, - "items": items, + "items": paged, }) } diff --git a/internal/service/container_image.go b/internal/service/container_image.go index 0a5cf8e8..f3db54e8 100644 --- a/internal/service/container_image.go +++ b/internal/service/container_image.go @@ -1,15 +1,12 @@ package service import ( - "net/http" - "time" - "github.com/go-rat/chix" + "net/http" "github.com/TheTNB/panel/internal/biz" "github.com/TheTNB/panel/internal/data" "github.com/TheTNB/panel/internal/http/request" - "github.com/TheTNB/panel/pkg/str" ) type ContainerImageService struct { @@ -31,22 +28,9 @@ func (s *ContainerImageService) List(w http.ResponseWriter, r *http.Request) { paged, total := Paginate(r, images) - items := make([]any, 0) - for _, item := range paged { - items = append(items, map[string]any{ - "id": item.ID, - "created": time.Unix(item.Created, 0).Format(time.DateTime), - "containers": item.Containers, - "size": str.FormatBytes(float64(item.Size)), - "labels": item.Labels, - "repo_tags": item.RepoTags, - "repo_digests": item.RepoDigests, - }) - } - Success(w, chix.M{ "total": total, - "items": items, + "items": paged, }) } diff --git a/pkg/types/container_image.go b/pkg/types/container_image.go new file mode 100644 index 00000000..1ec86c82 --- /dev/null +++ b/pkg/types/container_image.go @@ -0,0 +1,13 @@ +package types + +import ( + "time" +) + +type ContainerImage struct { + ID string `json:"id"` + Containers int64 `json:"containers"` + Tag string `json:"tag"` + Size string `json:"size"` + CreatedAt time.Time `json:"created_at"` +}