mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
feat: 优化docker兼容
This commit is contained in:
1
go.mod
1
go.mod
@@ -51,6 +51,7 @@ require (
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/creack/pty v1.1.23 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/devhaozi/huaweicloud-sdk-go-v3 v0.0.0-20241018211007-bbebb6de5db7 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -6,6 +6,8 @@ github.com/bddjr/hlfhr v1.1.3 h1:O1Vxi7Qf0tOs9oGoSb3Q9KAGTSfh/EXyVMVSO9V1m90=
|
||||
github.com/bddjr/hlfhr v1.1.3/go.mod h1:oyIv4Q9JpCgZFdtH3KyTNWp7YYRWl4zl8k4ozrMAB4g=
|
||||
github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
|
||||
github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
|
||||
github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0=
|
||||
github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package podman
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package podman
|
||||
package docker
|
||||
|
||||
type UpdateConfig struct {
|
||||
Config string `form:"config" json:"config" validate:"required"`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package podman
|
||||
package docker
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
@@ -62,9 +62,12 @@ func (r *containerRepo) ListAll() ([]types.Container, error) {
|
||||
Host: port.IP,
|
||||
})
|
||||
}
|
||||
if len(item.Names) == 0 {
|
||||
item.Names = append(item.Names, "")
|
||||
}
|
||||
containers = append(containers, types.Container{
|
||||
ID: item.ID,
|
||||
Name: item.Names[0],
|
||||
Name: strings.TrimPrefix(item.Names[0], "/"), // https://github.com/moby/moby/issues/7519
|
||||
Image: item.Image,
|
||||
ImageID: item.ImageID,
|
||||
Command: item.Command,
|
||||
@@ -96,7 +99,7 @@ func (r *containerRepo) ListByName(names string) ([]types.Container, error) {
|
||||
// Create 创建容器
|
||||
func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(fmt.Sprintf("docker run --name %s", req.Name))
|
||||
sb.WriteString(fmt.Sprintf("docker run -d --name %s", req.Name))
|
||||
if req.PublishAllPorts {
|
||||
sb.WriteString(" -P")
|
||||
} else {
|
||||
@@ -155,65 +158,65 @@ func (r *containerRepo) Create(req *request.ContainerCreate) (string, error) {
|
||||
sb.WriteString(fmt.Sprintf(" --memory %d", req.Memory))
|
||||
}
|
||||
|
||||
sb.WriteString(" %s")
|
||||
return shell.Execf(sb.String(), req.Image)
|
||||
sb.WriteString(" %s bash")
|
||||
return shell.ExecfWithTTY(sb.String(), req.Image)
|
||||
}
|
||||
|
||||
// Remove 移除容器
|
||||
func (r *containerRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Start 启动容器
|
||||
func (r *containerRepo) Start(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker start %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker start %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Stop 停止容器
|
||||
func (r *containerRepo) Stop(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker stop %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker stop %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart 重启容器
|
||||
func (r *containerRepo) Restart(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker restart %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker restart %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Pause 暂停容器
|
||||
func (r *containerRepo) Pause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker pause %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker pause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Unpause 恢复容器
|
||||
func (r *containerRepo) Unpause(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker unpause %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker unpause %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Kill 杀死容器
|
||||
func (r *containerRepo) Kill(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker kill %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker kill %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename 重命名容器
|
||||
func (r *containerRepo) Rename(id string, newName string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rename %s %s", id, newName)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rename %s %s", id, newName)
|
||||
return err
|
||||
}
|
||||
|
||||
// Logs 查看容器日志
|
||||
func (r *containerRepo) Logs(id string) (string, error) {
|
||||
return shell.ExecfWithTimeout(30*time.Second, "docker logs %s", id)
|
||||
return shell.ExecfWithTimeout(120*time.Second, "docker logs %s", id)
|
||||
}
|
||||
|
||||
// Prune 清理未使用的容器
|
||||
func (r *containerRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker container prune -f")
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker container prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
sb.WriteString(fmt.Sprintf("docker pull %s", req.Name))
|
||||
|
||||
if _, err := shell.Execf(sb.String()); err != nil { // nolint: govet
|
||||
return fmt.Errorf("pull failed: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -88,12 +88,12 @@ func (r *containerImageRepo) Pull(req *request.ContainerImagePull) error {
|
||||
|
||||
// Remove 删除镜像
|
||||
func (r *containerImageRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker rmi %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker rmi %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的镜像
|
||||
func (r *containerImageRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker image prune -f")
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker image prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -110,17 +110,17 @@ func (r *containerNetworkRepo) Create(req *request.ContainerNetworkCreate) (stri
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
return shell.ExecfWithTimeout(30*time.Second, sb.String()) // nolint: govet
|
||||
return shell.ExecfWithTimeout(120*time.Second, sb.String()) // nolint: govet
|
||||
}
|
||||
|
||||
// Remove 删除网络
|
||||
func (r *containerNetworkRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker network rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的网络
|
||||
func (r *containerNetworkRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker network prune -f")
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker network prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -84,17 +84,17 @@ func (r *containerVolumeRepo) Create(req *request.ContainerVolumeCreate) (string
|
||||
sb.WriteString(fmt.Sprintf(" --opt %s=%s", option.Key, option.Value))
|
||||
}
|
||||
|
||||
return shell.ExecfWithTimeout(30*time.Second, sb.String()) // nolint: govet
|
||||
return shell.ExecfWithTimeout(120*time.Second, sb.String()) // nolint: govet
|
||||
}
|
||||
|
||||
// Remove 删除存储卷
|
||||
func (r *containerVolumeRepo) Remove(id string) error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker volume rm -f %s", id)
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume rm -f %s", id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune 清理未使用的存储卷
|
||||
func (r *containerVolumeRepo) Prune() error {
|
||||
_, err := shell.ExecfWithTimeout(30*time.Second, "docker volume prune -f")
|
||||
_, err := shell.ExecfWithTimeout(120*time.Second, "docker volume prune -f")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -197,9 +197,8 @@ func (r *Firewall) Port(rule FireInfo, operation Operation) error {
|
||||
}
|
||||
protocols := strings.Split(string(rule.Protocol), "/")
|
||||
for protocol := range slices.Values(protocols) {
|
||||
stdout, err := shell.Execf("firewall-cmd --zone=public --%s-port=%d-%d/%s --permanent", operation, rule.PortStart, rule.PortEnd, protocol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port %d-%d/%s failed, err: %s", operation, rule.PortStart, rule.PortEnd, protocol, stdout)
|
||||
if _, err := shell.Execf("firewall-cmd --zone=public --%s-port=%d-%d/%s --permanent", operation, rule.PortStart, rule.PortEnd, protocol); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +242,7 @@ func (r *Firewall) RichRules(rule FireInfo, operation Operation) error {
|
||||
ruleBuilder.WriteString(string(rule.Strategy))
|
||||
_, err := shell.Execf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleBuilder.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s rich rules (%s) failed, err: %v", operation, ruleBuilder.String(), err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +268,7 @@ func (r *Firewall) Forward(rule Forward, operation Operation) error {
|
||||
|
||||
_, err := shell.Execf(ruleBuilder.String()) // nolint: govet
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s port forward failed, err: %v", operation, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ import (
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/creack/pty"
|
||||
)
|
||||
|
||||
// Execf 执行 shell 命令
|
||||
@@ -26,12 +29,11 @@ func Execf(shell string, args ...any) (string, error) {
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return strings.TrimSpace(stdout.String()), errors.New(strings.TrimSpace(stderr.String()))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), err
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// ExecfAsync 异步执行 shell 命令
|
||||
@@ -50,7 +52,7 @@ func ExecfAsync(shell string, args ...any) error {
|
||||
|
||||
go func() {
|
||||
if err = cmd.Wait(); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
fmt.Println(fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(err.Error())))
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -72,7 +74,7 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
done := make(chan error)
|
||||
@@ -83,10 +85,10 @@ func ExecfWithTimeout(timeout time.Duration, shell string, args ...any) (string,
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
_ = cmd.Process.Kill()
|
||||
return strings.TrimSpace(stdout.String()), errors.New("执行超时")
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), "timeout")
|
||||
case err = <-done:
|
||||
if err != nil {
|
||||
return strings.TrimSpace(stdout.String()), errors.New(strings.TrimSpace(stderr.String()))
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,12 +142,40 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return strings.TrimSpace(stdout.String()), errors.New(strings.TrimSpace(stderr.String()))
|
||||
if err := cmd.Run(); err != nil {
|
||||
return strings.TrimSpace(stdout.String()), fmt.Errorf("run %s failed, err: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(stdout.String()), err
|
||||
return strings.TrimSpace(stdout.String()), nil
|
||||
}
|
||||
|
||||
// ExecfWithTTY 在伪终端下执行 shell 命令
|
||||
func ExecfWithTTY(shell string, args ...any) (string, error) {
|
||||
if !preCheckArg(args) {
|
||||
return "", errors.New("command contains illegal characters")
|
||||
}
|
||||
|
||||
_ = os.Setenv("LC_ALL", "C")
|
||||
cmd := exec.Command("bash", "-i", "-c", fmt.Sprintf(shell, args...))
|
||||
|
||||
var out bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr // https://github.com/creack/pty/issues/147 取 stderr
|
||||
|
||||
f, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("run %s failed", fmt.Sprintf(shell, args...))
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err = io.Copy(&out, f); ptyError(err) != nil {
|
||||
return "", fmt.Errorf("run %s failed, out: %s, err: %w", fmt.Sprintf(shell, args...), strings.TrimSpace(out.String()), err)
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
return "", fmt.Errorf("run %s failed, out: %s", fmt.Sprintf(shell, args...), strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strings.TrimSpace(out.String()), nil
|
||||
}
|
||||
|
||||
func preCheckArg(args []any) bool {
|
||||
@@ -158,3 +188,15 @@ func preCheckArg(args []any) bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Linux kernel return EIO when attempting to read from a master pseudo
|
||||
// terminal which no longer has an open slave. So ignore error here.
|
||||
// See https://github.com/creack/pty/issues/21
|
||||
func ptyError(err error) error {
|
||||
var pathErr *os.PathError
|
||||
if !errors.As(err, &pathErr) || !errors.Is(pathErr.Err, syscall.EIO) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -24,12 +24,23 @@ export function createAppInstallGuard(router: Router) {
|
||||
}
|
||||
// 容器
|
||||
if (to.path.startsWith('/container')) {
|
||||
await app.isInstalled('podman').then((res) => {
|
||||
if (!res.data.installed) {
|
||||
window.$message.error(`容器引擎 ${res.data.name} 未安装`)
|
||||
return router.push({ name: 'app-index' })
|
||||
let flag = false
|
||||
await app.isInstalled('docker').then((res) => {
|
||||
if (res.data.installed) {
|
||||
flag = true
|
||||
}
|
||||
})
|
||||
if (!flag) {
|
||||
await app.isInstalled('podman').then((res) => {
|
||||
if (res.data.installed) {
|
||||
flag = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!flag) {
|
||||
window.$message.error(`容器引擎 Docker / Podman 未安装`)
|
||||
return router.push({ name: 'app-index' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const columns: any = [
|
||||
{
|
||||
title: 'ID',
|
||||
key: 'id',
|
||||
width: 150,
|
||||
minWidth: 400,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true }
|
||||
},
|
||||
@@ -40,7 +40,7 @@ const columns: any = [
|
||||
{
|
||||
title: '镜像',
|
||||
key: 'repo_tags',
|
||||
minWidth: 300,
|
||||
minWidth: 200,
|
||||
resizable: true,
|
||||
ellipsis: { tooltip: true },
|
||||
render(row: any): any {
|
||||
|
||||
Reference in New Issue
Block a user