woodpecker-docker-buildx/docker.go

393 lines
10 KiB
Go
Raw Normal View History

2017-08-27 19:27:28 +00:00
package docker
2016-05-03 23:17:16 +00:00
import (
"fmt"
2020-03-24 20:49:10 +00:00
"io/ioutil"
2016-05-03 23:17:16 +00:00
"os"
"os/exec"
2020-03-24 20:49:10 +00:00
"path/filepath"
2016-05-03 23:17:16 +00:00
"strings"
"time"
)
type (
// Daemon defines Docker daemon parameters.
Daemon struct {
Registry string // Docker registry
Mirror string // Docker registry mirror
Insecure bool // Docker daemon enable insecure registries
StorageDriver string // Docker daemon storage driver
StoragePath string // Docker daemon storage path
Disabled bool // DOcker daemon is disabled (already running)
Debug bool // Docker daemon started in debug mode
Bip string // Docker daemon network bridge IP address
DNS []string // Docker daemon dns server
DNSSearch []string // Docker daemon dns search domain
2016-07-26 10:09:00 +00:00
MTU string // Docker daemon mtu setting
2016-10-31 20:23:33 +00:00
IPv6 bool // Docker daemon IPv6 networking
2017-01-19 18:17:43 +00:00
Experimental bool // Docker daemon enable experimental mode
2016-05-03 23:17:16 +00:00
}
// Login defines Docker login parameters.
Login struct {
2020-03-24 20:49:10 +00:00
Registry string // Docker registry address
Username string // Docker registry username
Password string // Docker registry password
Email string // Docker registry email
Config string // Docker Auth Config
2016-05-03 23:17:16 +00:00
}
// Build defines Docker build parameters.
Build struct {
2021-01-10 22:47:55 +00:00
Remote string // Git remote URL
Name string // Docker build using default named tag
Dockerfile string // Docker build Dockerfile
Context string // Docker build context
Tags []string // Docker build tags
Platforms []string // Docker build target platforms
Args []string // Docker build args
ArgsEnv []string // Docker build args from env
Target string // Docker build target
Squash bool // Docker build squash
Pull bool // Docker build pull
CacheFrom []string // Docker build cache-from
Compress bool // Docker build compress
Repo string // Docker build repository
NoCache bool // Docker build no-cache
AddHost []string // Docker build add-host
Quiet bool // Docker build quiet
2016-05-03 23:17:16 +00:00
}
// Plugin defines the Docker plugin parameters.
Plugin struct {
2017-11-09 21:28:19 +00:00
Login Login // Docker login configuration
Build Build // Docker build configuration
Daemon Daemon // Docker daemon configuration
Dryrun bool // Docker push is skipped
Cleanup bool // Docker purge is enabled
2016-05-03 23:17:16 +00:00
}
)
// Exec executes the plugin step
func (p Plugin) Exec() error {
// start the Docker daemon server
if !p.Daemon.Disabled {
2019-08-14 19:51:43 +00:00
p.startDaemon()
2016-05-03 23:17:16 +00:00
}
// poll the docker daemon until it is started. This ensures the daemon is
// ready to accept connections before we proceed.
for i := 0; i < 15; i++ {
cmd := commandInfo()
err := cmd.Run()
if err == nil {
break
}
time.Sleep(time.Second * 1)
}
// Create Auth Config File
2020-03-24 20:49:10 +00:00
if p.Login.Config != "" {
os.MkdirAll(dockerHome, 0600)
path := filepath.Join(dockerHome, "config.json")
err := ioutil.WriteFile(path, []byte(p.Login.Config), 0600)
if err != nil {
2021-01-10 22:53:07 +00:00
return fmt.Errorf("error writing config.json: %s", err)
}
}
2016-05-03 23:17:16 +00:00
// login to the Docker registry
if p.Login.Password != "" {
cmd := commandLogin(p.Login)
err := cmd.Run()
if err != nil {
2021-01-10 22:53:07 +00:00
return fmt.Errorf("error authenticating: %s", err)
2016-05-03 23:17:16 +00:00
}
2020-03-24 20:49:10 +00:00
}
2020-03-24 20:49:10 +00:00
switch {
case p.Login.Password != "":
fmt.Println("Detected registry credentials")
case p.Login.Config != "":
fmt.Println("Detected registry credentials file")
default:
fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.")
2016-05-03 23:17:16 +00:00
}
2020-03-24 20:49:10 +00:00
if p.Build.Squash && !p.Daemon.Experimental {
fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...")
p.Build.Squash = false
}
2016-10-31 20:23:33 +00:00
// add proxy build args
addProxyBuildArgs(&p.Build)
2016-05-03 23:17:16 +00:00
var cmds []*exec.Cmd
2017-07-17 19:51:02 +00:00
cmds = append(cmds, commandVersion()) // docker version
cmds = append(cmds, commandInfo()) // docker info
2021-01-10 22:47:55 +00:00
cmds = append(cmds, commandBuilder())
cmds = append(cmds, commandBuildx())
// pre-pull cache images
for _, img := range p.Build.CacheFrom {
cmds = append(cmds, commandPull(img))
}
2016-05-03 23:17:16 +00:00
cmds = append(cmds, commandBuild(p.Build)) // docker build
2016-06-18 20:38:38 +00:00
2016-05-03 23:17:16 +00:00
for _, tag := range p.Build.Tags {
cmds = append(cmds, commandTag(p.Build, tag)) // docker tag
2016-06-18 20:38:38 +00:00
2021-01-10 22:53:07 +00:00
if !p.Dryrun {
cmds = append(cmds, commandPush(p.Build, tag)) // docker push
2016-05-03 23:17:16 +00:00
}
}
2017-11-09 21:28:19 +00:00
if p.Cleanup {
cmds = append(cmds, commandRmi(p.Build.Name)) // docker rmi
cmds = append(cmds, commandPrune()) // docker system prune -f
}
2016-05-03 23:17:16 +00:00
// execute all commands in batch mode.
for _, cmd := range cmds {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
trace(cmd)
err := cmd.Run()
2018-11-05 18:43:29 +00:00
if err != nil && isCommandPull(cmd.Args) {
fmt.Printf("Could not pull cache-from image %s. Ignoring...\n", cmd.Args[2])
2020-05-22 20:15:19 +00:00
} else if err != nil && isCommandPrune(cmd.Args) {
fmt.Printf("Could not prune system containers. Ignoring...\n")
} else if err != nil && isCommandRmi(cmd.Args) {
fmt.Printf("Could not remove image %s. Ignoring...\n", cmd.Args[2])
} else if err != nil {
2016-05-03 23:17:16 +00:00
return err
}
}
return nil
}
// helper function to create the docker login command.
func commandLogin(login Login) *exec.Cmd {
2016-06-18 21:24:57 +00:00
if login.Email != "" {
2016-06-18 21:15:03 +00:00
return commandLoginEmail(login)
2016-06-18 20:56:22 +00:00
}
return exec.Command(
dockerExe, "login",
"-u", login.Username,
"-p", login.Password,
login.Registry,
)
}
2018-11-05 18:43:29 +00:00
// helper to check if args match "docker pull <image>"
func isCommandPull(args []string) bool {
2019-01-21 00:22:48 +00:00
return len(args) > 2 && args[1] == "pull"
2018-11-05 18:43:29 +00:00
}
2017-09-26 18:42:57 +00:00
func commandPull(repo string) *exec.Cmd {
return exec.Command(dockerExe, "pull", repo)
}
2016-06-18 21:13:45 +00:00
func commandLoginEmail(login Login) *exec.Cmd {
2016-05-03 23:17:16 +00:00
return exec.Command(
2016-05-20 20:00:06 +00:00
dockerExe, "login",
2016-05-03 23:17:16 +00:00
"-u", login.Username,
"-p", login.Password,
2016-06-18 20:56:22 +00:00
"-e", login.Email,
2016-05-03 23:17:16 +00:00
login.Registry,
)
}
// helper function to create the docker info command.
func commandVersion() *exec.Cmd {
2016-05-20 20:00:06 +00:00
return exec.Command(dockerExe, "version")
2016-05-03 23:17:16 +00:00
}
// helper function to create the docker info command.
func commandInfo() *exec.Cmd {
2016-05-20 20:00:06 +00:00
return exec.Command(dockerExe, "info")
2016-05-03 23:17:16 +00:00
}
2021-01-10 22:47:55 +00:00
func commandBuilder() *exec.Cmd {
return exec.Command(dockerExe, "buildx", "create", "--use")
}
func commandBuildx() *exec.Cmd {
return exec.Command(dockerExe, "buildx", "ls")
}
2016-05-03 23:17:16 +00:00
// helper function to create the docker build command.
func commandBuild(build Build) *exec.Cmd {
2017-02-20 11:40:35 +00:00
args := []string{
2021-01-10 22:47:55 +00:00
"buildx",
"build",
2021-01-10 22:47:55 +00:00
"--load",
2016-05-03 23:17:16 +00:00
"--rm=true",
"-f", build.Dockerfile,
"-t", build.Name,
}
2016-10-31 20:23:33 +00:00
args = append(args, build.Context)
if build.Squash {
args = append(args, "--squash")
}
2017-01-27 18:04:55 +00:00
if build.Compress {
args = append(args, "--compress")
}
2017-02-20 11:40:35 +00:00
if build.Pull {
args = append(args, "--pull=true")
}
2018-02-15 05:01:13 +00:00
if build.NoCache {
args = append(args, "--no-cache")
}
2017-10-26 16:26:26 +00:00
for _, arg := range build.CacheFrom {
args = append(args, "--cache-from", arg)
2017-09-26 18:42:57 +00:00
}
2017-08-27 23:28:58 +00:00
for _, arg := range build.ArgsEnv {
addProxyValue(&build, arg)
}
2016-05-03 23:17:16 +00:00
for _, arg := range build.Args {
args = append(args, "--build-arg", arg)
2016-05-03 23:17:16 +00:00
}
2019-12-11 00:33:26 +00:00
for _, host := range build.AddHost {
args = append(args, "--add-host", host)
}
2018-02-07 18:31:10 +00:00
if build.Target != "" {
args = append(args, "--target", build.Target)
}
if build.Quiet {
args = append(args, "--quiet")
}
2021-01-10 22:47:55 +00:00
if len(build.Platforms) > 0 {
args = append(args, "--platform", strings.Join(build.Platforms[:], ","))
}
return exec.Command(dockerExe, args...)
2016-05-03 23:17:16 +00:00
}
2016-10-31 20:23:33 +00:00
// helper function to add proxy values from the environment
func addProxyBuildArgs(build *Build) {
addProxyValue(build, "http_proxy")
addProxyValue(build, "https_proxy")
addProxyValue(build, "no_proxy")
}
// helper function to add the upper and lower case version of a proxy value.
func addProxyValue(build *Build, key string) {
value := getProxyValue(key)
if len(value) > 0 && !hasProxyBuildArg(build, key) {
build.Args = append(build.Args, fmt.Sprintf("%s=%s", key, value))
build.Args = append(build.Args, fmt.Sprintf("%s=%s", strings.ToUpper(key), value))
}
}
// helper function to get a proxy value from the environment.
//
// assumes that the upper and lower case versions of are the same.
func getProxyValue(key string) string {
value := os.Getenv(key)
if len(value) > 0 {
return value
}
return os.Getenv(strings.ToUpper(key))
}
// helper function that looks to see if a proxy value was set in the build args.
func hasProxyBuildArg(build *Build, key string) bool {
keyUpper := strings.ToUpper(key)
for _, s := range build.Args {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) {
return true
}
}
return false
}
2016-05-03 23:17:16 +00:00
// helper function to create the docker tag command.
func commandTag(build Build, tag string) *exec.Cmd {
var (
2016-05-03 23:34:08 +00:00
source = build.Name
target = fmt.Sprintf("%s:%s", build.Repo, tag)
2016-05-03 23:17:16 +00:00
)
return exec.Command(
2016-05-20 20:00:06 +00:00
dockerExe, "tag", source, target,
2016-05-03 23:17:16 +00:00
)
}
// helper function to create the docker push command.
func commandPush(build Build, tag string) *exec.Cmd {
target := fmt.Sprintf("%s:%s", build.Repo, tag)
2016-05-20 20:00:06 +00:00
return exec.Command(dockerExe, "push", target)
2016-05-03 23:17:16 +00:00
}
// helper function to create the docker daemon command.
func commandDaemon(daemon Daemon) *exec.Cmd {
args := []string{
"--data-root", daemon.StoragePath,
"--host=unix:///var/run/docker.sock",
}
2016-05-03 23:17:16 +00:00
if daemon.StorageDriver != "" {
args = append(args, "-s", daemon.StorageDriver)
}
if daemon.Insecure && daemon.Registry != "" {
args = append(args, "--insecure-registry", daemon.Registry)
}
2016-10-10 18:26:13 +00:00
if daemon.IPv6 {
args = append(args, "--ipv6")
}
2016-05-03 23:17:16 +00:00
if len(daemon.Mirror) != 0 {
args = append(args, "--registry-mirror", daemon.Mirror)
}
if len(daemon.Bip) != 0 {
args = append(args, "--bip", daemon.Bip)
}
for _, dns := range daemon.DNS {
args = append(args, "--dns", dns)
}
for _, dnsSearch := range daemon.DNSSearch {
args = append(args, "--dns-search", dnsSearch)
}
2016-07-25 21:45:31 +00:00
if len(daemon.MTU) != 0 {
args = append(args, "--mtu", daemon.MTU)
}
2017-01-19 18:17:43 +00:00
if daemon.Experimental {
args = append(args, "--experimental")
}
return exec.Command(dockerdExe, args...)
2016-05-03 23:17:16 +00:00
}
2020-05-22 20:15:19 +00:00
// helper to check if args match "docker prune"
func isCommandPrune(args []string) bool {
2020-05-28 18:48:36 +00:00
return len(args) > 3 && args[2] == "prune"
2020-05-22 20:15:19 +00:00
}
func commandPrune() *exec.Cmd {
2017-03-14 21:14:48 +00:00
return exec.Command(dockerExe, "system", "prune", "-f")
}
2020-05-22 20:15:19 +00:00
// helper to check if args match "docker rmi"
func isCommandRmi(args []string) bool {
return len(args) > 2 && args[1] == "rmi"
}
func commandRmi(tag string) *exec.Cmd {
return exec.Command(dockerExe, "rmi", tag)
}
2016-05-03 23:17:16 +00:00
// trace writes each command to stdout with the command wrapped in an xml
// tag so that it can be extracted and displayed in the logs.
func trace(cmd *exec.Cmd) {
2016-05-11 08:43:44 +00:00
fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " "))
2016-05-03 23:17:16 +00:00
}