go基于docker sdk用dockerfile构建镜像

发布时间 2023-07-19 18:27:56作者: 厚礼蝎

测试了好久,发现,单独读取dockerfile文件,然后上传,是无法构建成功的

构建的时候总会报无法找到dockerfile文件

Error response from daemon: Cannot locate specified Dockerfile: Dockerfile

查资料发现需要将dockerfile以及相关依赖文件打包成tar存档,才能正常读取并构建镜像

而使用 "github.com/docker/docker/pkg/archive" 库是无法正常创建所需的tar存档

需要自己手动打包成tar存档

如果大家有更好更优雅的方法,麻烦留言告知,万分感谢

打包tar存档的方法

打包文件夹中的文件

package main

import (
	"archive/tar"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

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 main() {
	sourceDir := "path/to/source/directory" // 替换为要打包的文件夹路径
	tarFilePath := "archive.tar"            // 替换为要生成的.tar存档路径

	if err := createTarArchiveDir(sourceDir, tarFilePath); err != nil {
		fmt.Println("无法创建tar存档:", err)
		return
	}

	fmt.Println("tar存档创建成功!")
}

特定文件打包的方法

package main

import (
	"archive/tar"
	"fmt"
	"io"
	"os"
)

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

		header := &tar.Header{
			Name: sourceFile,
			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() {
	sourceFiles := []string{"path/to/file1.txt", "path/to/file2.txt"} // 替换为要打包的特定文件的路径
	tarFilePath := "specific.tar"                                    // 替换为要生成的.tar存档路径

	if err := createTarArchiveFiles(sourceFiles, tarFilePath); err != nil {
		fmt.Println("无法创建tar存档:", err)
		return
	}

	fmt.Println("tar存档创建成功!")
}

整合完成构建镜像

package main

import (
	"archive/tar"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"

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

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

		header := &tar.Header{
			Name: sourceFile,
			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(),
	)
	if err != nil {
		fmt.Println("创建容器失败:", err)
		return
	}
	sourceFiles := []string{"aaa", "a.html"} // 替换为要打包的特定文件的路径
	tarFilePath := "archive.tar"             // 替换为要生成的.tar存档路径
    
    // 对特定文件打包成tar存档
	if err := createTarArchiveFiles(sourceFiles, tarFilePath); err != nil {
		fmt.Println("无法创建tar存档:", err)
		return
	}

	fmt.Println("tar存档创建成功!")
	dockerBuildContext, err := os.Open("archive.tar")
	defer dockerBuildContext.Close()
	// 构建Docker镜像
	buildOptions := types.ImageBuildOptions{
		Dockerfile: "aaa",
		Tags:       []string{"a:v5"},
	}
    
    // 构建镜像
	resp, err := cli.ImageBuild(context.Background(), dockerBuildContext, buildOptions)
	if err != nil {
		fmt.Println("无法构建Docker镜像:", err)
		os.Exit(1)
	}
	defer resp.Body.Close()

	// 解析构建过程中的输出日志并打印到控制台
	// 这里只是一个简单的示例,实际应用中可能需要更多处理
	fmt.Println("构建Docker镜像中...")
	dec := json.NewDecoder(resp.Body)
	for {
		var v map[string]interface{}
		if err := dec.Decode(&v); err != nil {
			break
		}
		fmt.Println(v["stream"])
	}

	fmt.Println("Docker镜像构建成功!")

    // 用完后删除tar存档
	err = os.Remove("archive.tar")
	if err != nil {
		fmt.Println("无法删除文件:", err)
		return
	}

	fmt.Println("文件删除成功!")
}