2021-01-11 20:54:49 +00:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
2022-10-08 14:02:02 +00:00
|
|
|
"encoding/json"
|
2021-01-11 20:54:49 +00:00
|
|
|
"fmt"
|
2022-09-27 20:33:05 +00:00
|
|
|
"net/url"
|
2021-01-11 20:54:49 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2022-09-28 01:25:04 +00:00
|
|
|
"strings"
|
2021-01-11 20:54:49 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/urfave/cli/v2"
|
2023-05-02 19:25:49 +00:00
|
|
|
|
|
|
|
"codeberg.org/woodpecker-plugins/plugin-docker-buildx/utils"
|
2021-01-11 20:54:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Daemon defines Docker daemon parameters.
|
|
|
|
type Daemon struct {
|
2021-07-08 07:00:51 +00:00
|
|
|
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 cli.StringSlice // Docker daemon dns server
|
|
|
|
DNSSearch cli.StringSlice // Docker daemon dns search domain
|
|
|
|
MTU string // Docker daemon mtu setting
|
|
|
|
IPv6 bool // Docker daemon IPv6 networking
|
|
|
|
Experimental bool // Docker daemon enable experimental mode
|
2021-07-25 12:28:33 +00:00
|
|
|
BuildkitConfig string // Docker buildkit config
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Login defines Docker login parameters.
|
|
|
|
type Login struct {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build defines Docker build parameters.
|
|
|
|
type Build struct {
|
2022-11-05 02:25:44 +00:00
|
|
|
Remote string // Git remote URL
|
|
|
|
Ref string // Git commit ref
|
|
|
|
Branch string // Git repository branch
|
|
|
|
Dockerfile string // Docker build Dockerfile
|
|
|
|
Context string // Docker build context
|
|
|
|
TagsAuto bool // Docker build auto tag
|
|
|
|
TagsDefaultName string // Docker build auto tag name override
|
|
|
|
TagsSuffix string // Docker build tags with suffix
|
|
|
|
Tags cli.StringSlice // Docker build tags
|
2023-05-02 19:25:49 +00:00
|
|
|
TagsFile string // Docker build tags read from an file
|
2022-11-05 02:25:44 +00:00
|
|
|
LabelsAuto bool // Docker build auto labels
|
|
|
|
Labels cli.StringSlice // Docker build labels
|
|
|
|
Platforms cli.StringSlice // Docker build target platforms
|
|
|
|
Args cli.StringSlice // Docker build args
|
|
|
|
ArgsEnv cli.StringSlice // Docker build args from env
|
|
|
|
Target string // Docker build target
|
|
|
|
Output string // Docker build output
|
|
|
|
Pull bool // Docker build pull
|
|
|
|
CacheFrom cli.StringSlice // Docker build cache-from
|
2022-11-23 15:55:12 +00:00
|
|
|
CacheTo cli.StringSlice // Docker build cache-to
|
2022-11-05 02:25:44 +00:00
|
|
|
Compress bool // Docker build compress
|
|
|
|
Repo cli.StringSlice // Docker build repository
|
|
|
|
NoCache bool // Docker build no-cache
|
|
|
|
AddHost cli.StringSlice // Docker build add-host
|
|
|
|
Quiet bool // Docker build quiet
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Settings for the Plugin.
|
|
|
|
type Settings struct {
|
2022-10-08 14:02:02 +00:00
|
|
|
Daemon Daemon
|
|
|
|
Logins []Login
|
|
|
|
LoginsRaw string
|
|
|
|
DefaultLogin Login
|
|
|
|
Build Build
|
|
|
|
Dryrun bool
|
|
|
|
Cleanup bool
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
2022-10-08 14:02:02 +00:00
|
|
|
func (l Login) anonymous() bool {
|
|
|
|
return l.Username == "" || l.Password == ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// Init initialise plugin settings
|
|
|
|
func (p *Plugin) InitSettings() error {
|
2022-10-11 09:38:36 +00:00
|
|
|
if p.settings.LoginsRaw != "" {
|
|
|
|
if err := json.Unmarshal([]byte(p.settings.LoginsRaw), &p.settings.Logins); err != nil {
|
2022-11-04 22:47:43 +00:00
|
|
|
return fmt.Errorf("could not unmarshal logins: %v", err)
|
2022-10-11 09:38:36 +00:00
|
|
|
}
|
2022-10-08 14:02:02 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 21:38:17 +00:00
|
|
|
p.settings.Build.Branch = p.pipeline.Repo.Branch
|
|
|
|
p.settings.Build.Ref = p.pipeline.Commit.Ref
|
2022-10-11 09:38:36 +00:00
|
|
|
|
|
|
|
if len(p.settings.Logins) == 0 {
|
|
|
|
p.settings.Logins = []Login{p.settings.DefaultLogin}
|
|
|
|
} else if !p.settings.DefaultLogin.anonymous() {
|
2022-10-08 14:02:02 +00:00
|
|
|
p.settings.Logins = prepend(p.settings.Logins, p.settings.DefaultLogin)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.settings.Daemon.Registry = p.settings.Logins[0].Registry
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate handles the settings validation of the plugin.
|
|
|
|
func (p *Plugin) Validate() error {
|
|
|
|
if err := p.InitSettings(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-05 02:25:44 +00:00
|
|
|
if !isSingleTag(p.settings.Build.TagsDefaultName) {
|
|
|
|
return fmt.Errorf("'%s' is not a valid, single tag", p.settings.Build.TagsDefaultName)
|
|
|
|
}
|
|
|
|
|
2022-10-08 14:02:02 +00:00
|
|
|
// beside the default login all other logins need to set a username and password
|
|
|
|
for _, l := range p.settings.Logins[1:] {
|
|
|
|
if l.anonymous() {
|
|
|
|
return fmt.Errorf("beside the default login all other logins need to set a username and password")
|
|
|
|
}
|
|
|
|
}
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2023-05-02 19:25:49 +00:00
|
|
|
// overload tags flag with tags.file if set
|
|
|
|
if p.settings.Build.TagsFile != "" {
|
|
|
|
tagsFile, err := os.ReadFile(p.settings.Build.TagsFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not read tags file: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// split file content into slice of tags
|
|
|
|
tagsFileList := strings.Split(strings.TrimSpace(string(tagsFile)), "\n")
|
|
|
|
// trim space of each tag
|
|
|
|
tagsFileList = utils.Map(tagsFileList, func(s string) string { return strings.TrimSpace(s) })
|
|
|
|
|
|
|
|
// finally overwrite
|
|
|
|
p.settings.Build.Tags = *cli.NewStringSlice(tagsFileList...)
|
|
|
|
}
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
if p.settings.Build.TagsAuto {
|
2022-11-06 13:29:57 +00:00
|
|
|
// we only generate tags on default branch or an tag event
|
2021-01-11 20:54:49 +00:00
|
|
|
if UseDefaultTag(
|
|
|
|
p.settings.Build.Ref,
|
|
|
|
p.settings.Build.Branch,
|
|
|
|
) {
|
|
|
|
tag, err := DefaultTagSuffix(
|
|
|
|
p.settings.Build.Ref,
|
2022-11-05 02:25:44 +00:00
|
|
|
p.settings.Build.TagsDefaultName,
|
2022-12-09 10:53:49 +00:00
|
|
|
p.settings.Build.TagsSuffix,
|
2021-01-11 20:54:49 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref)
|
|
|
|
return err
|
|
|
|
}
|
2022-11-04 22:47:43 +00:00
|
|
|
|
|
|
|
// include user supplied tags
|
|
|
|
tag = append(tag, p.sanitizedUserTags()...)
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
p.settings.Build.Tags = *cli.NewStringSlice(tag...)
|
|
|
|
} else {
|
|
|
|
logrus.Printf("skipping automated docker build for %s", p.settings.Build.Ref)
|
|
|
|
return nil
|
|
|
|
}
|
2022-09-28 01:25:04 +00:00
|
|
|
} else {
|
2022-11-04 22:47:43 +00:00
|
|
|
p.settings.Build.Tags = *cli.NewStringSlice(p.sanitizedUserTags()...)
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
2022-09-28 01:25:04 +00:00
|
|
|
|
2022-09-28 00:16:57 +00:00
|
|
|
if p.settings.Build.LabelsAuto {
|
|
|
|
p.settings.Build.Labels = *cli.NewStringSlice(p.Labels()...)
|
|
|
|
}
|
2021-01-11 20:54:49 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-04 22:47:43 +00:00
|
|
|
func (p *Plugin) sanitizedUserTags() []string {
|
|
|
|
// ignore empty tags
|
|
|
|
var tags []string
|
|
|
|
for _, t := range p.settings.Build.Tags.Value() {
|
|
|
|
t = strings.TrimSpace(t)
|
|
|
|
if t != "" {
|
|
|
|
tags = append(tags, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
|
2022-09-27 20:33:05 +00:00
|
|
|
func (p *Plugin) writeBuildkitConfig() error {
|
2022-10-08 14:02:02 +00:00
|
|
|
// no buildkit config, automatically generate buildkit configuration to use a custom CA certificate for each registry
|
2022-09-27 20:33:05 +00:00
|
|
|
if p.settings.Daemon.BuildkitConfig == "" && p.settings.Daemon.Registry != "" {
|
2022-10-08 14:02:02 +00:00
|
|
|
for _, login := range p.settings.Logins {
|
|
|
|
if registry := login.Registry; registry != "" {
|
|
|
|
u, err := url.Parse(registry)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not parse registry address: %s: %v", registry, err)
|
|
|
|
}
|
|
|
|
if u.Host != "" {
|
|
|
|
registry = u.Host
|
|
|
|
}
|
2022-09-27 20:33:05 +00:00
|
|
|
|
2022-10-08 14:02:02 +00:00
|
|
|
caPath := fmt.Sprintf("/etc/docker/certs.d/%s/ca.crt", registry)
|
|
|
|
ca, err := os.Open(caPath)
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
|
|
|
logrus.Warnf("error reading %s: %v", caPath, err)
|
|
|
|
} else if err == nil {
|
|
|
|
ca.Close()
|
|
|
|
p.settings.Daemon.BuildkitConfig += fmt.Sprintf(buildkitConfigTemplate, registry, caPath)
|
|
|
|
}
|
|
|
|
}
|
2022-09-27 20:33:05 +00:00
|
|
|
}
|
|
|
|
}
|
2022-10-08 14:02:02 +00:00
|
|
|
|
|
|
|
// save buildkit config as described
|
2022-09-27 20:33:05 +00:00
|
|
|
if p.settings.Daemon.BuildkitConfig != "" {
|
|
|
|
err := os.WriteFile(buildkitConfig, []byte(p.settings.Daemon.BuildkitConfig), 0o600)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error writing buildkit.toml: %s", err)
|
|
|
|
}
|
|
|
|
}
|
2022-10-08 14:02:02 +00:00
|
|
|
|
2022-09-27 20:33:05 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
// Execute provides the implementation of the plugin.
|
|
|
|
func (p *Plugin) Execute() error {
|
|
|
|
// start the Docker daemon server
|
|
|
|
if !p.settings.Daemon.Disabled {
|
2022-11-23 14:01:45 +00:00
|
|
|
// If no custom DNS value set start internal DNS server
|
|
|
|
if len(p.settings.Daemon.DNS.Value()) == 0 {
|
|
|
|
ip, err := getContainerIP()
|
|
|
|
if err != nil {
|
|
|
|
logrus.Warnf("error detecting IP address: %v", err)
|
|
|
|
} else if ip != "" {
|
|
|
|
p.startCoredns()
|
|
|
|
p.settings.Daemon.DNS.Set(ip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
p.startDaemon()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2022-10-08 14:02:02 +00:00
|
|
|
if p.settings.Logins[0].Config != "" {
|
2022-09-20 16:02:35 +00:00
|
|
|
os.MkdirAll(dockerHome, 0o600)
|
2021-01-11 20:54:49 +00:00
|
|
|
|
|
|
|
path := filepath.Join(dockerHome, "config.json")
|
2022-10-08 14:02:02 +00:00
|
|
|
err := os.WriteFile(path, []byte(p.settings.Logins[0].Config), 0o600)
|
2021-01-11 20:54:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error writing config.json: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// login to the Docker registry
|
2022-10-08 14:02:02 +00:00
|
|
|
if err := p.Login(); err != nil {
|
|
|
|
return err
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
2022-09-27 20:33:05 +00:00
|
|
|
if err := p.writeBuildkitConfig(); err != nil {
|
|
|
|
return err
|
2021-07-25 12:28:33 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
switch {
|
2022-10-08 14:02:02 +00:00
|
|
|
case p.settings.Logins[0].Password != "":
|
2021-01-11 20:54:49 +00:00
|
|
|
fmt.Println("Detected registry credentials")
|
2022-10-08 14:02:02 +00:00
|
|
|
case p.settings.Logins[0].Config != "":
|
2021-01-11 20:54:49 +00:00
|
|
|
fmt.Println("Detected registry credentials file")
|
|
|
|
default:
|
|
|
|
fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.")
|
|
|
|
}
|
|
|
|
|
|
|
|
// add proxy build args
|
|
|
|
addProxyBuildArgs(&p.settings.Build)
|
|
|
|
|
|
|
|
var cmds []*exec.Cmd
|
|
|
|
cmds = append(cmds, commandVersion()) // docker version
|
|
|
|
cmds = append(cmds, commandInfo()) // docker info
|
2021-07-08 07:00:51 +00:00
|
|
|
cmds = append(cmds, commandBuilder(p.settings.Daemon))
|
2021-01-11 20:54:49 +00:00
|
|
|
cmds = append(cmds, commandBuildx())
|
2021-07-02 16:58:55 +00:00
|
|
|
cmds = append(cmds, commandBuild(p.settings.Build, p.settings.Dryrun)) // docker build
|
2021-01-11 20:54:49 +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()
|
2022-11-23 15:55:12 +00:00
|
|
|
if err != nil {
|
2021-01-11 20:54:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-10-08 14:02:02 +00:00
|
|
|
|
|
|
|
func prepend[Type any](slice []Type, elems ...Type) []Type {
|
|
|
|
return append(elems, slice...)
|
|
|
|
}
|