docker.go

68 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
package scaffold

import (
	"compress/gzip"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
)

// BuildImage builds a Docker image locally and saves it as a gzipped tarball.
// Returns the path to the tarball, or empty string if local Docker is unavailable.
func BuildImage(name, source string) (string, error) {
	if err := os.MkdirAll("build", 0755); err != nil {
		return "", err
	}

	dockerfile := filepath.Join(source, "Dockerfile")
	if _, err := os.Stat(dockerfile); os.IsNotExist(err) {
		if source == "." {
			if _, err := os.Stat("Dockerfile"); os.IsNotExist(err) {
				return "", fmt.Errorf("Dockerfile not found")
			}
		} else {
			return "", fmt.Errorf("Dockerfile not found: %s", dockerfile)
		}
	}

	if err := exec.Command("docker", "info").Run(); err != nil {
		log.Printf("Local Docker unavailable, will build on server")
		return "", nil
	}

	log.Printf("Building %s...", name)
	buildCmd := exec.Command("docker", "build", "-t", name, "-f", dockerfile, ".")
	buildCmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=1")
	buildCmd.Stdout = os.Stdout
	buildCmd.Stderr = os.Stderr
	if err := buildCmd.Run(); err != nil {
		return "", fmt.Errorf("docker build: %w", err)
	}

	out := filepath.Join("build", name+".tar.gz")
	outFile, err := os.Create(out)
	if err != nil {
		return "", err
	}
	defer outFile.Close()

	gzWriter := gzip.NewWriter(outFile)

	saveCmd := exec.Command("docker", "save", name)
	saveCmd.Stdout = gzWriter
	saveCmd.Stderr = os.Stderr
	if err := saveCmd.Run(); err != nil {
		gzWriter.Close()
		os.Remove(out)
		return "", fmt.Errorf("docker save: %w", err)
	}

	if err := gzWriter.Close(); err != nil {
		os.Remove(out)
		return "", fmt.Errorf("gzip close: %w", err)
	}

	return out, nil
}