1
0
mirror of https://github.com/1Panel-dev/1Panel.git synced 2025-03-19 05:39:26 +08:00
1Panel/backend/app/service/container.go

613 lines
17 KiB
Go
Raw Normal View History

package service
import (
2022-10-17 16:04:39 +08:00
"bufio"
"bytes"
"context"
"encoding/json"
2022-10-11 14:20:51 +08:00
"fmt"
"io"
2022-10-13 18:24:24 +08:00
"io/ioutil"
2022-10-17 16:04:39 +08:00
"os"
"os/exec"
2022-10-12 18:55:47 +08:00
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/app/dto"
"github.com/1Panel-dev/1Panel/constant"
2022-10-17 16:04:39 +08:00
"github.com/1Panel-dev/1Panel/global"
"github.com/1Panel-dev/1Panel/utils/docker"
"github.com/docker/docker/api/types"
2022-10-12 18:55:47 +08:00
"github.com/docker/docker/api/types/container"
2022-10-11 14:20:51 +08:00
"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"
2022-10-12 18:55:47 +08:00
"github.com/docker/go-connections/nat"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)
type ContainerService struct{}
2022-10-17 16:04:39 +08:00
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)
2022-10-11 14:20:51 +08:00
PageNetwork(req dto.PageInfo) (int64, interface{}, error)
PageVolume(req dto.PageInfo) (int64, interface{}, error)
2022-10-12 18:55:47 +08:00
ListVolume() ([]dto.Options, error)
2022-10-17 16:04:39 +08:00
PageCompose(req dto.PageInfo) (int64, interface{}, error)
CreateCompose(req dto.ComposeCreate) error
ComposeOperation(req dto.ComposeOperation) error
2022-10-12 18:55:47 +08:00
ContainerCreate(req dto.ContainerCreate) error
ContainerOperation(req dto.ContainerOperation) error
ContainerLogs(param dto.ContainerLog) (string, error)
2022-10-13 18:24:24 +08:00
ContainerStats(id string) (*dto.ContainterStats, error)
2022-10-11 19:47:16 +08:00
Inspect(req dto.InspectReq) (string, error)
2022-10-11 14:20:51 +08:00
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{}
}
2022-10-11 14:20:51 +08:00
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
}
2022-10-17 16:04:39 +08:00
options := types.ContainerListOptions{All: true}
if len(req.Filters) != 0 {
options.Filters = filters.NewArgs()
options.Filters.Add("label", req.Filters)
}
list, err = client.ContainerList(context.Background(), options)
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
}
2022-10-17 16:04:39 +08:00
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
}
2022-10-11 19:47:16 +08:00
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
}
2022-10-12 18:55:47 +08:00
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()
2022-10-13 18:24:24 +08:00
client, err := docker.NewDockerClient()
if err != nil {
return err
}
switch req.Operation {
case constant.ContainerOpStart:
2022-10-13 18:24:24 +08:00
err = client.ContainerStart(ctx, req.ContainerID, types.ContainerStartOptions{})
case constant.ContainerOpStop:
2022-10-13 18:24:24 +08:00
err = client.ContainerStop(ctx, req.ContainerID, nil)
case constant.ContainerOpRestart:
2022-10-13 18:24:24 +08:00
err = client.ContainerRestart(ctx, req.ContainerID, nil)
case constant.ContainerOpKill:
2022-10-13 18:24:24 +08:00
err = client.ContainerKill(ctx, req.ContainerID, "SIGKILL")
case constant.ContainerOpPause:
2022-10-13 18:24:24 +08:00
err = client.ContainerPause(ctx, req.ContainerID)
case constant.ContainerOpUnpause:
2022-10-13 18:24:24 +08:00
err = client.ContainerUnpause(ctx, req.ContainerID)
case constant.ContainerOpRename:
2022-10-13 18:24:24 +08:00
err = client.ContainerRename(ctx, req.ContainerID, req.NewName)
case constant.ContainerOpRemove:
2022-10-13 18:24:24 +08:00
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
}
2022-10-11 14:20:51 +08:00
2022-10-13 18:24:24 +08:00
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
}
2022-10-11 14:20:51 +08:00
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))
}
2022-10-11 17:46:52 +08:00
var ipam network.IPAMConfig
if len(item.IPAM.Config) > 0 {
ipam = item.IPAM.Config[0]
2022-10-11 14:20:51 +08:00
}
data = append(data, dto.Network{
2022-10-11 17:46:52 +08:00
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,
2022-10-11 14:20:51 +08:00
})
}
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
}
2022-10-11 17:46:52 +08:00
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
2022-10-11 14:20:51 +08:00
}
2022-10-11 17:46:52 +08:00
2022-10-11 14:20:51 +08:00
options := types.NetworkCreate{
2022-10-11 17:46:52 +08:00
Driver: req.Driver,
2022-10-11 14:20:51 +08:00
Options: stringsToMap(req.Options),
Labels: stringsToMap(req.Labels),
}
2022-10-11 17:46:52 +08:00
if hasConf {
options.IPAM = &network.IPAM{Config: []network.IPAMConfig{ipam}}
}
2022-10-11 14:20:51 +08:00
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
}
2022-10-12 18:55:47 +08:00
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
}
2022-10-11 14:20:51 +08:00
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
}
2022-10-13 18:24:24 +08:00
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
}