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)
|
||
}
|