taskExecute/pkg/docker/index.go

372 lines
8.9 KiB
Go
Raw Permalink Normal View History

2023-03-23 14:35:24 +08:00
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)
}