mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-23 18:49:21 +08:00
feat: 完成容器终端功能
This commit is contained in:
parent
3ca1e97469
commit
e58e5cef0d
@ -1,10 +1,10 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -1,13 +1,18 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/terminal"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (b *BaseApi) SearchContainer(c *gin.Context) {
|
||||
@ -158,6 +163,59 @@ func (b *BaseApi) Inspect(c *gin.Context) {
|
||||
helper.SuccessWithData(c, result)
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerExec(c *gin.Context) {
|
||||
containerID := c.Query("containerid")
|
||||
command := c.Query("command")
|
||||
user := c.Query("user")
|
||||
cols, err := strconv.Atoi(c.DefaultQuery("cols", "80"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
rows, err := strconv.Atoi(c.DefaultQuery("rows", "40"))
|
||||
if err != nil {
|
||||
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
|
||||
return
|
||||
}
|
||||
|
||||
wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("gin context http handler failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
client, err := docker.NewDockerClient()
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "New docker client failed.")) {
|
||||
return
|
||||
}
|
||||
conf := types.ExecConfig{Tty: true, Cmd: []string{command}, AttachStderr: true, AttachStdin: true, AttachStdout: true, User: user}
|
||||
ir, err := client.ContainerExecCreate(context.TODO(), containerID, conf)
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set exec conf.")) {
|
||||
return
|
||||
}
|
||||
hr, err := client.ContainerExecAttach(c, ir.ID, types.ExecStartCheck{Detach: false, Tty: true})
|
||||
if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection.")) {
|
||||
return
|
||||
}
|
||||
defer hr.Close()
|
||||
|
||||
sws, err := terminal.NewExecConn(cols, rows, wsConn, hr.Conn)
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
|
||||
quitChan := make(chan bool, 3)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sws.Start(ctx, quitChan)
|
||||
<-quitChan
|
||||
cancel()
|
||||
|
||||
if wshandleError(wsConn, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseApi) ContainerLogs(c *gin.Context) {
|
||||
var req dto.ContainerLog
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
|
@ -1,10 +1,10 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/api/v1/helper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
|
@ -112,6 +112,7 @@ type BatchDelete struct {
|
||||
type ComposeInfo struct {
|
||||
Name string `json:"name"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
ContainerNumber int `json:"containerNumber"`
|
||||
ConfigFile string `json:"configFile"`
|
||||
Workdir string `json:"workdir"`
|
||||
|
@ -1,8 +1,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type ComposeTemplateRepo struct{}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package repo
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
)
|
||||
|
||||
type ImageRepoRepo struct{}
|
||||
|
@ -4,6 +4,16 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
@ -15,15 +25,6 @@ import (
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DatabaseOp string
|
||||
|
@ -1,8 +1,8 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -1,28 +1,23 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/go-connections/nat"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@ -30,10 +25,6 @@ import (
|
||||
|
||||
type ContainerService struct{}
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
|
||||
type IContainerService interface {
|
||||
Page(req dto.PageContainer) (int64, interface{}, error)
|
||||
PageNetwork(req dto.PageInfo) (int64, interface{}, error)
|
||||
@ -101,128 +92,6 @@ func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, erro
|
||||
return int64(total), backDatas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.PageInfo) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
BackDatas []dto.ComposeInfo
|
||||
)
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
composeMap := make(map[string]dto.ComposeInfo)
|
||||
for _, container := range list {
|
||||
if name, ok := container.Labels[composeProjectLabel]; ok {
|
||||
containerItem := dto.ComposeContainer{
|
||||
ContainerID: container.ID,
|
||||
Name: container.Names[0][1:],
|
||||
State: container.State,
|
||||
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
if compose, has := composeMap[name]; has {
|
||||
compose.ContainerNumber++
|
||||
compose.Containers = append(compose.Containers, containerItem)
|
||||
composeMap[name] = compose
|
||||
} else {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
composeItem := dto.ComposeInfo{
|
||||
ContainerNumber: 1,
|
||||
CreatedAt: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
ConfigFile: config,
|
||||
Workdir: workdir,
|
||||
Containers: []dto.ComposeContainer{containerItem},
|
||||
}
|
||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
||||
composeItem.Path = config
|
||||
} else {
|
||||
composeItem.Path = workdir
|
||||
}
|
||||
composeMap[name] = composeItem
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, value := range composeMap {
|
||||
value.Name = key
|
||||
records = append(records, value)
|
||||
}
|
||||
total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
BackDatas = make([]dto.ComposeInfo, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
return int64(total), BackDatas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
if req.From == "template" {
|
||||
template, err := composeRepo.Get(commonRepo.WithByID(req.Template))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.From = template.From
|
||||
if req.From == "edit" {
|
||||
req.File = template.Content
|
||||
} else {
|
||||
req.Path = template.Path
|
||||
}
|
||||
}
|
||||
if req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/%s", constant.TmpComposeBuildDir, req.Name)
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/docker-compose.yml", dir)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(string(req.File))
|
||||
write.Flush()
|
||||
req.Path = path
|
||||
}
|
||||
go func() {
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
global.LOG.Debugf("docker-compose up %s failed, err: %v", req.Name, err)
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("docker-compose up %s successful, logs: %v", req.Name, string(stdout))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, req.Operation)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Debugf("docker-compose %s %s successful: logs: %v", req.Operation, req.Path, string(stdout))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
@ -387,187 +256,6 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, erro
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var (
|
||||
data []dto.Network
|
||||
records []types.NetworkResource
|
||||
)
|
||||
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]types.NetworkResource, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = list[start:end]
|
||||
}
|
||||
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for key, val := range item.Labels {
|
||||
tag = append(tag, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
var ipam network.IPAMConfig
|
||||
if len(item.IPAM.Config) > 0 {
|
||||
ipam = item.IPAM.Config[0]
|
||||
}
|
||||
data = append(data, dto.Network{
|
||||
ID: item.ID,
|
||||
CreatedAt: item.Created,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
IPAMDriver: item.IPAM.Driver,
|
||||
Subnet: ipam.Subnet,
|
||||
Gateway: ipam.Gateway,
|
||||
Attachable: item.Attachable,
|
||||
Labels: tag,
|
||||
})
|
||||
}
|
||||
|
||||
return int64(total), data, nil
|
||||
}
|
||||
func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range req.Ids {
|
||||
if err := client.NetworkRemove(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
ipam network.IPAMConfig
|
||||
hasConf bool
|
||||
)
|
||||
if len(req.Subnet) != 0 {
|
||||
ipam.Subnet = req.Subnet
|
||||
hasConf = true
|
||||
}
|
||||
if len(req.Gateway) != 0 {
|
||||
ipam.Gateway = req.Gateway
|
||||
hasConf = true
|
||||
}
|
||||
if len(req.IPRange) != 0 {
|
||||
ipam.IPRange = req.IPRange
|
||||
hasConf = true
|
||||
}
|
||||
|
||||
options := types.NetworkCreate{
|
||||
Driver: req.Driver,
|
||||
Options: stringsToMap(req.Options),
|
||||
Labels: stringsToMap(req.Labels),
|
||||
}
|
||||
if hasConf {
|
||||
options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}}
|
||||
}
|
||||
if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
list, err := client.VolumeList(context.TODO(), filters.NewArgs())
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var (
|
||||
data []dto.Volume
|
||||
records []*types.Volume
|
||||
)
|
||||
total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]*types.Volume, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = list.Volumes[start:end]
|
||||
}
|
||||
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for _, val := range item.Labels {
|
||||
tag = append(tag, val)
|
||||
}
|
||||
createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt)
|
||||
data = append(data, dto.Volume{
|
||||
CreatedAt: createTime,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
Mountpoint: item.Mountpoint,
|
||||
Labels: tag,
|
||||
})
|
||||
}
|
||||
|
||||
return int64(total), data, nil
|
||||
}
|
||||
func (u *ContainerService) ListVolume() ([]dto.Options, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.VolumeList(context.TODO(), filters.NewArgs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []dto.Options
|
||||
for _, item := range list.Volumes {
|
||||
data = append(data, dto.Options{
|
||||
Option: item.Name,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range req.Ids {
|
||||
if err := client.VolumeRemove(context.TODO(), id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := volume.VolumeCreateBody{
|
||||
Name: req.Name,
|
||||
Driver: req.Driver,
|
||||
DriverOpts: stringsToMap(req.Options),
|
||||
Labels: stringsToMap(req.Labels),
|
||||
}
|
||||
if _, err := client.VolumeCreate(context.TODO(), options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func stringsToMap(list []string) map[string]string {
|
||||
var lableMap = make(map[string]string)
|
||||
for _, label := range list {
|
||||
|
149
backend/app/service/container_compose.go
Normal file
149
backend/app/service/container_compose.go
Normal file
@ -0,0 +1,149 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const composeProjectLabel = "com.docker.compose.project"
|
||||
const composeConfigLabel = "com.docker.compose.project.config_files"
|
||||
const composeWorkdirLabel = "com.docker.compose.project.working_dir"
|
||||
const composeCreatedBy = "createdBy"
|
||||
|
||||
func (u *ContainerService) PageCompose(req dto.PageInfo) (int64, interface{}, error) {
|
||||
var (
|
||||
records []dto.ComposeInfo
|
||||
BackDatas []dto.ComposeInfo
|
||||
)
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", composeProjectLabel)
|
||||
|
||||
list, err := client.ContainerList(context.Background(), options)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
composeMap := make(map[string]dto.ComposeInfo)
|
||||
for _, container := range list {
|
||||
if name, ok := container.Labels[composeProjectLabel]; ok {
|
||||
containerItem := dto.ComposeContainer{
|
||||
ContainerID: container.ID,
|
||||
Name: container.Names[0][1:],
|
||||
State: container.State,
|
||||
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
if compose, has := composeMap[name]; has {
|
||||
compose.ContainerNumber++
|
||||
compose.Containers = append(compose.Containers, containerItem)
|
||||
composeMap[name] = compose
|
||||
} else {
|
||||
config := container.Labels[composeConfigLabel]
|
||||
workdir := container.Labels[composeWorkdirLabel]
|
||||
composeItem := dto.ComposeInfo{
|
||||
ContainerNumber: 1,
|
||||
CreatedAt: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
|
||||
ConfigFile: config,
|
||||
Workdir: workdir,
|
||||
Containers: []dto.ComposeContainer{containerItem},
|
||||
}
|
||||
createdBy, ok := container.Labels[composeCreatedBy]
|
||||
if ok {
|
||||
composeItem.CreatedBy = createdBy
|
||||
}
|
||||
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) {
|
||||
composeItem.Path = config
|
||||
} else {
|
||||
composeItem.Path = workdir
|
||||
}
|
||||
composeMap[name] = composeItem
|
||||
}
|
||||
}
|
||||
}
|
||||
for key, value := range composeMap {
|
||||
value.Name = key
|
||||
records = append(records, value)
|
||||
}
|
||||
total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
BackDatas = make([]dto.ComposeInfo, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
BackDatas = records[start:end]
|
||||
}
|
||||
return int64(total), BackDatas, nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error {
|
||||
if req.From == "template" {
|
||||
template, err := composeRepo.Get(commonRepo.WithByID(req.Template))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.From = template.From
|
||||
if req.From == "edit" {
|
||||
req.File = template.Content
|
||||
} else {
|
||||
req.Path = template.Path
|
||||
}
|
||||
}
|
||||
if req.From == "edit" {
|
||||
dir := fmt.Sprintf("%s/%s", constant.TmpComposeBuildDir, req.Name)
|
||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path := fmt.Sprintf("%s/docker-compose.yml", dir)
|
||||
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
write := bufio.NewWriter(file)
|
||||
_, _ = write.WriteString(string(req.File))
|
||||
write.Flush()
|
||||
req.Path = path
|
||||
}
|
||||
go func() {
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, "up", "-d")
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
global.LOG.Debugf("docker-compose up %s failed, err: %v", req.Name, err)
|
||||
return
|
||||
}
|
||||
global.LOG.Debugf("docker-compose up %s successful, logs: %v", req.Name, string(stdout))
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error {
|
||||
cmd := exec.Command("docker-compose", "-f", req.Path, req.Operation)
|
||||
stdout, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
global.LOG.Debugf("docker-compose %s %s successful: logs: %v", req.Operation, req.Path, string(stdout))
|
||||
|
||||
return err
|
||||
}
|
106
backend/app/service/container_network.go
Normal file
106
backend/app/service/container_network.go
Normal file
@ -0,0 +1,106 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
func (u *ContainerService) PageNetwork(req dto.PageInfo) (int64, interface{}, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
list, err := client.NetworkList(context.TODO(), types.NetworkListOptions{})
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var (
|
||||
data []dto.Network
|
||||
records []types.NetworkResource
|
||||
)
|
||||
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]types.NetworkResource, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = list[start:end]
|
||||
}
|
||||
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for key, val := range item.Labels {
|
||||
tag = append(tag, fmt.Sprintf("%s=%s", key, val))
|
||||
}
|
||||
var ipam network.IPAMConfig
|
||||
if len(item.IPAM.Config) > 0 {
|
||||
ipam = item.IPAM.Config[0]
|
||||
}
|
||||
data = append(data, dto.Network{
|
||||
ID: item.ID,
|
||||
CreatedAt: item.Created,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
IPAMDriver: item.IPAM.Driver,
|
||||
Subnet: ipam.Subnet,
|
||||
Gateway: ipam.Gateway,
|
||||
Attachable: item.Attachable,
|
||||
Labels: tag,
|
||||
})
|
||||
}
|
||||
|
||||
return int64(total), data, nil
|
||||
}
|
||||
func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range req.Ids {
|
||||
if err := client.NetworkRemove(context.TODO(), id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *ContainerService) CreateNetwork(req dto.NetworkCreat) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
ipam network.IPAMConfig
|
||||
hasConf bool
|
||||
)
|
||||
if len(req.Subnet) != 0 {
|
||||
ipam.Subnet = req.Subnet
|
||||
hasConf = true
|
||||
}
|
||||
if len(req.Gateway) != 0 {
|
||||
ipam.Gateway = req.Gateway
|
||||
hasConf = true
|
||||
}
|
||||
if len(req.IPRange) != 0 {
|
||||
ipam.IPRange = req.IPRange
|
||||
hasConf = true
|
||||
}
|
||||
|
||||
options := types.NetworkCreate{
|
||||
Driver: req.Driver,
|
||||
Options: stringsToMap(req.Options),
|
||||
Labels: stringsToMap(req.Labels),
|
||||
}
|
||||
if hasConf {
|
||||
options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}}
|
||||
}
|
||||
if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
98
backend/app/service/container_volume.go
Normal file
98
backend/app/service/container_volume.go
Normal file
@ -0,0 +1,98 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
)
|
||||
|
||||
func (u *ContainerService) PageVolume(req dto.PageInfo) (int64, interface{}, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
list, err := client.VolumeList(context.TODO(), filters.NewArgs())
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
var (
|
||||
data []dto.Volume
|
||||
records []*types.Volume
|
||||
)
|
||||
total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize
|
||||
if start > total {
|
||||
records = make([]*types.Volume, 0)
|
||||
} else {
|
||||
if end >= total {
|
||||
end = total
|
||||
}
|
||||
records = list.Volumes[start:end]
|
||||
}
|
||||
|
||||
for _, item := range records {
|
||||
tag := make([]string, 0)
|
||||
for _, val := range item.Labels {
|
||||
tag = append(tag, val)
|
||||
}
|
||||
createTime, _ := time.Parse("2006-01-02T15:04:05Z", item.CreatedAt)
|
||||
data = append(data, dto.Volume{
|
||||
CreatedAt: createTime,
|
||||
Name: item.Name,
|
||||
Driver: item.Driver,
|
||||
Mountpoint: item.Mountpoint,
|
||||
Labels: tag,
|
||||
})
|
||||
}
|
||||
|
||||
return int64(total), data, nil
|
||||
}
|
||||
func (u *ContainerService) ListVolume() ([]dto.Options, error) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := client.VolumeList(context.TODO(), filters.NewArgs())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var data []dto.Options
|
||||
for _, item := range list.Volumes {
|
||||
data = append(data, dto.Options{
|
||||
Option: item.Name,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range req.Ids {
|
||||
if err := client.VolumeRemove(context.TODO(), id, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *ContainerService) CreateVolume(req dto.VolumeCreat) error {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := volume.VolumeCreateBody{
|
||||
Name: req.Name,
|
||||
Driver: req.Driver,
|
||||
DriverOpts: stringsToMap(req.Options),
|
||||
Labels: stringsToMap(req.Labels),
|
||||
}
|
||||
if _, err := client.VolumeCreate(context.TODO(), options); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -38,7 +38,6 @@ var (
|
||||
tagRepo = repo.RepoGroupApp.TagRepo
|
||||
appInstallRepo = repo.RepoGroupApp.AppInstallRepo
|
||||
appInstallResourceRepo = repo.RepoGroupApp.AppInstallResourceRpo
|
||||
appContainerRepo = repo.RepoGroupApp.AppContainerRepo
|
||||
dataBaseRepo = repo.RepoGroupApp.DatabaseRepo
|
||||
appInstallBackupRepo = repo.RepoGroupApp.AppInstallBackupRepo
|
||||
)
|
||||
|
@ -11,10 +11,10 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -1,115 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/utils/docker"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
)
|
||||
|
||||
func TestImage(t *testing.T) {
|
||||
file, err := os.OpenFile(("/tmp/nginx.tar"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
out, err := client.ImageSave(context.TODO(), []string{"nginx:1.14.2"})
|
||||
fmt.Println(err)
|
||||
defer out.Close()
|
||||
if _, err = io.Copy(file, out); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
tar, err := archive.TarWithOptions("/tmp/testbuild/", &archive.TarOptions{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
opts := types.ImageBuildOptions{
|
||||
Dockerfile: "Dockerfile",
|
||||
Tags: []string{"hello/test:v1"},
|
||||
Remove: true,
|
||||
}
|
||||
res, err := client.ImageBuild(context.TODO(), tar, opts)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
func TestDeam(t *testing.T) {
|
||||
file, err := ioutil.ReadFile(constant.DaemonJsonDir)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
deamonMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(file, &deamonMap)
|
||||
fmt.Println(err)
|
||||
for k, v := range deamonMap {
|
||||
fmt.Println(k, v)
|
||||
}
|
||||
if _, ok := deamonMap["insecure-registries"]; ok {
|
||||
if k, v := deamonMap["insecure-registries"].(string); v {
|
||||
fmt.Println("string ", k)
|
||||
}
|
||||
if k, v := deamonMap["insecure-registries"].([]interface{}); v {
|
||||
fmt.Println("[]string ", k)
|
||||
k = append(k, "172.16.10.111:8085")
|
||||
deamonMap["insecure-registries"] = k
|
||||
}
|
||||
}
|
||||
newss, err := json.Marshal(deamonMap)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(string(newss))
|
||||
if err := ioutil.WriteFile(constant.DaemonJsonDir, newss, 0777); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetwork(t *testing.T) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
options := types.ContainerListOptions{All: true}
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("label", "maintainer")
|
||||
ss, _ := client.ContainerList(context.TODO(), options)
|
||||
fmt.Println(ss)
|
||||
}
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
client, err := docker.NewDockerClient()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
_, err = client.ContainerCreate(context.TODO(), &container.Config{}, &container.HostConfig{}, &network.NetworkingConfig{}, &v1.Platform{}, "test")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
v1 "github.com/1Panel-dev/1Panel/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/middleware"
|
||||
v1 "github.com/1Panel-dev/1Panel/backend/app/api/v1"
|
||||
"github.com/1Panel-dev/1Panel/backend/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@ -20,6 +20,8 @@ func (s *ContainerRouter) InitContainerRouter(Router *gin.RouterGroup) {
|
||||
Use(middleware.OperationRecord())
|
||||
baseApi := v1.ApiGroupApp.BaseApi
|
||||
{
|
||||
baRouter.GET("/exec", baseApi.ContainerExec)
|
||||
|
||||
baRouter.POST("/search", baseApi.SearchContainer)
|
||||
baRouter.POST("/inspect", baseApi.Inspect)
|
||||
baRouter.POST("", baseApi.ContainerCreate)
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/init/db"
|
||||
"github.com/1Panel-dev/1Panel/init/log"
|
||||
"github.com/1Panel-dev/1Panel/init/viper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||
)
|
||||
|
||||
func TestMinio(t *testing.T) {
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/init/db"
|
||||
"github.com/1Panel-dev/1Panel/init/log"
|
||||
"github.com/1Panel-dev/1Panel/init/viper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
)
|
||||
|
||||
|
@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/app/model"
|
||||
"github.com/1Panel-dev/1Panel/constant"
|
||||
"github.com/1Panel-dev/1Panel/global"
|
||||
"github.com/1Panel-dev/1Panel/init/db"
|
||||
"github.com/1Panel-dev/1Panel/init/log"
|
||||
"github.com/1Panel-dev/1Panel/init/viper"
|
||||
"github.com/1Panel-dev/1Panel/backend/app/model"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/db"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/log"
|
||||
"github.com/1Panel-dev/1Panel/backend/init/viper"
|
||||
)
|
||||
|
||||
func TestCronS(t *testing.T) {
|
||||
|
102
backend/utils/terminal/exec.go
Normal file
102
backend/utils/terminal/exec.go
Normal file
@ -0,0 +1,102 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/backend/global"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ExecWsSession struct {
|
||||
conn net.Conn
|
||||
wsConn *websocket.Conn
|
||||
|
||||
writeMutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewExecConn(cols, rows int, wsConn *websocket.Conn, hijacked net.Conn) (*ExecWsSession, error) {
|
||||
_, _ = hijacked.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", cols, rows)))
|
||||
|
||||
return &ExecWsSession{
|
||||
conn: hijacked,
|
||||
wsConn: wsConn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) Start(ctx context.Context, quitChan chan bool) {
|
||||
go sws.handleSlaveEvent(ctx, quitChan)
|
||||
go sws.receiveWsMsg(ctx, quitChan)
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) handleSlaveEvent(ctx context.Context, exitCh chan bool) {
|
||||
defer setQuit(exitCh)
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
for {
|
||||
n, err := sws.conn.Read(buffer)
|
||||
if err != nil && errors.Is(err, net.ErrClosed) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sws.masterWrite(buffer[:n]); err != nil {
|
||||
if errors.Is(err, websocket.ErrCloseSent) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) masterWrite(data []byte) error {
|
||||
sws.writeMutex.Lock()
|
||||
defer sws.writeMutex.Unlock()
|
||||
err := sws.wsConn.WriteMessage(websocket.TextMessage, data)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to write to master")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) receiveWsMsg(ctx context.Context, exitCh chan bool) {
|
||||
wsConn := sws.wsConn
|
||||
defer setQuit(exitCh)
|
||||
for {
|
||||
_, wsData, err := wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("reading webSocket message failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
msgObj := wsMsg{}
|
||||
_ = json.Unmarshal(wsData, &msgObj)
|
||||
switch msgObj.Type {
|
||||
case wsMsgResize:
|
||||
if msgObj.Cols > 0 && msgObj.Rows > 0 {
|
||||
sws.ResizeTerminal(msgObj.Rows, msgObj.Cols)
|
||||
}
|
||||
case wsMsgCmd:
|
||||
decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Cmd)
|
||||
if err != nil {
|
||||
global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err)
|
||||
return
|
||||
}
|
||||
sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes)
|
||||
case wsMsgClose:
|
||||
_, _ = sws.conn.Write([]byte("exit\r"))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) {
|
||||
_, _ = sws.conn.Write(cmdBytes)
|
||||
}
|
||||
|
||||
func (sws *ExecWsSession) ResizeTerminal(width int, height int) {
|
||||
_, _ = sws.conn.Write([]byte(fmt.Sprintf("stty cols %d rows %d && clear \r", width, height)))
|
||||
}
|
@ -37,6 +37,7 @@ func (w *safeBuffer) Reset() {
|
||||
const (
|
||||
wsMsgCmd = "cmd"
|
||||
wsMsgResize = "resize"
|
||||
wsMsgClose = "close"
|
||||
)
|
||||
|
||||
type wsMsg struct {
|
||||
|
@ -109,6 +109,7 @@ export namespace Container {
|
||||
gateway: string;
|
||||
createdAt: string;
|
||||
attachable: string;
|
||||
expand: boolean;
|
||||
}
|
||||
export interface NetworkCreate {
|
||||
name: string;
|
||||
@ -169,6 +170,7 @@ export namespace Container {
|
||||
export interface ComposeInfo {
|
||||
name: string;
|
||||
createdAt: string;
|
||||
createdBy: string;
|
||||
containerNumber: number;
|
||||
configFile: string;
|
||||
workdir: string;
|
||||
|
@ -9,6 +9,7 @@
|
||||
</template>
|
||||
<SubItem :menuList="subItem.children" />
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item v-else-if="subItem.children && subItem.children.length === 1" :index="subItem.children[0].path">
|
||||
<el-icon>
|
||||
<SvgIcon :iconName="(subItem.meta?.icon as string)" :className="'svg-icon'"></SvgIcon>
|
||||
@ -17,7 +18,11 @@
|
||||
<span>{{ $t(subItem.meta?.title as string) }}</span>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
|
||||
<el-menu-item v-else :index="subItem.path">
|
||||
<el-icon v-if="subItem.meta?.icon">
|
||||
<SvgIcon :iconName="(subItem.meta?.icon as string)" :className="'svg-icon'"></SvgIcon>
|
||||
</el-icon>
|
||||
<template #title>
|
||||
<span style="margin-left: 10px">{{ $t(subItem.meta?.title as string) }}</span>
|
||||
</template>
|
||||
|
@ -16,6 +16,7 @@ export default {
|
||||
cancel: 'Cancel',
|
||||
reset: 'Reset',
|
||||
conn: 'Connect',
|
||||
disconn: 'Disconnect',
|
||||
clean: 'Clean',
|
||||
login: 'Login',
|
||||
close: 'Close',
|
||||
@ -169,6 +170,9 @@ export default {
|
||||
lastHour: 'Last Hour',
|
||||
last10Min: 'Last 10 Minutes',
|
||||
|
||||
custom: 'Custom',
|
||||
containerTerminal: 'Container terminal',
|
||||
|
||||
containerCreate: 'Container create',
|
||||
port: 'Port',
|
||||
exposePort: 'Expose port',
|
||||
|
@ -17,6 +17,7 @@ export default {
|
||||
cancel: '取消',
|
||||
reset: '重置',
|
||||
conn: '连接',
|
||||
disconn: '断开',
|
||||
clean: '清空',
|
||||
login: '登录',
|
||||
close: '关闭',
|
||||
@ -167,6 +168,9 @@ export default {
|
||||
lastHour: '最近 1 小时',
|
||||
last10Min: '最近 10 分钟',
|
||||
|
||||
custom: '自定义',
|
||||
containerTerminal: '容器终端',
|
||||
|
||||
containerCreate: '容器创建',
|
||||
port: '端口',
|
||||
exposePort: '暴露端口',
|
||||
|
@ -24,7 +24,6 @@ const containerRouter = {
|
||||
path: 'image',
|
||||
name: 'Image',
|
||||
component: () => import('@/views/container/image/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
@ -34,7 +33,6 @@ const containerRouter = {
|
||||
path: 'network',
|
||||
name: 'Network',
|
||||
component: () => import('@/views/container/network/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
@ -44,7 +42,6 @@ const containerRouter = {
|
||||
path: 'volume',
|
||||
name: 'Volume',
|
||||
component: () => import('@/views/container/volume/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
@ -54,7 +51,6 @@ const containerRouter = {
|
||||
path: 'repo',
|
||||
name: 'Repo',
|
||||
component: () => import('@/views/container/repo/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
@ -64,7 +60,6 @@ const containerRouter = {
|
||||
path: 'compose',
|
||||
name: 'Compose',
|
||||
component: () => import('@/views/container/compose/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
@ -74,7 +69,6 @@ const containerRouter = {
|
||||
path: 'template',
|
||||
name: 'composeTemplate',
|
||||
component: () => import('@/views/container/template/index.vue'),
|
||||
props: true,
|
||||
hidden: true,
|
||||
meta: {
|
||||
activeMenu: '/containers',
|
||||
|
@ -40,15 +40,16 @@
|
||||
<el-link @click="goContainer(row.name)" type="primary">{{ row.name }}</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('container.from')" prop="createdBy" min-width="80" fix />
|
||||
<el-table-column :label="$t('container.containerNumber')" prop="containerNumber" min-width="80" fix />
|
||||
<el-table-column :label="$t('container.containerNumber')" prop="contaienrs" min-width="80" fix>
|
||||
<el-table-column :label="$t('container.container')" prop="contaienrs" min-width="80" fix>
|
||||
<template #default="{ row }">
|
||||
<div v-for="(item, index) in row.containers" :key="index">
|
||||
<div v-if="row.expand || (!row.expand && index < 1)">
|
||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||
<el-tag>{{ item.name }} [{{ item.state }}]</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.expand">
|
||||
<div v-if="!row.expand && row.containers.length > 3">
|
||||
<el-button type="primary" link @click="row.expand = true">
|
||||
{{ $t('commons.button.expand') }}...
|
||||
</el-button>
|
||||
@ -91,6 +92,7 @@ const search = async () => {
|
||||
await searchCompose(params).then((res) => {
|
||||
if (res.data) {
|
||||
data.value = res.data.items;
|
||||
paginationConfig.total = res.data.total;
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -132,7 +134,6 @@ const onOperate = async (operation: string) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
// const buttons = [];
|
||||
|
||||
onMounted(() => {
|
||||
search();
|
||||
|
@ -96,6 +96,9 @@ const rules = reactive({
|
||||
const loadTemplates = async () => {
|
||||
const res = await listComposeTemplate();
|
||||
templateOptions.value = res.data;
|
||||
if (templateOptions.value && templateOptions.value.length !== 0) {
|
||||
form.template = templateOptions.value[0].id;
|
||||
}
|
||||
};
|
||||
|
||||
const acceptParams = (): void => {
|
||||
@ -104,7 +107,6 @@ const acceptParams = (): void => {
|
||||
form.from = 'edit';
|
||||
form.path = '';
|
||||
form.file = '';
|
||||
form.template = 0;
|
||||
loadTemplates();
|
||||
};
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
|
@ -159,6 +159,7 @@
|
||||
</el-dialog>
|
||||
<CreateDialog @search="search" ref="dialogCreateRef" />
|
||||
<MonitorDialog ref="dialogMonitorRef" />
|
||||
<TerminalDialog ref="dialogTerminalRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -166,6 +167,7 @@
|
||||
import ComplexTable from '@/components/complex-table/index.vue';
|
||||
import CreateDialog from '@/views/container/container/create/index.vue';
|
||||
import MonitorDialog from '@/views/container/container/monitor/index.vue';
|
||||
import TerminalDialog from '@/views/container/container/terminal/index.vue';
|
||||
import Submenu from '@/views/container/index.vue';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
@ -259,6 +261,11 @@ const onMonitor = (containerID: string) => {
|
||||
dialogMonitorRef.value!.acceptParams({ containerID: containerID });
|
||||
};
|
||||
|
||||
const dialogTerminalRef = ref();
|
||||
const onTerminal = (containerID: string) => {
|
||||
dialogTerminalRef.value!.acceptParams({ containerID: containerID });
|
||||
};
|
||||
|
||||
const onInspect = async (id: string) => {
|
||||
const res = await inspect({ id: id, type: 'container' });
|
||||
detailInfo.value = JSON.stringify(JSON.parse(res.data), null, 2);
|
||||
@ -375,8 +382,20 @@ const onOperate = async (operation: string) => {
|
||||
};
|
||||
|
||||
const buttons = [
|
||||
{
|
||||
label: i18n.global.t('file.terminal'),
|
||||
disabled: (row: Container.ContainerInfo) => {
|
||||
return row.state !== 'running';
|
||||
},
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onTerminal(row.containerID);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: i18n.global.t('container.monitor'),
|
||||
disabled: (row: Container.ContainerInfo) => {
|
||||
return row.state !== 'running';
|
||||
},
|
||||
click: (row: Container.ContainerInfo) => {
|
||||
onMonitor(row.containerID);
|
||||
},
|
||||
|
207
frontend/src/views/container/container/terminal/index.vue
Normal file
207
frontend/src/views/container/container/terminal/index.vue
Normal file
@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="terminalVisiable"
|
||||
:destroy-on-close="true"
|
||||
@close="onClose"
|
||||
:close-on-click-modal="false"
|
||||
width="70%"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>{{ $t('container.containerTerminal') }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="formRef" :model="form" label-width="80px">
|
||||
<el-form-item label="User" prop="user" :rules="Rules.requiredInput">
|
||||
<el-input style="width: 30%" clearable placeholder="root" v-model="form.user" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.custom')" prop="custom">
|
||||
<el-switch v-model="form.isCustom" @change="form.command = ''" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.isCustom" label="Command" prop="command" :rules="Rules.requiredInput">
|
||||
<el-input style="width: 30%" clearable v-model="form.command" />
|
||||
</el-form-item>
|
||||
<el-form-item v-if="!form.isCustom" label="Command" prop="command" :rules="Rules.requiredSelect">
|
||||
<el-select style="width: 30%" allow-create filterable clearable v-model="form.command">
|
||||
<el-option value="/bin/ash" label="/bin/ash" />
|
||||
<el-option value="/bin/bash" label="/bin/bash" />
|
||||
<el-option value="/bin/sh" label="/bin/sh" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button v-if="!terminalOpen" @click="initTerm(formRef)">{{ $t('commons.button.conn') }}</el-button>
|
||||
<el-button v-else @click="onClose()">{{ $t('commons.button.disconn') }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div :id="'terminal-exec'"></div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="terminalVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { ElForm, FormInstance } from 'element-plus';
|
||||
import { Terminal } from 'xterm';
|
||||
import { AttachAddon } from 'xterm-addon-attach';
|
||||
import { Base64 } from 'js-base64';
|
||||
import 'xterm/css/xterm.css';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
|
||||
const terminalVisiable = ref(false);
|
||||
const terminalOpen = ref(false);
|
||||
const fitAddon = new FitAddon();
|
||||
let terminalSocket = ref(null) as unknown as WebSocket;
|
||||
let term = ref(null) as unknown as Terminal;
|
||||
const loading = ref(true);
|
||||
const runRealTerminal = () => {
|
||||
loading.value = false;
|
||||
};
|
||||
const form = reactive({
|
||||
isCustom: false,
|
||||
command: '',
|
||||
user: '',
|
||||
containerID: '',
|
||||
});
|
||||
type FormInstance = InstanceType<typeof ElForm>;
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
interface DialogProps {
|
||||
containerID: string;
|
||||
}
|
||||
const acceptParams = async (params: DialogProps): Promise<void> => {
|
||||
terminalVisiable.value = true;
|
||||
form.containerID = params.containerID;
|
||||
form.isCustom = false;
|
||||
form.user = 'root';
|
||||
form.command = '/bin/bash';
|
||||
terminalOpen.value = false;
|
||||
window.addEventListener('resize', changeTerminalSize);
|
||||
};
|
||||
|
||||
const onWSReceive = (message: any) => {
|
||||
if (!isJson(message.data)) {
|
||||
return;
|
||||
}
|
||||
const data = JSON.parse(message.data);
|
||||
term.element && term.focus();
|
||||
term.write(data.Data);
|
||||
};
|
||||
|
||||
function isJson(str: string) {
|
||||
try {
|
||||
if (typeof JSON.parse(str) === 'object') {
|
||||
return true;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const errorRealTerminal = (ex: any) => {
|
||||
let message = ex.message;
|
||||
if (!message) message = 'disconnected';
|
||||
term.write(`\x1b[31m${message}\x1b[m\r\n`);
|
||||
};
|
||||
|
||||
const closeRealTerminal = (ev: CloseEvent) => {
|
||||
term.write(ev.reason);
|
||||
};
|
||||
|
||||
const initTerm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
formEl.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
terminalOpen.value = true;
|
||||
let ifm = document.getElementById('terminal-exec') as HTMLInputElement | null;
|
||||
term = new Terminal({
|
||||
lineHeight: 1.2,
|
||||
fontSize: 12,
|
||||
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
|
||||
theme: {
|
||||
background: '#000000',
|
||||
},
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'underline',
|
||||
scrollback: 100,
|
||||
tabStopWidth: 4,
|
||||
});
|
||||
if (ifm) {
|
||||
term.open(ifm);
|
||||
terminalSocket = new WebSocket(
|
||||
`ws://localhost:9999/api/v1/containers/exec?containerid=${form.containerID}&cols=${term.cols}&rows=${term.rows}&user=${form.user}&command=${form.command}`,
|
||||
);
|
||||
terminalSocket.onopen = runRealTerminal;
|
||||
terminalSocket.onmessage = onWSReceive;
|
||||
terminalSocket.onclose = closeRealTerminal;
|
||||
terminalSocket.onerror = errorRealTerminal;
|
||||
term.onData((data: any) => {
|
||||
if (isWsOpen()) {
|
||||
terminalSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'cmd',
|
||||
cmd: Base64.encode(data),
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
term.loadAddon(new AttachAddon(terminalSocket));
|
||||
term.loadAddon(fitAddon);
|
||||
setTimeout(() => {
|
||||
fitAddon.fit();
|
||||
if (isWsOpen()) {
|
||||
terminalSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'resize',
|
||||
cols: term.cols,
|
||||
rows: term.rows,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, 30);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const fitTerm = () => {
|
||||
fitAddon.fit();
|
||||
};
|
||||
|
||||
const isWsOpen = () => {
|
||||
const readyState = terminalSocket && terminalSocket.readyState;
|
||||
if (readyState) {
|
||||
return readyState === 1;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function onClose() {
|
||||
terminalOpen.value = false;
|
||||
window.removeEventListener('resize', changeTerminalSize);
|
||||
if (isWsOpen()) {
|
||||
terminalSocket && terminalSocket.close();
|
||||
term.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function changeTerminalSize() {
|
||||
fitTerm();
|
||||
const { cols, rows } = term;
|
||||
if (isWsOpen()) {
|
||||
terminalSocket.send(
|
||||
JSON.stringify({
|
||||
type: 'resize',
|
||||
cols: cols,
|
||||
rows: rows,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
defineExpose({
|
||||
acceptParams,
|
||||
});
|
||||
</script>
|
@ -54,7 +54,7 @@ const props = withDefaults(defineProps<MenuProps>(), {
|
||||
activeName: 'container',
|
||||
});
|
||||
|
||||
const active = ref();
|
||||
const active = ref('container');
|
||||
|
||||
onMounted(() => {
|
||||
if (props.activeName) {
|
||||
|
@ -28,10 +28,15 @@
|
||||
<el-table-column :label="$t('container.gateway')" min-width="80" prop="gateway" fix />
|
||||
<el-table-column :label="$t('container.tag')" min-width="140" fix>
|
||||
<template #default="{ row }">
|
||||
<div v-for="(item, index) of row.labels" :key="index">
|
||||
<el-tooltip class="item" :content="item" placement="top">
|
||||
<div v-for="(item, index) in row.labels" :key="index">
|
||||
<div v-if="row.expand || (!row.expand && index < 3)">
|
||||
<el-tag>{{ item }}</el-tag>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!row.expand && row.labels.length > 3">
|
||||
<el-button type="primary" link @click="row.expand = true">
|
||||
{{ $t('commons.button.expand') }}...
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
1
go.mod
1
go.mod
@ -148,5 +148,4 @@ require (
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
gotest.tools/v3 v3.3.0 // indirect
|
||||
)
|
||||
|
40
go.sum
40
go.sum
@ -126,12 +126,9 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
<<<<<<< HEAD:go.sum
|
||||
=======
|
||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||
>>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@ -147,12 +144,10 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
<<<<<<< HEAD:go.sum
|
||||
github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q=
|
||||
github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0=
|
||||
=======
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/compose-spec/compose-go v1.6.0 h1:7Ol/UULMUtbPmB0EYrETASRoum821JpOh/XaEf+hN+Q=
|
||||
github.com/compose-spec/compose-go v1.6.0/go.mod h1:os+Ulh2jlZxY1XT1hbciERadjSUU/BtZ6+gcN7vD7J0=
|
||||
github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE=
|
||||
github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU=
|
||||
github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU=
|
||||
@ -245,7 +240,6 @@ github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/
|
||||
github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4=
|
||||
github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
>>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
@ -286,17 +280,14 @@ github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
<<<<<<< HEAD:go.sum
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb h1:oCCuuU3kMO3sjZH/p7LamvQNW9SWoT4yQuMGcdSxGAE=
|
||||
github.com/distribution/distribution/v3 v3.0.0-20220725133111-4bf3547399eb/go.mod h1:28YO/VJk9/64+sTGNuYaBjWxrXTPrj0C0XmgTIOjxX4=
|
||||
=======
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
>>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
@ -556,16 +547,13 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
<<<<<<< HEAD:go.sum
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
=======
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
>>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
@ -665,13 +653,10 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
<<<<<<< HEAD:go.sum
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
=======
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
>>>>>>> 94a2b8a (feat: 实现容器网络功能):backend/go.sum
|
||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@ -952,12 +937,6 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||
@ -966,9 +945,13 @@ github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17
|
||||
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
|
||||
github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f h1:C8De+7emQKojPBC+mXA0fr39XN5mKjRm9IUzdxI4whI=
|
||||
github.com/xlzd/gotp v0.0.0-20220817083547-a63b9d03d72f/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg=
|
||||
@ -1487,7 +1470,6 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
|
||||
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
Loading…
Reference in New Issue
Block a user