mirror of https://github.com/1Panel-dev/1Panel
appstorecrontabdatabasedockerdocker-composedocker-containerdocker-imagedocker-uifilemanagerlamplnmppanel
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1364 lines
40 KiB
1364 lines
40 KiB
package service |
|
|
|
import ( |
|
"bufio" |
|
"context" |
|
"encoding/base64" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"net/url" |
|
"os" |
|
"os/exec" |
|
"path/filepath" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"syscall" |
|
"time" |
|
"unicode/utf8" |
|
|
|
"github.com/gin-gonic/gin" |
|
|
|
"github.com/pkg/errors" |
|
|
|
"github.com/1Panel-dev/1Panel/backend/app/dto" |
|
"github.com/1Panel-dev/1Panel/backend/buserr" |
|
"github.com/1Panel-dev/1Panel/backend/constant" |
|
"github.com/1Panel-dev/1Panel/backend/global" |
|
"github.com/1Panel-dev/1Panel/backend/utils/cmd" |
|
"github.com/1Panel-dev/1Panel/backend/utils/common" |
|
"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/image" |
|
"github.com/docker/docker/api/types/mount" |
|
"github.com/docker/docker/api/types/network" |
|
"github.com/docker/docker/api/types/registry" |
|
"github.com/docker/docker/client" |
|
"github.com/docker/go-connections/nat" |
|
"github.com/gorilla/websocket" |
|
v1 "github.com/opencontainers/image-spec/specs-go/v1" |
|
"github.com/shirou/gopsutil/v3/cpu" |
|
"github.com/shirou/gopsutil/v3/mem" |
|
) |
|
|
|
type ContainerService struct{} |
|
|
|
type IContainerService interface { |
|
Page(req dto.PageContainer) (int64, interface{}, error) |
|
List() ([]string, error) |
|
PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) |
|
ListNetwork() ([]dto.Options, error) |
|
PageVolume(req dto.SearchWithPage) (int64, interface{}, error) |
|
ListVolume() ([]dto.Options, error) |
|
PageCompose(req dto.SearchWithPage) (int64, interface{}, error) |
|
CreateCompose(req dto.ComposeCreate) (string, error) |
|
ComposeOperation(req dto.ComposeOperation) error |
|
ContainerCreate(req dto.ContainerOperate) error |
|
ContainerUpdate(req dto.ContainerOperate) error |
|
ContainerUpgrade(req dto.ContainerUpgrade) error |
|
ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) |
|
ContainerListStats() ([]dto.ContainerListStats, error) |
|
LoadResourceLimit() (*dto.ResourceLimit, error) |
|
ContainerRename(req dto.ContainerRename) error |
|
ContainerCommit(req dto.ContainerCommit) error |
|
ContainerLogClean(req dto.OperationWithName) error |
|
ContainerOperation(req dto.ContainerOperation) error |
|
ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error |
|
DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error |
|
ContainerStats(id string) (*dto.ContainerStats, error) |
|
Inspect(req dto.InspectReq) (string, error) |
|
DeleteNetwork(req dto.BatchDelete) error |
|
CreateNetwork(req dto.NetworkCreate) error |
|
DeleteVolume(req dto.BatchDelete) error |
|
CreateVolume(req dto.VolumeCreate) error |
|
TestCompose(req dto.ComposeCreate) (bool, error) |
|
ComposeUpdate(req dto.ComposeUpdate) error |
|
Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) |
|
|
|
LoadContainerLogs(req dto.OperationWithNameAndType) string |
|
} |
|
|
|
func NewIContainerService() IContainerService { |
|
return &ContainerService{} |
|
} |
|
|
|
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { |
|
var ( |
|
records []types.Container |
|
list []types.Container |
|
) |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
defer client.Close() |
|
options := container.ListOptions{ |
|
All: true, |
|
} |
|
if len(req.Filters) != 0 { |
|
options.Filters = filters.NewArgs() |
|
options.Filters.Add("label", req.Filters) |
|
} |
|
containers, err := client.ContainerList(context.Background(), options) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
if req.ExcludeAppStore { |
|
for _, item := range containers { |
|
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { |
|
continue |
|
} |
|
list = append(list, item) |
|
} |
|
} else { |
|
list = containers |
|
} |
|
|
|
if len(req.Name) != 0 { |
|
length, count := len(list), 0 |
|
for count < length { |
|
if !strings.Contains(list[count].Names[0][1:], req.Name) { |
|
list = append(list[:count], list[(count+1):]...) |
|
length-- |
|
} else { |
|
count++ |
|
} |
|
} |
|
} |
|
if req.State != "all" { |
|
length, count := len(list), 0 |
|
for count < length { |
|
if list[count].State != req.State { |
|
list = append(list[:count], list[(count+1):]...) |
|
length-- |
|
} else { |
|
count++ |
|
} |
|
} |
|
} |
|
switch req.OrderBy { |
|
case "name": |
|
sort.Slice(list, func(i, j int) bool { |
|
if req.Order == constant.OrderAsc { |
|
return list[i].Names[0][1:] < list[j].Names[0][1:] |
|
} |
|
return list[i].Names[0][1:] > list[j].Names[0][1:] |
|
}) |
|
case "state": |
|
sort.Slice(list, func(i, j int) bool { |
|
if req.Order == constant.OrderAsc { |
|
return list[i].State < list[j].State |
|
} |
|
return list[i].State > list[j].State |
|
}) |
|
default: |
|
sort.Slice(list, func(i, j int) bool { |
|
if req.Order == constant.OrderAsc { |
|
return list[i].Created < list[j].Created |
|
} |
|
return list[i].Created > list[j].Created |
|
}) |
|
} |
|
|
|
total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize |
|
if start > total { |
|
records = make([]types.Container, 0) |
|
} else { |
|
if end >= total { |
|
end = total |
|
} |
|
records = list[start:end] |
|
} |
|
|
|
backDatas := make([]dto.ContainerInfo, len(records)) |
|
for i := 0; i < len(records); i++ { |
|
item := records[i] |
|
IsFromCompose := false |
|
if _, ok := item.Labels[composeProjectLabel]; ok { |
|
IsFromCompose = true |
|
} |
|
IsFromApp := false |
|
if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { |
|
IsFromApp = true |
|
} |
|
|
|
exposePorts := transPortToStr(records[i].Ports) |
|
info := dto.ContainerInfo{ |
|
ContainerID: item.ID, |
|
CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout), |
|
Name: item.Names[0][1:], |
|
ImageId: strings.Split(item.ImageID, ":")[1], |
|
ImageName: item.Image, |
|
State: item.State, |
|
RunTime: item.Status, |
|
Ports: exposePorts, |
|
IsFromApp: IsFromApp, |
|
IsFromCompose: IsFromCompose, |
|
} |
|
install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(info.Name)) |
|
if install.ID > 0 { |
|
info.AppInstallName = install.Name |
|
info.AppName = install.App.Name |
|
websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(install.ID)) |
|
for _, website := range websites { |
|
info.Websites = append(info.Websites, website.PrimaryDomain) |
|
} |
|
} |
|
backDatas[i] = info |
|
if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 { |
|
networks := make([]string, 0, len(item.NetworkSettings.Networks)) |
|
for key := range item.NetworkSettings.Networks { |
|
networks = append(networks, item.NetworkSettings.Networks[key].IPAddress) |
|
} |
|
sort.Strings(networks) |
|
backDatas[i].Network = networks |
|
} |
|
} |
|
|
|
return int64(total), backDatas, nil |
|
} |
|
|
|
func (u *ContainerService) List() ([]string, error) { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer client.Close() |
|
containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
var datas []string |
|
for _, container := range containers { |
|
for _, name := range container.Names { |
|
if len(name) != 0 { |
|
datas = append(datas, strings.TrimPrefix(name, "/")) |
|
} |
|
} |
|
} |
|
|
|
return datas, nil |
|
} |
|
|
|
func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer client.Close() |
|
list, err := client.ContainerList(context.Background(), container.ListOptions{All: true}) |
|
if err != nil { |
|
return nil, err |
|
} |
|
var datas []dto.ContainerListStats |
|
var wg sync.WaitGroup |
|
wg.Add(len(list)) |
|
for i := 0; i < len(list); i++ { |
|
go func(item types.Container) { |
|
datas = append(datas, loadCpuAndMem(client, item.ID)) |
|
wg.Done() |
|
}(list[i]) |
|
} |
|
wg.Wait() |
|
return datas, nil |
|
} |
|
|
|
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return "", err |
|
} |
|
defer client.Close() |
|
var inspectInfo interface{} |
|
switch req.Type { |
|
case "container": |
|
inspectInfo, err = client.ContainerInspect(context.Background(), req.ID) |
|
case "image": |
|
inspectInfo, _, err = client.ImageInspectWithRaw(context.Background(), req.ID) |
|
case "network": |
|
inspectInfo, err = client.NetworkInspect(context.TODO(), req.ID, types.NetworkInspectOptions{}) |
|
case "volume": |
|
inspectInfo, err = client.VolumeInspect(context.TODO(), req.ID) |
|
} |
|
if err != nil { |
|
return "", err |
|
} |
|
bytes, err := json.Marshal(inspectInfo) |
|
if err != nil { |
|
return "", err |
|
} |
|
return string(bytes), nil |
|
} |
|
|
|
func (u *ContainerService) Prune(req dto.ContainerPrune) (dto.ContainerPruneReport, error) { |
|
report := dto.ContainerPruneReport{} |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return report, err |
|
} |
|
defer client.Close() |
|
pruneFilters := filters.NewArgs() |
|
if req.WithTagAll { |
|
pruneFilters.Add("dangling", "false") |
|
if req.PruneType != "image" { |
|
pruneFilters.Add("until", "24h") |
|
} |
|
} |
|
switch req.PruneType { |
|
case "container": |
|
rep, err := client.ContainersPrune(context.Background(), pruneFilters) |
|
if err != nil { |
|
return report, err |
|
} |
|
report.DeletedNumber = len(rep.ContainersDeleted) |
|
report.SpaceReclaimed = int(rep.SpaceReclaimed) |
|
case "image": |
|
rep, err := client.ImagesPrune(context.Background(), pruneFilters) |
|
if err != nil { |
|
return report, err |
|
} |
|
report.DeletedNumber = len(rep.ImagesDeleted) |
|
report.SpaceReclaimed = int(rep.SpaceReclaimed) |
|
case "network": |
|
rep, err := client.NetworksPrune(context.Background(), pruneFilters) |
|
if err != nil { |
|
return report, err |
|
} |
|
report.DeletedNumber = len(rep.NetworksDeleted) |
|
case "volume": |
|
versions, err := client.ServerVersion(context.Background()) |
|
if err != nil { |
|
return report, err |
|
} |
|
if common.ComparePanelVersion(versions.APIVersion, "1.42") { |
|
pruneFilters.Add("all", "true") |
|
} |
|
rep, err := client.VolumesPrune(context.Background(), pruneFilters) |
|
if err != nil { |
|
return report, err |
|
} |
|
report.DeletedNumber = len(rep.VolumesDeleted) |
|
report.SpaceReclaimed = int(rep.SpaceReclaimed) |
|
case "buildcache": |
|
opts := types.BuildCachePruneOptions{} |
|
opts.All = true |
|
rep, err := client.BuildCachePrune(context.Background(), opts) |
|
if err != nil { |
|
return report, err |
|
} |
|
report.DeletedNumber = len(rep.CachesDeleted) |
|
report.SpaceReclaimed = int(rep.SpaceReclaimed) |
|
} |
|
return report, nil |
|
} |
|
|
|
func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) { |
|
cpuCounts, err := cpu.Counts(true) |
|
if err != nil { |
|
return nil, fmt.Errorf("load cpu limit failed, err: %v", err) |
|
} |
|
memoryInfo, err := mem.VirtualMemory() |
|
if err != nil { |
|
return nil, fmt.Errorf("load memory limit failed, err: %v", err) |
|
} |
|
|
|
data := dto.ResourceLimit{ |
|
CPU: cpuCounts, |
|
Memory: memoryInfo.Total, |
|
} |
|
return &data, nil |
|
} |
|
|
|
func (u *ContainerService) ContainerCreate(req dto.ContainerOperate) error { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
ctx := context.Background() |
|
newContainer, _ := client.ContainerInspect(ctx, req.Name) |
|
if newContainer.ContainerJSONBase != nil { |
|
return buserr.New(constant.ErrContainerName) |
|
} |
|
|
|
if !checkImageExist(client, req.Image) || req.ForcePull { |
|
if err := pullImages(ctx, client, req.Image); err != nil { |
|
if !req.ForcePull { |
|
return err |
|
} |
|
global.LOG.Errorf("force pull image %s failed, err: %v", req.Image, err) |
|
} |
|
} |
|
imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image) |
|
if err != nil { |
|
return err |
|
} |
|
if len(req.Entrypoint) == 0 { |
|
req.Entrypoint = imageInfo.Config.Entrypoint |
|
} |
|
if len(req.Cmd) == 0 { |
|
req.Cmd = imageInfo.Config.Cmd |
|
} |
|
config, hostConf, networkConf, err := loadConfigInfo(true, req, nil) |
|
if err != nil { |
|
return err |
|
} |
|
global.LOG.Infof("new container info %s has been made, now start to create", req.Name) |
|
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) |
|
if err != nil { |
|
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true}) |
|
return err |
|
} |
|
global.LOG.Infof("create container %s successful! now check if the container is started and delete the container information if it is not.", req.Name) |
|
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil { |
|
_ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true}) |
|
return fmt.Errorf("create successful but start failed, err: %v", err) |
|
} |
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer client.Close() |
|
ctx := context.Background() |
|
oldContainer, err := client.ContainerInspect(ctx, req.Name) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var data dto.ContainerOperate |
|
data.ContainerID = oldContainer.ID |
|
data.Name = strings.ReplaceAll(oldContainer.Name, "/", "") |
|
data.Image = oldContainer.Config.Image |
|
if oldContainer.NetworkSettings != nil { |
|
for network := range oldContainer.NetworkSettings.Networks { |
|
data.Network = network |
|
break |
|
} |
|
} |
|
|
|
exposePorts, _ := loadPortByInspect(oldContainer.ID, client) |
|
data.ExposedPorts = loadContainerPortForInfo(exposePorts) |
|
networkSettings := oldContainer.NetworkSettings |
|
bridgeNetworkSettings := networkSettings.Networks[data.Network] |
|
if bridgeNetworkSettings.IPAMConfig != nil { |
|
ipv4Address := bridgeNetworkSettings.IPAMConfig.IPv4Address |
|
data.Ipv4 = ipv4Address |
|
ipv6Address := bridgeNetworkSettings.IPAMConfig.IPv6Address |
|
data.Ipv6 = ipv6Address |
|
} else { |
|
data.Ipv4 = bridgeNetworkSettings.IPAddress |
|
} |
|
|
|
data.Cmd = oldContainer.Config.Cmd |
|
data.OpenStdin = oldContainer.Config.OpenStdin |
|
data.Tty = oldContainer.Config.Tty |
|
data.Entrypoint = oldContainer.Config.Entrypoint |
|
data.Env = oldContainer.Config.Env |
|
data.CPUShares = oldContainer.HostConfig.CPUShares |
|
for key, val := range oldContainer.Config.Labels { |
|
data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val)) |
|
} |
|
|
|
data.AutoRemove = oldContainer.HostConfig.AutoRemove |
|
data.Privileged = oldContainer.HostConfig.Privileged |
|
data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts |
|
data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name) |
|
if oldContainer.HostConfig.NanoCPUs != 0 { |
|
data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000 |
|
} |
|
if oldContainer.HostConfig.Memory != 0 { |
|
data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024 |
|
} |
|
data.Volumes = loadVolumeBinds(oldContainer.Mounts) |
|
|
|
return &data, nil |
|
} |
|
|
|
func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
ctx := context.Background() |
|
newContainer, _ := client.ContainerInspect(ctx, req.Name) |
|
if newContainer.ContainerJSONBase != nil && newContainer.ID != req.ContainerID { |
|
return buserr.New(constant.ErrContainerName) |
|
} |
|
|
|
oldContainer, err := client.ContainerInspect(ctx, req.ContainerID) |
|
if err != nil { |
|
return err |
|
} |
|
if !checkImageExist(client, req.Image) || req.ForcePull { |
|
if err := pullImages(ctx, client, req.Image); err != nil { |
|
if !req.ForcePull { |
|
return err |
|
} |
|
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err) |
|
} |
|
} |
|
|
|
if err := client.ContainerRemove(ctx, req.ContainerID, container.RemoveOptions{Force: true}); err != nil { |
|
return err |
|
} |
|
|
|
config, hostConf, networkConf, err := loadConfigInfo(false, req, &oldContainer) |
|
if err != nil { |
|
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) |
|
return err |
|
} |
|
|
|
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name) |
|
con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) |
|
if err != nil { |
|
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) |
|
return fmt.Errorf("update container failed, err: %v", err) |
|
} |
|
global.LOG.Infof("update container %s successful! now check if the container is started.", req.Name) |
|
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil { |
|
return fmt.Errorf("update successful but start failed, err: %v", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
ctx := context.Background() |
|
oldContainer, err := client.ContainerInspect(ctx, req.Name) |
|
if err != nil { |
|
return err |
|
} |
|
if !checkImageExist(client, req.Image) || req.ForcePull { |
|
if err := pullImages(ctx, client, req.Image); err != nil { |
|
if !req.ForcePull { |
|
return err |
|
} |
|
return fmt.Errorf("pull image %s failed, err: %v", req.Image, err) |
|
} |
|
} |
|
config := oldContainer.Config |
|
config.Image = req.Image |
|
hostConf := oldContainer.HostConfig |
|
var networkConf network.NetworkingConfig |
|
if oldContainer.NetworkSettings != nil { |
|
for networkKey := range oldContainer.NetworkSettings.Networks { |
|
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} |
|
break |
|
} |
|
} |
|
if err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}); err != nil { |
|
return err |
|
} |
|
|
|
global.LOG.Infof("new container info %s has been update, now start to recreate", req.Name) |
|
con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, req.Name) |
|
if err != nil { |
|
reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) |
|
return fmt.Errorf("upgrade container failed, err: %v", err) |
|
} |
|
global.LOG.Infof("upgrade container %s successful! now check if the container is started.", req.Name) |
|
if err := client.ContainerStart(ctx, con.ID, container.StartOptions{}); err != nil { |
|
return fmt.Errorf("upgrade successful but start failed, err: %v", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerRename(req dto.ContainerRename) error { |
|
ctx := context.Background() |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
|
|
newContainer, _ := client.ContainerInspect(ctx, req.NewName) |
|
if newContainer.ContainerJSONBase != nil { |
|
return buserr.New(constant.ErrContainerName) |
|
} |
|
return client.ContainerRename(ctx, req.Name, req.NewName) |
|
} |
|
|
|
func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error { |
|
ctx := context.Background() |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
options := container.CommitOptions{ |
|
Reference: req.NewImageName, |
|
Comment: req.Comment, |
|
Author: req.Author, |
|
Changes: nil, |
|
Pause: req.Pause, |
|
Config: nil, |
|
} |
|
_, err = client.ContainerCommit(ctx, req.ContainerId, options) |
|
if err != nil { |
|
return fmt.Errorf("failed to commit container, err: %v", err) |
|
} |
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error { |
|
var err error |
|
ctx := context.Background() |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
for _, item := range req.Names { |
|
global.LOG.Infof("start container %s operation %s", item, req.Operation) |
|
switch req.Operation { |
|
case constant.ContainerOpStart: |
|
err = client.ContainerStart(ctx, item, container.StartOptions{}) |
|
case constant.ContainerOpStop: |
|
err = client.ContainerStop(ctx, item, container.StopOptions{}) |
|
case constant.ContainerOpRestart: |
|
err = client.ContainerRestart(ctx, item, container.StopOptions{}) |
|
case constant.ContainerOpKill: |
|
err = client.ContainerKill(ctx, item, "SIGKILL") |
|
case constant.ContainerOpPause: |
|
err = client.ContainerPause(ctx, item) |
|
case constant.ContainerOpUnpause: |
|
err = client.ContainerUnpause(ctx, item) |
|
case constant.ContainerOpRemove: |
|
err = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true}) |
|
} |
|
} |
|
return err |
|
} |
|
|
|
func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return err |
|
} |
|
defer client.Close() |
|
ctx := context.Background() |
|
containerItem, err := client.ContainerInspect(ctx, req.Name) |
|
if err != nil { |
|
return err |
|
} |
|
if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil { |
|
return err |
|
} |
|
file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, 0666) |
|
if err != nil { |
|
return err |
|
} |
|
defer file.Close() |
|
if err = file.Truncate(0); err != nil { |
|
return err |
|
} |
|
_, _ = file.Seek(0, 0) |
|
|
|
files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath)) |
|
for _, file := range files { |
|
_ = os.Remove(file) |
|
} |
|
|
|
if err := client.ContainerStart(ctx, containerItem.ID, container.StartOptions{}); err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerLogs(wsConn *websocket.Conn, containerType, container, since, tail string, follow bool) error { |
|
defer func() { wsConn.Close() }() |
|
if cmd.CheckIllegal(container, since, tail) { |
|
return buserr.New(constant.ErrCmdIllegal) |
|
} |
|
commandName := "docker" |
|
commandArg := []string{"logs", container} |
|
if containerType == "compose" { |
|
commandName = "docker-compose" |
|
commandArg = []string{"-f", container, "logs"} |
|
} |
|
if tail != "0" { |
|
commandArg = append(commandArg, "--tail") |
|
commandArg = append(commandArg, tail) |
|
} |
|
if since != "all" { |
|
commandArg = append(commandArg, "--since") |
|
commandArg = append(commandArg, since) |
|
} |
|
if follow { |
|
commandArg = append(commandArg, "-f") |
|
} |
|
if !follow { |
|
cmd := exec.Command(commandName, commandArg...) |
|
cmd.Stderr = cmd.Stdout |
|
stdout, _ := cmd.CombinedOutput() |
|
if !utf8.Valid(stdout) { |
|
return errors.New("invalid utf8") |
|
} |
|
if err := wsConn.WriteMessage(websocket.TextMessage, stdout); err != nil { |
|
global.LOG.Errorf("send message with log to ws failed, err: %v", err) |
|
} |
|
return nil |
|
} |
|
|
|
cmd := exec.Command(commandName, commandArg...) |
|
stdout, err := cmd.StdoutPipe() |
|
if err != nil { |
|
_ = cmd.Process.Signal(syscall.SIGTERM) |
|
return err |
|
} |
|
cmd.Stderr = cmd.Stdout |
|
if err := cmd.Start(); err != nil { |
|
_ = cmd.Process.Signal(syscall.SIGTERM) |
|
return err |
|
} |
|
exitCh := make(chan struct{}) |
|
go func() { |
|
_, wsData, _ := wsConn.ReadMessage() |
|
if string(wsData) == "close conn" { |
|
_ = cmd.Process.Signal(syscall.SIGTERM) |
|
exitCh <- struct{}{} |
|
} |
|
}() |
|
|
|
go func() { |
|
buffer := make([]byte, 1024) |
|
for { |
|
select { |
|
case <-exitCh: |
|
return |
|
default: |
|
n, err := stdout.Read(buffer) |
|
if err != nil { |
|
if err == io.EOF { |
|
return |
|
} |
|
global.LOG.Errorf("read bytes from log failed, err: %v", err) |
|
return |
|
} |
|
if !utf8.Valid(buffer[:n]) { |
|
continue |
|
} |
|
if err = wsConn.WriteMessage(websocket.TextMessage, buffer[:n]); err != nil { |
|
global.LOG.Errorf("send message with log to ws failed, err: %v", err) |
|
return |
|
} |
|
} |
|
} |
|
}() |
|
_ = cmd.Wait() |
|
return nil |
|
} |
|
|
|
func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error { |
|
if cmd.CheckIllegal(container, since, tail) { |
|
return buserr.New(constant.ErrCmdIllegal) |
|
} |
|
commandName := "docker" |
|
commandArg := []string{"logs", container} |
|
if containerType == "compose" { |
|
commandName = "docker-compose" |
|
commandArg = []string{"-f", container, "logs"} |
|
} |
|
if tail != "0" { |
|
commandArg = append(commandArg, "--tail") |
|
commandArg = append(commandArg, tail) |
|
} |
|
if since != "all" { |
|
commandArg = append(commandArg, "--since") |
|
commandArg = append(commandArg, since) |
|
} |
|
|
|
cmd := exec.Command(commandName, commandArg...) |
|
stdout, err := cmd.StdoutPipe() |
|
if err != nil { |
|
_ = cmd.Process.Signal(syscall.SIGTERM) |
|
return err |
|
} |
|
cmd.Stderr = cmd.Stdout |
|
if err := cmd.Start(); err != nil { |
|
_ = cmd.Process.Signal(syscall.SIGTERM) |
|
return err |
|
} |
|
|
|
tempFile, err := os.CreateTemp("", "cmd_output_*.txt") |
|
if err != nil { |
|
return err |
|
} |
|
defer tempFile.Close() |
|
defer func() { |
|
if err := os.Remove(tempFile.Name()); err != nil { |
|
global.LOG.Errorf("os.Remove() failed: %v", err) |
|
} |
|
}() |
|
errCh := make(chan error) |
|
go func() { |
|
scanner := bufio.NewScanner(stdout) |
|
for scanner.Scan() { |
|
line := scanner.Text() |
|
if _, err := tempFile.WriteString(line + "\n"); err != nil { |
|
errCh <- err |
|
return |
|
} |
|
} |
|
if err := scanner.Err(); err != nil { |
|
errCh <- err |
|
return |
|
} |
|
errCh <- nil |
|
}() |
|
select { |
|
case err := <-errCh: |
|
if err != nil { |
|
global.LOG.Errorf("Error: %v", err) |
|
} |
|
case <-time.After(3 * time.Second): |
|
global.LOG.Errorf("Timeout reached") |
|
} |
|
info, _ := tempFile.Stat() |
|
|
|
c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) |
|
c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) |
|
http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), tempFile) |
|
return nil |
|
} |
|
|
|
func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) { |
|
client, err := docker.NewDockerClient() |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer client.Close() |
|
res, err := client.ContainerStats(context.TODO(), id, false) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer res.Body.Close() |
|
|
|
body, err := io.ReadAll(res.Body) |
|
if err != nil { |
|
res.Body.Close() |
|
return nil, err |
|
} |
|
res.Body.Close() |
|
var stats *types.StatsJSON |
|
if err := json.Unmarshal(body, &stats); err != nil { |
|
return nil, err |
|
} |
|
var data dto.ContainerStats |
|
data.CPUPercent = calculateCPUPercentUnix(stats) |
|
data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) |
|
data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 |
|
if cache, ok := stats.MemoryStats.Stats["cache"]; ok { |
|
data.Cache = float64(cache) / 1024 / 1024 |
|
} |
|
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks) |
|
data.ShotTime = stats.Read |
|
return &data, nil |
|
} |
|
|
|
func (u *ContainerService) LoadContainerLogs(req dto.OperationWithNameAndType) string { |
|
filePath := "" |
|
if req.Type == "compose-detail" { |
|
cli, err := docker.NewDockerClient() |
|
if err != nil { |
|
return "" |
|
} |
|
defer cli.Close() |
|
options := container.ListOptions{All: true} |
|
options.Filters = filters.NewArgs() |
|
options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.Name)) |
|
containers, err := cli.ContainerList(context.Background(), options) |
|
if err != nil { |
|
return "" |
|
} |
|
for _, container := range containers { |
|
config := container.Labels[composeConfigLabel] |
|
workdir := container.Labels[composeWorkdirLabel] |
|
if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { |
|
filePath = config |
|
break |
|
} else { |
|
filePath = workdir |
|
break |
|
} |
|
} |
|
if len(containers) == 0 { |
|
composeItem, _ := composeRepo.GetRecord(commonRepo.WithByName(req.Name)) |
|
filePath = composeItem.Path |
|
} |
|
} |
|
if _, err := os.Stat(filePath); err != nil { |
|
return "" |
|
} |
|
content, err := os.ReadFile(filePath) |
|
if err != nil { |
|
return "" |
|
} |
|
return string(content) |
|
} |
|
|
|
func stringsToMap(list []string) map[string]string { |
|
var labelMap = make(map[string]string) |
|
for _, label := range list { |
|
if strings.Contains(label, "=") { |
|
sps := strings.SplitN(label, "=", 2) |
|
labelMap[sps[0]] = sps[1] |
|
} |
|
} |
|
return labelMap |
|
} |
|
|
|
func calculateCPUPercentUnix(stats *types.StatsJSON) float64 { |
|
cpuPercent := 0.0 |
|
cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage) |
|
systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage) |
|
|
|
if systemDelta > 0.0 && cpuDelta > 0.0 { |
|
cpuPercent = (cpuDelta / systemDelta) * 100.0 |
|
if len(stats.CPUStats.CPUUsage.PercpuUsage) != 0 { |
|
cpuPercent = cpuPercent * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) |
|
} |
|
} |
|
return cpuPercent |
|
} |
|
func calculateMemPercentUnix(memStats types.MemoryStats) float64 { |
|
memPercent := 0.0 |
|
memUsage := float64(memStats.Usage) |
|
memLimit := float64(memStats.Limit) |
|
if memUsage > 0.0 && memLimit > 0.0 { |
|
memPercent = (memUsage / memLimit) * 100.0 |
|
} |
|
return memPercent |
|
} |
|
func calculateBlockIO(blkio types.BlkioStats) (blkRead float64, blkWrite float64) { |
|
for _, bioEntry := range blkio.IoServiceBytesRecursive { |
|
switch strings.ToLower(bioEntry.Op) { |
|
case "read": |
|
blkRead = (blkRead + float64(bioEntry.Value)) / 1024 / 1024 |
|
case "write": |
|
blkWrite = (blkWrite + float64(bioEntry.Value)) / 1024 / 1024 |
|
} |
|
} |
|
return |
|
} |
|
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) { |
|
var rx, tx float64 |
|
|
|
for _, v := range network { |
|
rx += float64(v.RxBytes) / 1024 |
|
tx += float64(v.TxBytes) / 1024 |
|
} |
|
return rx, tx |
|
} |
|
|
|
func checkImageExist(client *client.Client, imageItem string) bool { |
|
images, err := client.ImageList(context.Background(), image.ListOptions{}) |
|
if err != nil { |
|
return false |
|
} |
|
|
|
for _, img := range images { |
|
for _, tag := range img.RepoTags { |
|
if tag == imageItem || tag == imageItem+":latest" { |
|
return true |
|
} |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func pullImages(ctx context.Context, client *client.Client, imageName string) error { |
|
options := image.PullOptions{} |
|
repos, _ := imageRepoRepo.List() |
|
if len(repos) != 0 { |
|
for _, repo := range repos { |
|
if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth { |
|
authConfig := registry.AuthConfig{ |
|
Username: repo.Username, |
|
Password: repo.Password, |
|
} |
|
encodedJSON, err := json.Marshal(authConfig) |
|
if err != nil { |
|
return err |
|
} |
|
authStr := base64.URLEncoding.EncodeToString(encodedJSON) |
|
options.RegistryAuth = authStr |
|
} |
|
} |
|
} else { |
|
hasAuth, authStr := loadAuthInfo(imageName) |
|
if hasAuth { |
|
options.RegistryAuth = authStr |
|
} |
|
} |
|
out, err := client.ImagePull(ctx, imageName, options) |
|
if err != nil { |
|
return err |
|
} |
|
defer out.Close() |
|
_, err = io.Copy(io.Discard, out) |
|
if err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
func loadCpuAndMem(client *client.Client, container string) dto.ContainerListStats { |
|
data := dto.ContainerListStats{ |
|
ContainerID: container, |
|
} |
|
res, err := client.ContainerStats(context.Background(), container, false) |
|
if err != nil { |
|
return data |
|
} |
|
|
|
defer res.Body.Close() |
|
body, err := io.ReadAll(res.Body) |
|
if err != nil { |
|
return data |
|
} |
|
var stats *types.StatsJSON |
|
if err := json.Unmarshal(body, &stats); err != nil { |
|
return data |
|
} |
|
|
|
data.CPUTotalUsage = stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage |
|
data.SystemUsage = stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage |
|
data.CPUPercent = calculateCPUPercentUnix(stats) |
|
data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage) |
|
|
|
data.MemoryCache = stats.MemoryStats.Stats["cache"] |
|
data.MemoryUsage = stats.MemoryStats.Usage |
|
data.MemoryLimit = stats.MemoryStats.Limit |
|
|
|
data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats) |
|
return data |
|
} |
|
|
|
func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) { |
|
portMap := make(nat.PortMap) |
|
if len(ports) == 0 { |
|
return portMap, nil |
|
} |
|
for _, port := range ports { |
|
if strings.Contains(port.ContainerPort, "-") { |
|
if !strings.Contains(port.HostPort, "-") { |
|
return portMap, buserr.New(constant.ErrPortRules) |
|
} |
|
hostStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0]) |
|
hostEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1]) |
|
containerStart, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[0]) |
|
containerEnd, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[1]) |
|
if (hostEnd-hostStart) <= 0 || (containerEnd-containerStart) <= 0 { |
|
return portMap, buserr.New(constant.ErrPortRules) |
|
} |
|
if (containerEnd - containerStart) != (hostEnd - hostStart) { |
|
return portMap, buserr.New(constant.ErrPortRules) |
|
} |
|
for i := 0; i <= hostEnd-hostStart; i++ { |
|
bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP} |
|
portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem} |
|
} |
|
for i := hostStart; i <= hostEnd; i++ { |
|
if common.ScanPort(i) { |
|
return portMap, buserr.WithDetail(constant.ErrPortInUsed, i, nil) |
|
} |
|
} |
|
} else { |
|
portItem := 0 |
|
if strings.Contains(port.HostPort, "-") { |
|
portItem, _ = strconv.Atoi(strings.Split(port.HostPort, "-")[0]) |
|
} else { |
|
portItem, _ = strconv.Atoi(port.HostPort) |
|
} |
|
if common.ScanPort(portItem) { |
|
return portMap, buserr.WithDetail(constant.ErrPortInUsed, portItem, nil) |
|
} |
|
bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP} |
|
portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem} |
|
} |
|
} |
|
return portMap, nil |
|
} |
|
|
|
func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *types.ContainerJSON) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { |
|
var config container.Config |
|
var hostConf container.HostConfig |
|
if !isCreate { |
|
config = *oldContainer.Config |
|
hostConf = *oldContainer.HostConfig |
|
} |
|
var networkConf network.NetworkingConfig |
|
|
|
portMap, err := checkPortStats(req.ExposedPorts) |
|
if err != nil { |
|
return nil, nil, nil, err |
|
} |
|
exposed := make(nat.PortSet) |
|
for port := range portMap { |
|
exposed[port] = struct{}{} |
|
} |
|
config.Image = req.Image |
|
config.Cmd = req.Cmd |
|
config.Entrypoint = req.Entrypoint |
|
config.Env = req.Env |
|
config.Labels = stringsToMap(req.Labels) |
|
config.ExposedPorts = exposed |
|
config.OpenStdin = req.OpenStdin |
|
config.Tty = req.Tty |
|
|
|
if len(req.Network) != 0 { |
|
switch req.Network { |
|
case "host", "none", "bridge": |
|
hostConf.NetworkMode = container.NetworkMode(req.Network) |
|
} |
|
if req.Ipv4 != "" || req.Ipv6 != "" { |
|
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{ |
|
req.Network: { |
|
IPAMConfig: &network.EndpointIPAMConfig{ |
|
IPv4Address: req.Ipv4, |
|
IPv6Address: req.Ipv6, |
|
}, |
|
}} |
|
} else { |
|
networkConf.EndpointsConfig = map[string]*network.EndpointSettings{req.Network: {}} |
|
} |
|
} else { |
|
if req.Ipv4 != "" || req.Ipv6 != "" { |
|
return nil, nil, nil, fmt.Errorf("please set up the network") |
|
} |
|
networkConf = network.NetworkingConfig{} |
|
} |
|
|
|
hostConf.Privileged = req.Privileged |
|
hostConf.AutoRemove = req.AutoRemove |
|
hostConf.CPUShares = req.CPUShares |
|
hostConf.PublishAllPorts = req.PublishAllPorts |
|
hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)} |
|
if req.RestartPolicy == "on-failure" { |
|
hostConf.RestartPolicy.MaximumRetryCount = 5 |
|
} |
|
hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000) |
|
hostConf.Memory = int64(req.Memory * 1024 * 1024) |
|
hostConf.MemorySwap = 0 |
|
hostConf.PortBindings = portMap |
|
hostConf.Binds = []string{} |
|
hostConf.Mounts = []mount.Mount{} |
|
config.Volumes = make(map[string]struct{}) |
|
for _, volume := range req.Volumes { |
|
if volume.Type == "volume" { |
|
hostConf.Mounts = append(hostConf.Mounts, mount.Mount{ |
|
Type: mount.Type(volume.Type), |
|
Source: volume.SourceDir, |
|
Target: volume.ContainerDir, |
|
}) |
|
config.Volumes[volume.ContainerDir] = struct{}{} |
|
} else { |
|
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode)) |
|
} |
|
} |
|
return &config, &hostConf, &networkConf, nil |
|
} |
|
|
|
func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *types.NetworkSettings) { |
|
ctx := context.Background() |
|
|
|
var oldNetworkConf network.NetworkingConfig |
|
if networkConf != nil { |
|
for networkKey := range networkConf.Networks { |
|
oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} |
|
break |
|
} |
|
} |
|
|
|
oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name) |
|
if err != nil { |
|
global.LOG.Errorf("recreate after container update failed, err: %v", err) |
|
return |
|
} |
|
if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil { |
|
global.LOG.Errorf("restart after container update failed, err: %v", err) |
|
} |
|
global.LOG.Info("recreate after container update successful") |
|
} |
|
|
|
func loadVolumeBinds(binds []types.MountPoint) []dto.VolumeHelper { |
|
var datas []dto.VolumeHelper |
|
for _, bind := range binds { |
|
var volumeItem dto.VolumeHelper |
|
volumeItem.Type = string(bind.Type) |
|
if bind.Type == "volume" { |
|
volumeItem.SourceDir = bind.Name |
|
} else { |
|
volumeItem.SourceDir = bind.Source |
|
} |
|
volumeItem.ContainerDir = bind.Destination |
|
volumeItem.Mode = "ro" |
|
if bind.RW { |
|
volumeItem.Mode = "rw" |
|
} |
|
datas = append(datas, volumeItem) |
|
} |
|
return datas |
|
} |
|
|
|
func loadPortByInspect(id string, client *client.Client) ([]types.Port, error) { |
|
container, err := client.ContainerInspect(context.Background(), id) |
|
if err != nil { |
|
return nil, err |
|
} |
|
var itemPorts []types.Port |
|
for key, val := range container.ContainerJSONBase.HostConfig.PortBindings { |
|
if !strings.Contains(string(key), "/") { |
|
continue |
|
} |
|
item := strings.Split(string(key), "/") |
|
itemPort, _ := strconv.ParseUint(item[0], 10, 16) |
|
|
|
for _, itemVal := range val { |
|
publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16) |
|
itemPorts = append(itemPorts, types.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP}) |
|
} |
|
} |
|
return itemPorts, nil |
|
} |
|
|
|
func loadContainerPortForInfo(itemPorts []types.Port) []dto.PortHelper { |
|
var exposedPorts []dto.PortHelper |
|
samePortMap := make(map[string]dto.PortHelper) |
|
ports := transPortToStr(itemPorts) |
|
var itemPort dto.PortHelper |
|
for _, item := range ports { |
|
itemStr := strings.Split(item, "->") |
|
if len(itemStr) < 2 { |
|
continue |
|
} |
|
lastIndex := strings.LastIndex(itemStr[0], ":") |
|
if lastIndex == -1 { |
|
itemPort.HostPort = itemStr[0] |
|
} else { |
|
itemPort.HostIP = itemStr[0][0:lastIndex] |
|
itemPort.HostPort = itemStr[0][lastIndex+1:] |
|
} |
|
itemContainer := strings.Split(itemStr[1], "/") |
|
if len(itemContainer) != 2 { |
|
continue |
|
} |
|
itemPort.ContainerPort = itemContainer[0] |
|
itemPort.Protocol = itemContainer[1] |
|
keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol) |
|
if val, ok := samePortMap[keyItem]; ok { |
|
val.HostIP = "" |
|
samePortMap[keyItem] = val |
|
} else { |
|
samePortMap[keyItem] = itemPort |
|
} |
|
} |
|
for _, val := range samePortMap { |
|
exposedPorts = append(exposedPorts, val) |
|
} |
|
return exposedPorts |
|
} |
|
|
|
func transPortToStr(ports []types.Port) []string { |
|
var ( |
|
ipv4Ports []types.Port |
|
ipv6Ports []types.Port |
|
) |
|
for _, port := range ports { |
|
if strings.Contains(port.IP, ":") { |
|
ipv6Ports = append(ipv6Ports, port) |
|
} else { |
|
ipv4Ports = append(ipv4Ports, port) |
|
} |
|
} |
|
list1 := simplifyPort(ipv4Ports) |
|
list2 := simplifyPort(ipv6Ports) |
|
return append(list1, list2...) |
|
} |
|
func simplifyPort(ports []types.Port) []string { |
|
var datas []string |
|
if len(ports) == 0 { |
|
return datas |
|
} |
|
if len(ports) == 1 { |
|
ip := "" |
|
if len(ports[0].IP) != 0 { |
|
ip = ports[0].IP + ":" |
|
} |
|
itemPortStr := fmt.Sprintf("%s%v/%s", ip, ports[0].PrivatePort, ports[0].Type) |
|
if ports[0].PublicPort != 0 { |
|
itemPortStr = fmt.Sprintf("%s%v->%v/%s", ip, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type) |
|
} |
|
datas = append(datas, itemPortStr) |
|
return datas |
|
} |
|
|
|
sort.Slice(ports, func(i, j int) bool { |
|
return ports[i].PrivatePort < ports[j].PrivatePort |
|
}) |
|
start := ports[0] |
|
|
|
for i := 1; i < len(ports); i++ { |
|
if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type { |
|
if ports[i-1].PrivatePort == start.PrivatePort { |
|
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type) |
|
if start.PublicPort != 0 { |
|
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type) |
|
} |
|
if len(start.IP) == 0 { |
|
itemPortStr = strings.TrimPrefix(itemPortStr, ":") |
|
} |
|
datas = append(datas, itemPortStr) |
|
} else { |
|
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type) |
|
if start.PublicPort != 0 { |
|
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type) |
|
} |
|
if len(start.IP) == 0 { |
|
itemPortStr = strings.TrimPrefix(itemPortStr, ":") |
|
} |
|
datas = append(datas, itemPortStr) |
|
} |
|
start = ports[i] |
|
} |
|
if i == len(ports)-1 { |
|
if ports[i].PrivatePort == start.PrivatePort { |
|
itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type) |
|
if start.PublicPort != 0 { |
|
itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type) |
|
} |
|
if len(start.IP) == 0 { |
|
itemPortStr = strings.TrimPrefix(itemPortStr, ":") |
|
} |
|
datas = append(datas, itemPortStr) |
|
} else { |
|
itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type) |
|
if start.PublicPort != 0 { |
|
itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type) |
|
} |
|
if len(start.IP) == 0 { |
|
itemPortStr = strings.TrimPrefix(itemPortStr, ":") |
|
} |
|
datas = append(datas, itemPortStr) |
|
} |
|
} |
|
} |
|
return datas |
|
}
|
|
|