372 lines
8.9 KiB
Go
372 lines
8.9 KiB
Go
|
package docker
|
|||
|
|
|||
|
import (
|
|||
|
"archive/tar"
|
|||
|
"context"
|
|||
|
"encoding/base64"
|
|||
|
"encoding/json"
|
|||
|
"fmt"
|
|||
|
"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/mount"
|
|||
|
"github.com/docker/docker/api/types/registry"
|
|||
|
"github.com/docker/docker/client"
|
|||
|
"github.com/docker/go-connections/nat"
|
|||
|
"go.uber.org/zap"
|
|||
|
"io"
|
|||
|
"os"
|
|||
|
"path/filepath"
|
|||
|
"strconv"
|
|||
|
"strings"
|
|||
|
"taskExecute/config"
|
|||
|
"taskExecute/pkg/utils"
|
|||
|
)
|
|||
|
|
|||
|
var (
|
|||
|
ContainerList []ContainerStore
|
|||
|
)
|
|||
|
|
|||
|
func SaveStore() {
|
|||
|
str, _ := json.Marshal(ContainerList)
|
|||
|
_ = os.WriteFile(config.Cfg.Store, str, os.ModePerm)
|
|||
|
}
|
|||
|
|
|||
|
// Docker 1.Docker docker client
|
|||
|
type Docker struct {
|
|||
|
*client.Client
|
|||
|
}
|
|||
|
|
|||
|
// NewDockerClient 2.init docker client
|
|||
|
func NewDockerClient() *Docker {
|
|||
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
|||
|
if err != nil {
|
|||
|
return nil
|
|||
|
}
|
|||
|
return &Docker{
|
|||
|
cli,
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Images get images from
|
|||
|
func (d *Docker) Images(opt types.ImageListOptions) ([]types.ImageSummary, error) {
|
|||
|
return d.ImageList(context.TODO(), opt)
|
|||
|
}
|
|||
|
|
|||
|
// PushImage --> pull image to harbor仓库
|
|||
|
func (d *Docker) PushImage(image, user, pwd string) error {
|
|||
|
authConfig := types.AuthConfig{
|
|||
|
Username: user, //harbor用户名
|
|||
|
Password: pwd, //harbor 密码
|
|||
|
}
|
|||
|
encodedJSON, err := json.Marshal(authConfig)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
authStr := base64.URLEncoding.EncodeToString(encodedJSON)
|
|||
|
out, err := d.ImagePush(context.TODO(), image, types.ImagePushOptions{RegistryAuth: authStr})
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
body, err := io.ReadAll(out)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
fmt.Printf("Push docker image output: %v\n", string(body))
|
|||
|
|
|||
|
if strings.Contains(string(body), "error") {
|
|||
|
return fmt.Errorf("push image to docker error")
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// PullImage pull image
|
|||
|
func (d *Docker) PullImage(name string) error {
|
|||
|
resp, err := d.ImagePull(context.TODO(), name, types.ImagePullOptions{})
|
|||
|
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
_, err = io.Copy(io.Discard, resp)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// RemoveImage remove image 这里需要注意的一点就是移除了镜像之后,
|
|||
|
// 会出现<none>:<none>的标签,这个是因为下载的镜像是分层的,所以删除会导致
|
|||
|
func (d *Docker) RemoveImage(name string) error {
|
|||
|
_, err := d.ImageRemove(context.TODO(), name, types.ImageRemoveOptions{})
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
// RemoveDanglingImages remove dangling images <none>
|
|||
|
func (d *Docker) RemoveDanglingImages() error {
|
|||
|
opt := types.ImageListOptions{
|
|||
|
Filters: filters.NewArgs(filters.Arg("dangling", "true")),
|
|||
|
}
|
|||
|
|
|||
|
images, err := d.Images(opt)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
errIDs := make([]string, 0)
|
|||
|
|
|||
|
for _, image := range images {
|
|||
|
fmt.Printf("image.ID: %v\n", image.ID)
|
|||
|
if err := d.RemoveImage(image.ID); err != nil {
|
|||
|
errIDs = append(errIDs, image.ID[7:19])
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if len(errIDs) > 1 {
|
|||
|
return fmt.Errorf("can not remove ids\n%s", errIDs)
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// SaveImage save image to tar file
|
|||
|
func (d *Docker) SaveImage(ids []string, path string) error {
|
|||
|
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer func() {
|
|||
|
_ = file.Close()
|
|||
|
}()
|
|||
|
|
|||
|
out, err := d.ImageSave(context.TODO(), ids)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
if _, err = io.Copy(file, out); err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
// LoadImage load image from tar file
|
|||
|
func (d *Docker) LoadImage(path string) error {
|
|||
|
file, err := os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer func() {
|
|||
|
_ = file.Close()
|
|||
|
}()
|
|||
|
_, err = d.ImageLoad(context.TODO(), file, true)
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
// ImportImage import image
|
|||
|
func (d *Docker) ImportImage(name, tag, path string) error {
|
|||
|
file, err := os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer func() {
|
|||
|
_ = file.Close()
|
|||
|
}()
|
|||
|
source := types.ImageImportSource{
|
|||
|
Source: file,
|
|||
|
SourceName: "-",
|
|||
|
}
|
|||
|
|
|||
|
opt := types.ImageImportOptions{
|
|||
|
Tag: tag,
|
|||
|
}
|
|||
|
|
|||
|
_, err = d.ImageImport(context.TODO(), source, name, opt)
|
|||
|
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
// SearchImage search images
|
|||
|
func (d *Docker) SearchImage(name string) ([]registry.SearchResult, error) {
|
|||
|
|
|||
|
return d.ImageSearch(context.TODO(), name, types.ImageSearchOptions{Limit: 100})
|
|||
|
}
|
|||
|
|
|||
|
// BuildImage build image image 需要构建的镜像名称
|
|||
|
func (d *Docker) BuildImage(warName, image string) error {
|
|||
|
|
|||
|
// 1.需要构建的war包上传到docker/web/目录下
|
|||
|
err := utils.CopyFile(fmt.Sprintf("/tmp/docker/%s", warName), fmt.Sprintf("docker/web/%s", warName))
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
var tags []string
|
|||
|
tags = append(tags, image)
|
|||
|
//打一个docker.tar包
|
|||
|
err = tarIt("docker/", ".")
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
} //src:要打包文件的源地址 target:要打包文件的目标地址 (使用相对路径-->相对于main.go)
|
|||
|
//打开刚刚打的tar包
|
|||
|
dockerBuildContext, _ := os.Open("docker.tar") //打开打包的文件,
|
|||
|
defer func(dockerBuildContext *os.File) {
|
|||
|
_ = dockerBuildContext.Close()
|
|||
|
}(dockerBuildContext)
|
|||
|
options := types.ImageBuildOptions{
|
|||
|
Dockerfile: "docker/Dockerfile", //不能是绝对路径 是相对于build context来说的,
|
|||
|
SuppressOutput: false,
|
|||
|
Remove: true,
|
|||
|
ForceRemove: true,
|
|||
|
PullParent: true,
|
|||
|
Tags: tags, //[]string{"192.168.0.1/harbor/cdisample:v1"}
|
|||
|
}
|
|||
|
buildResponse, err := d.ImageBuild(context.Background(), dockerBuildContext, options)
|
|||
|
fmt.Printf("err build: %v\n", err)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("%s", err.Error())
|
|||
|
return err
|
|||
|
}
|
|||
|
fmt.Printf("********* %s **********", buildResponse.OSType)
|
|||
|
response, err := io.ReadAll(buildResponse.Body)
|
|||
|
if err != nil {
|
|||
|
fmt.Printf("%s", err.Error())
|
|||
|
return err
|
|||
|
}
|
|||
|
fmt.Println(string(response))
|
|||
|
return nil
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
source:打包的的路径
|
|||
|
target:放置打包文件的位置
|
|||
|
*/
|
|||
|
func tarIt(source string, target string) error {
|
|||
|
filename := filepath.Base(source)
|
|||
|
fmt.Println(filename)
|
|||
|
target = filepath.Join(target, fmt.Sprintf("%s.tar", filename))
|
|||
|
//target := fmt.Sprintf("%s.tar", filename)
|
|||
|
fmt.Println(target)
|
|||
|
tarFile, err := os.Create(target)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
fmt.Println(tarFile)
|
|||
|
defer func(tarFile *os.File) {
|
|||
|
_ = tarFile.Close()
|
|||
|
}(tarFile)
|
|||
|
|
|||
|
tarball := tar.NewWriter(tarFile)
|
|||
|
// 这里不要忘记关闭,如果不能成功关闭会造成 tar 包不完整
|
|||
|
// 所以这里在关闭的同时进行判断,可以清楚的知道是否成功关闭
|
|||
|
defer func() {
|
|||
|
if err := tarball.Close(); err != nil {
|
|||
|
config.Logger.With(zap.String("docker打包的的路径", source)).
|
|||
|
With(zap.String("放置打包文件的位置", target)).Error("错误信息", zap.Error(err))
|
|||
|
}
|
|||
|
}()
|
|||
|
|
|||
|
info, err := os.Stat(source)
|
|||
|
if err != nil {
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
var baseDir string
|
|||
|
if info.IsDir() {
|
|||
|
baseDir = filepath.Base(source)
|
|||
|
}
|
|||
|
|
|||
|
return filepath.Walk(source,
|
|||
|
func(path string, info os.FileInfo, err error) error {
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
if baseDir != "" {
|
|||
|
header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
|
|||
|
}
|
|||
|
|
|||
|
if err := tarball.WriteHeader(header); err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
|
|||
|
if info.IsDir() {
|
|||
|
return nil
|
|||
|
}
|
|||
|
|
|||
|
file, err := os.Open(path)
|
|||
|
if err != nil {
|
|||
|
return err
|
|||
|
}
|
|||
|
defer func(file *os.File) {
|
|||
|
_ = file.Close()
|
|||
|
}(file)
|
|||
|
_, err = io.Copy(tarball, file)
|
|||
|
return err
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
// CreateContainer create container
|
|||
|
func (d *Docker) CreateContainer(containerName, image string, cmd []string, volumes map[string]string, srcPort string) string {
|
|||
|
// 文件挂载
|
|||
|
m := make([]mount.Mount, 0, len(volumes))
|
|||
|
for k, v := range volumes {
|
|||
|
m = append(m, mount.Mount{Type: mount.TypeBind, Source: k, Target: v})
|
|||
|
}
|
|||
|
|
|||
|
exports := make(nat.PortSet)
|
|||
|
netPort := make(nat.PortMap)
|
|||
|
|
|||
|
// 网络端口映射
|
|||
|
natPort, _ := nat.NewPort("tcp", srcPort)
|
|||
|
exports[natPort] = struct{}{}
|
|||
|
dstPort, err := utils.GetAvailablePort()
|
|||
|
if err != nil {
|
|||
|
config.Logger.With(
|
|||
|
zap.String("containerName", containerName),
|
|||
|
zap.Strings("cmd", cmd),
|
|||
|
zap.String("image", image),
|
|||
|
zap.Int("dstPort", dstPort),
|
|||
|
).Error("创建镜像错误", zap.Error(err))
|
|||
|
}
|
|||
|
portList := make([]nat.PortBinding, 0, 1)
|
|||
|
portList = append(portList, nat.PortBinding{HostIP: "0.0.0.0", HostPort: strconv.Itoa(dstPort)})
|
|||
|
netPort[natPort] = portList
|
|||
|
|
|||
|
ctx := context.Background()
|
|||
|
// 创建容器
|
|||
|
resp, err := d.ContainerCreate(ctx, &container.Config{
|
|||
|
Image: image,
|
|||
|
ExposedPorts: exports,
|
|||
|
Cmd: cmd,
|
|||
|
Tty: false,
|
|||
|
// WorkingDir: workDir,
|
|||
|
}, &container.HostConfig{
|
|||
|
PortBindings: netPort,
|
|||
|
Mounts: m,
|
|||
|
}, nil, nil, containerName)
|
|||
|
|
|||
|
if err != nil {
|
|||
|
config.Logger.With(
|
|||
|
zap.String("containerName", containerName),
|
|||
|
zap.Strings("cmd", cmd), zap.String("image", image),
|
|||
|
).Error("创建镜像错误", zap.Error(err))
|
|||
|
return ""
|
|||
|
}
|
|||
|
|
|||
|
if err := d.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
|
|||
|
config.Logger.With(
|
|||
|
zap.String("containerName", containerName),
|
|||
|
zap.Strings("cmd", cmd), zap.String("image", image),
|
|||
|
).Error("启动镜像错误", zap.Error(err))
|
|||
|
return ""
|
|||
|
}
|
|||
|
return strconv.Itoa(dstPort)
|
|||
|
}
|