go通过docker sdk访问复制文件到容器中

发布时间 2023-07-20 11:34:35作者: 厚礼蝎

第一步:需要先判断容器中的文件夹是否存在

第二步:将需要拷贝的文件打包成tar存档

第三步:读取打包的tar存档,然后拷贝到容器中

package main

import (
	"archive/tar"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
	"unicode"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/client"
)

// 判断文件夹是否存在
func checkFolderExistenceInContainer(cli *client.Client, containerID, folderPath string) (bool, error) {
	ctx := context.Background()

	// 创建一个容器内执行的命令
	cmd := []string{"sh", "-c", fmt.Sprintf("test -e %s > /dev/null && echo $?", folderPath)}

	// 创建一个容器执行请求
	createResp, err := cli.ContainerExecCreate(ctx, containerID, types.ExecConfig{
		AttachStdout: true,
		AttachStderr: true,
		Cmd:          cmd,
	})
	if err != nil {
		fmt.Println(err.Error())
		return false, err
	}

	// 执行命令并获取输出
	resp, err := cli.ContainerExecAttach(ctx, createResp.ID, types.ExecStartCheck{})
	if err != nil {
		return false, err
	}
	defer resp.Close()
	// 读取命令输出
	output, err := io.ReadAll(resp.Reader)
	if err != nil {
		return false, err
	}

	// 去除不可见字符串
	cleanedStr := strings.Map(func(r rune) rune {
		// 使用unicode.IsGraphic函数判断字符是否为可见字符
		if unicode.IsGraphic(r) {
			return r
		}
		return -1 // 返回-1表示删除该字符
	}, string(output))
	// 判断文件夹是否存在
	// 此处假设如果文件夹存在,Shell命令返回值为0
	return cleanedStr == "0", nil
}

// 打包文件夹中的文件
func createTarArchiveDir(sourceDir, tarFilePath string) error {
	tarFile, err := os.Create(tarFilePath)
	if err != nil {
		return fmt.Errorf("无法创建tar存档文件: %w", err)
	}
	defer tarFile.Close()

	tw := tar.NewWriter(tarFile)
	defer tw.Close()

	return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(sourceDir, path)
		if err != nil {
			return err
		}

		header, err := tar.FileInfoHeader(info, relPath)
		if err != nil {
			return err
		}

		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		if !info.Mode().IsRegular() {
			return nil
		}

		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		_, err = io.Copy(tw, file)
		return err
	})
}

// 打包特定文件
func createTarArchiveFiles(sourceFiles []string, tarFilePath string) error {
	tarFile, err := os.Create(tarFilePath)
	if err != nil {
		return fmt.Errorf("无法创建tar存档文件: %w", err)
	}
	defer tarFile.Close()

	tw := tar.NewWriter(tarFile)
	defer tw.Close()

	for _, sourceFile := range sourceFiles {
		file, err := os.Open(sourceFile)
		if err != nil {
			return fmt.Errorf("无法打开文件 %s: %w", sourceFile, err)
		}
		defer file.Close()

		stat, err := file.Stat()
		if err != nil {
			return fmt.Errorf("无法获取文件信息 %s: %w", sourceFile, err)
		}
		// 这里可以修改拷贝到容器中文件的文件名
		newName := "c" + filepath.Base(sourceFile)
		header := &tar.Header{
			Name: newName,
			Mode: int64(stat.Mode().Perm()),
			Size: stat.Size(),
		}

		if err := tw.WriteHeader(header); err != nil {
			return fmt.Errorf("无法写入tar头部信息 %s: %w", sourceFile, err)
		}

		_, err = io.Copy(tw, file)
		if err != nil {
			return fmt.Errorf("无法将文件内容写入tar存档 %s: %w", sourceFile, err)
		}
	}

	return nil
}

func main() {
	// 设置远程Docker守护进程的地址(包括协议和端口号)
	remoteDockerHost := "tcp://10.0.0.12:2376"

	// 创建Docker客户端并指定远程Docker守护进程地址
	cli, err := client.NewClientWithOpts(
		client.WithHost(remoteDockerHost),
		// client.WithVersion("1.41"),
		client.WithAPIVersionNegotiation(),
		client.WithTLSClientConfig("cert/ca.pem", "cert/cert.pem", "cert/key.pem"),
	)
	if err != nil {
		fmt.Println("创建容器失败:", err)
		return
	}
	containerID := "nginx"                  // 要复制文件的容器ID
	sourceFiles := []string{"a.html"}       // 替换为要打包的特定文件的路径
	tarFilePath := "test.tar"               // 替换为要生成的.tar存档路径
	destFilePath := "/usr/share/nginx/html" // 容器中的目标文件夹路径

	//判断目标文件夹路径是否存在
	exists, err := checkFolderExistenceInContainer(cli, containerID, destFilePath)
	if err != nil {
		fmt.Printf("检查文件夹存在性时发生错误:%v\n", err)
		return
	}

	if exists {
		err = createTarArchiveFiles(sourceFiles, tarFilePath)
		if err != nil {
			fmt.Println("创建tar存档失败:", err)
			return
		}
		srcFile, err := os.Open("test.tar")
		if err != nil {
			fmt.Println("文件打开失败:", err)
			return
		}
		err = cli.CopyToContainer(context.Background(), containerID, destFilePath, srcFile, types.CopyToContainerOptions{
			AllowOverwriteDirWithFile: true,
		})
		if err != nil {
			fmt.Println("复制文件失败:", err.Error())
			return
		}

		fmt.Println("文件拷贝成功!")
		err = os.Remove("test.tar")
		if err != nil {
			fmt.Println("无法删除文件:", err)
			return
		}

		fmt.Println("缓存文件删除成功!")
	} else {
		fmt.Printf("文件夹 %s 不存在于容器 %s 内\n", destFilePath, containerID)
	}
}