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 这里需要注意的一点就是移除了镜像之后, // 会出现:的标签,这个是因为下载的镜像是分层的,所以删除会导致 func (d *Docker) RemoveImage(name string) error { _, err := d.ImageRemove(context.TODO(), name, types.ImageRemoveOptions{}) return err } // RemoveDanglingImages remove dangling images 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) }