mirror of
https://github.com/thegeeklab/wp-docker-buildx.git
synced 2024-11-22 10:10:39 +00:00
Robert Kaussow
a2bc0869af
BREAKING CHANGE: The type of the `build_args` property was changed from `list` to `map` to support the use of secrets for certain build arguments. During this refactoring, the `build_args_from_env` behavior was also changed to achieve the intended behavior. Environment variable names passed to this list will be used as build arguments if the environment variable is set.
230 lines
5.8 KiB
Go
230 lines
5.8 KiB
Go
package plugin
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/cenkalti/backoff/v4"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/thegeeklab/wp-docker-buildx/docker"
|
|
plugin_exec "github.com/thegeeklab/wp-plugin-go/v3/exec"
|
|
plugin_file "github.com/thegeeklab/wp-plugin-go/v3/file"
|
|
plugin_tag "github.com/thegeeklab/wp-plugin-go/v3/tag"
|
|
plugin_types "github.com/thegeeklab/wp-plugin-go/v3/types"
|
|
plugin_util "github.com/thegeeklab/wp-plugin-go/v3/util"
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
var ErrTypeAssertionFailed = errors.New("type assertion failed")
|
|
|
|
const (
|
|
strictFilePerm = 0o600
|
|
daemonBackoffMaxRetries = 3
|
|
daemonBackoffInitialInterval = 2 * time.Second
|
|
daemonBackoffMultiplier = 3.5
|
|
)
|
|
|
|
//nolint:revive
|
|
func (p *Plugin) run(ctx context.Context) error {
|
|
if err := p.FlagsFromContext(); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
if err := p.Validate(); err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
if err := p.Execute(); err != nil {
|
|
return fmt.Errorf("execution failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate handles the settings validation of the plugin.
|
|
func (p *Plugin) Validate() error {
|
|
p.Settings.Build.Branch = p.Metadata.Repository.Branch
|
|
p.Settings.Build.Ref = p.Metadata.Curr.Ref
|
|
p.Settings.Daemon.Registry = p.Settings.Registry.Address
|
|
|
|
if p.Settings.Build.TagsAuto {
|
|
// return true if tag event or default branch
|
|
if plugin_tag.IsTaggable(
|
|
p.Settings.Build.Ref,
|
|
p.Settings.Build.Branch,
|
|
) {
|
|
tag, err := plugin_tag.SemverTagSuffix(
|
|
p.Settings.Build.Ref,
|
|
p.Settings.Build.TagsSuffix,
|
|
true,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot generate tags from %s, invalid semantic version: %w", p.Settings.Build.Ref, err)
|
|
}
|
|
|
|
p.Settings.Build.Tags = *cli.NewStringSlice(tag...)
|
|
} else {
|
|
log.Info().Msgf("skip auto-tagging for %s, not on default branch or tag", p.Settings.Build.Ref)
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Execute provides the implementation of the plugin.
|
|
//
|
|
//nolint:gocognit
|
|
func (p *Plugin) Execute() error {
|
|
var err error
|
|
|
|
homeDir := plugin_util.GetUserHomeDir()
|
|
batchCmd := make([]*plugin_exec.Cmd, 0)
|
|
|
|
// start the Docker daemon server
|
|
//nolint: nestif
|
|
if !p.Settings.Daemon.Disabled {
|
|
// If no custom DNS value set start internal DNS server
|
|
if len(p.Settings.Daemon.DNS.Value()) == 0 {
|
|
ip, err := GetContainerIP()
|
|
if err != nil {
|
|
log.Warn().Msgf("error detecting IP address: %v", err)
|
|
}
|
|
|
|
if ip != "" {
|
|
log.Debug().Msgf("discovered IP address: %v", ip)
|
|
|
|
cmd := p.Settings.Daemon.StartCoreDNS()
|
|
go func() {
|
|
_ = cmd.Run()
|
|
}()
|
|
|
|
if err := p.Settings.Daemon.DNS.Set(ip); err != nil {
|
|
return fmt.Errorf("error setting daemon dns: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
cmd := p.Settings.Daemon.Start()
|
|
go func() {
|
|
_ = cmd.Run()
|
|
}()
|
|
}
|
|
|
|
// 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 := docker.Info()
|
|
|
|
err := cmd.Run()
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
time.Sleep(time.Second * 1)
|
|
}
|
|
|
|
if p.Settings.Registry.Config != "" {
|
|
path := filepath.Join(homeDir, ".docker", "config.json")
|
|
if err := os.MkdirAll(filepath.Dir(path), strictFilePerm); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := WriteDockerConf(path, p.Settings.Registry.Config); err != nil {
|
|
return fmt.Errorf("error writing docker config: %w", err)
|
|
}
|
|
}
|
|
|
|
if p.Settings.Registry.Password != "" {
|
|
if err := p.Settings.Registry.Login().Run(); err != nil {
|
|
return fmt.Errorf("error authenticating: %w", err)
|
|
}
|
|
}
|
|
|
|
buildkitConf := p.Settings.BuildkitConfig
|
|
if buildkitConf != "" {
|
|
if p.Settings.Daemon.BuildkitConfigFile, err = plugin_file.WriteTmpFile("buildkit.toml", buildkitConf); err != nil {
|
|
return fmt.Errorf("error writing buildkit config: %w", err)
|
|
}
|
|
|
|
defer os.Remove(p.Settings.Daemon.BuildkitConfigFile)
|
|
}
|
|
|
|
switch {
|
|
case p.Settings.Registry.Password != "":
|
|
log.Info().Msgf("Detected registry credentials")
|
|
case p.Settings.Registry.Config != "":
|
|
log.Info().Msgf("Detected registry credentials file")
|
|
default:
|
|
log.Info().Msgf("Registry credentials or Docker config not provided. Guest mode enabled.")
|
|
}
|
|
|
|
p.Settings.Build.AddProxyBuildArgs()
|
|
|
|
backoffOps := func() error {
|
|
return docker.Version().Run()
|
|
}
|
|
backoffLog := func(err error, delay time.Duration) {
|
|
log.Error().Msgf("failed to run docker version command: %v: retry in %s", err, delay.Truncate(time.Second))
|
|
}
|
|
|
|
if err := backoff.RetryNotify(backoffOps, newBackoff(daemonBackoffMaxRetries), backoffLog); err != nil {
|
|
return err
|
|
}
|
|
|
|
batchCmd = append(batchCmd, docker.Info())
|
|
batchCmd = append(batchCmd, p.Settings.Daemon.CreateBuilder())
|
|
batchCmd = append(batchCmd, p.Settings.Daemon.ListBuilder())
|
|
batchCmd = append(batchCmd, p.Settings.Build.Run())
|
|
|
|
for _, cmd := range batchCmd {
|
|
if cmd == nil {
|
|
continue
|
|
}
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Plugin) FlagsFromContext() error {
|
|
cacheFrom, ok := p.Context.Generic("cache-from").(*plugin_types.StringSliceFlag)
|
|
if !ok {
|
|
return fmt.Errorf("%w: failed to read cache-from input", ErrTypeAssertionFailed)
|
|
}
|
|
|
|
p.Settings.Build.CacheFrom = cacheFrom.Get()
|
|
|
|
secrets, ok := p.Context.Generic("secrets").(*plugin_types.StringSliceFlag)
|
|
if !ok {
|
|
return fmt.Errorf("%w: failed to read secrets input", ErrTypeAssertionFailed)
|
|
}
|
|
|
|
p.Settings.Build.Secrets = secrets.Get()
|
|
|
|
args, ok := p.Context.Generic("args").(*plugin_types.StringMapFlag)
|
|
if !ok {
|
|
return fmt.Errorf("%w: failed to read args input", ErrTypeAssertionFailed)
|
|
}
|
|
|
|
p.Settings.Build.Args = args.Get()
|
|
|
|
return nil
|
|
}
|
|
|
|
func newBackoff(maxRetries uint64) backoff.BackOff {
|
|
b := backoff.NewExponentialBackOff()
|
|
b.InitialInterval = daemonBackoffInitialInterval
|
|
b.Multiplier = daemonBackoffMultiplier
|
|
|
|
return backoff.WithMaxRetries(b, maxRetries)
|
|
}
|