1Panel/backend/app/service/container.go
2022-10-19 15:06:32 +08:00

475 lines
13 KiB
Go

package service
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/app/dto"
"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/api/types/volume"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/go-connections/nat"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type ContainerService struct{}
type IContainerService interface {
Page(req dto.PageContainer) (int64, interface{}, error)
PageNetwork(req dto.PageInfo) (int64, interface{}, error)
PageVolume(req dto.PageInfo) (int64, interface{}, error)
ListVolume() ([]dto.Options, error)
ContainerCreate(req dto.ContainerCreate) error
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error)
ContainerStats(id string) (*dto.ContainterStats, error)
Inspect(req dto.InspectReq) (string, error)
DeleteNetwork(req dto.BatchDelete) error
CreateNetwork(req dto.NetworkCreat) error
DeleteVolume(req dto.BatchDelete) error
CreateVolume(req dto.VolumeCreat) error
}
func NewIContainerService() IContainerService {
return &ContainerService{}
}
func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) {
var (
records []types.Container
list []types.Container
backDatas []dto.ContainerInfo
)
client, err := docker.NewDockerClient()
if err != nil {
return 0, nil, err
}
list, err = client.ContainerList(context.Background(), types.ContainerListOptions{All: req.Status == "all"})
if err != nil {
return 0, nil, err
}
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]
}
for _, container := range records {
backDatas = append(backDatas, dto.ContainerInfo{
ContainerID: container.ID,
CreateTime: time.Unix(container.Created, 0).Format("2006-01-02 15:04:05"),
Name: container.Names[0][1:],
ImageId: strings.Split(container.ImageID, ":")[1],
ImageName: container.Image,
State: container.State,
RunTime: container.Status,
})
}
return int64(total), backDatas, nil
}
func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) {
client, err := docker.NewDockerClient()
if err != nil {
return "", err
}
var inspectInfo interface{}
switch req.Type {
case "container":
inspectInfo, err = client.ContainerInspect(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) ContainerCreate(req dto.ContainerCreate) error {
client, err := docker.NewDockerClient()
if err != nil {
return err
}
config := &container.Config{
Image: req.Image,
Cmd: req.Cmd,
Env: req.Env,
Labels: stringsToMap(req.Labels),
}
hostConf := &container.HostConfig{
AutoRemove: req.AutoRemove,
PublishAllPorts: req.PublishAllPorts,
RestartPolicy: container.RestartPolicy{Name: req.RestartPolicy},
}
if req.RestartPolicy == "on-failure" {
hostConf.RestartPolicy.MaximumRetryCount = 5
}
if req.NanoCPUs != 0 {
hostConf.NanoCPUs = req.NanoCPUs * 1000000000
}
if req.Memory != 0 {
hostConf.Memory = req.Memory
}
if len(req.ExposedPorts) != 0 {
hostConf.PortBindings = make(nat.PortMap)
for _, port := range req.ExposedPorts {
bindItem := nat.PortBinding{HostPort: strconv.Itoa(port.HostPort)}
hostConf.PortBindings[nat.Port(fmt.Sprintf("%d/tcp", port.ContainerPort))] = []nat.PortBinding{bindItem}
}
}
if len(req.Volumes) != 0 {
config.Volumes = make(map[string]struct{})
for _, volume := range req.Volumes {
config.Volumes[volume.ContainerDir] = struct{}{}
hostConf.Binds = append(hostConf.Binds, fmt.Sprintf("%s:%s:%s", volume.SourceDir, volume.ContainerDir, volume.Mode))
}
}
container, err := client.ContainerCreate(context.TODO(), config, hostConf, &network.NetworkingConfig{}, &v1.Platform{}, req.Name)
if err != nil {
return err
}
if err := client.ContainerStart(context.TODO(), container.ID, types.ContainerStartOptions{}); err != nil {
return fmt.Errorf("create successful but start failed, 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
}
switch req.Operation {
case constant.ContainerOpStart:
err = client.ContainerStart(ctx, req.ContainerID, types.ContainerStartOptions{})
case constant.ContainerOpStop:
err = client.ContainerStop(ctx, req.ContainerID, nil)
case constant.ContainerOpRestart:
err = client.ContainerRestart(ctx, req.ContainerID, nil)
case constant.ContainerOpKill:
err = client.ContainerKill(ctx, req.ContainerID, "SIGKILL")
case constant.ContainerOpPause:
err = client.ContainerPause(ctx, req.ContainerID)
case constant.ContainerOpUnpause:
err = client.ContainerUnpause(ctx, req.ContainerID)
case constant.ContainerOpRename:
err = client.ContainerRename(ctx, req.ContainerID, req.NewName)
case constant.ContainerOpRemove:
err = client.ContainerRemove(ctx, req.ContainerID, types.ContainerRemoveOptions{RemoveVolumes: true, RemoveLinks: true, Force: true})
}
return err
}
func (u *ContainerService) ContainerLogs(req dto.ContainerLog) (string, error) {
var (
options types.ContainerLogsOptions
logs io.ReadCloser
buf *bytes.Buffer
err error
)
client, err := docker.NewDockerClient()
if err != nil {
return "", err
}
options = types.ContainerLogsOptions{
ShowStdout: true,
Timestamps: true,
}
if req.Mode != "all" {
options.Since = req.Mode
}
if logs, err = client.ContainerLogs(context.Background(), req.ContainerID, options); err != nil {
return "", err
}
defer logs.Close()
buf = new(bytes.Buffer)
if _, err = stdcopy.StdCopy(buf, nil, logs); err != nil {
return "", err
}
return buf.String(), nil
}
func (u *ContainerService) ContainerStats(id string) (*dto.ContainterStats, error) {
client, err := docker.NewDockerClient()
if err != nil {
return nil, err
}
res, err := client.ContainerStats(context.TODO(), id, false)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
var stats *types.StatsJSON
if err := json.Unmarshal(body, &stats); err != nil {
return nil, err
}
var data dto.ContainterStats
previousCPU := stats.PreCPUStats.CPUUsage.TotalUsage
previousSystem := stats.PreCPUStats.SystemUsage
data.CPUPercent = calculateCPUPercentUnix(previousCPU, previousSystem, 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.Memory = data.Memory - data.Cache
data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks)
data.ShotTime = stats.Read
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 {
sps := strings.Split(label, "=")
if len(sps) > 1 {
lableMap[sps[0]] = sps[1]
}
}
return lableMap
}
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
var (
cpuPercent = 0.0
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
)
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
}
return cpuPercent
}
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
}