taskExecute/pkg/docker/index.go

372 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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