Skip to content

Commit

Permalink
feat: 优化docker兼容
Browse files Browse the repository at this point in the history
  • Loading branch information
devhaozi committed Oct 30, 2024
1 parent 52e8830 commit f01afac
Show file tree
Hide file tree
Showing 13 changed files with 106 additions and 48 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
2 changes: 1 addition & 1 deletion internal/apps/docker/init.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package podman
package docker

import (
"github.com/go-chi/chi/v5"
Expand Down
2 changes: 1 addition & 1 deletion internal/apps/docker/request.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package podman
package docker

type UpdateConfig struct {
Config string `form:"config" json:"config" validate:"required"`
Expand Down
2 changes: 1 addition & 1 deletion internal/apps/docker/service.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package podman
package docker

import (
"net/http"
Expand Down
31 changes: 17 additions & 14 deletions internal/data/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions internal/data/container_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,20 @@ 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
}

// 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
}
6 changes: 3 additions & 3 deletions internal/data/container_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
6 changes: 3 additions & 3 deletions internal/data/container_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
9 changes: 4 additions & 5 deletions pkg/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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
}
}

Expand All @@ -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
}
}

Expand Down
64 changes: 53 additions & 11 deletions pkg/shell/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import (
"os/exec"
"slices"
"strings"
"syscall"
"time"

"github.com/creack/pty"
)

// Execf 执行 shell 命令
Expand All @@ -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 命令
Expand All @@ -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())))
}
}()

Expand All @@ -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)
Expand All @@ -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()))
}
}

Expand Down Expand Up @@ -140,12 +142,40 @@ func ExecfWithDir(dir, shell string, args ...any) (string, error) {
cmd.Stdout = &stdout
cmd.Stderr = &stderr

err := cmd.Run()
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()), 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 strings.TrimSpace(stdout.String()), errors.New(strings.TrimSpace(stderr.String()))
return "", fmt.Errorf("run %s failed", fmt.Sprintf(shell, args...))
}
defer f.Close()

return strings.TrimSpace(stdout.String()), err
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 {
Expand All @@ -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
}
19 changes: 15 additions & 4 deletions web/src/router/guard/app-install-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' })
}
}
})
}
4 changes: 2 additions & 2 deletions web/src/views/container/ImageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const columns: any = [
{
title: 'ID',
key: 'id',
width: 150,
minWidth: 400,
resizable: true,
ellipsis: { tooltip: true }
},
Expand All @@ -40,7 +40,7 @@ const columns: any = [
{
title: '镜像',
key: 'repo_tags',
minWidth: 300,
minWidth: 200,
resizable: true,
ellipsis: { tooltip: true },
render(row: any): any {
Expand Down

0 comments on commit f01afac

Please sign in to comment.