2021-01-11 20:54:49 +00:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
2023-08-13 20:08:53 +00:00
|
|
|
"context"
|
|
|
|
"errors"
|
2021-01-11 20:54:49 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2024-11-13 22:24:50 +00:00
|
|
|
"path/filepath"
|
2021-01-11 20:54:49 +00:00
|
|
|
"time"
|
|
|
|
|
2023-12-05 14:25:37 +00:00
|
|
|
"github.com/cenkalti/backoff/v4"
|
2023-08-13 20:08:53 +00:00
|
|
|
"github.com/rs/zerolog/log"
|
2024-05-06 18:29:51 +00:00
|
|
|
"github.com/thegeeklab/wp-docker-buildx/docker"
|
2024-05-17 19:55:16 +00:00
|
|
|
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"
|
2021-01-11 20:54:49 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
)
|
|
|
|
|
2023-08-13 20:08:53 +00:00
|
|
|
var ErrTypeAssertionFailed = errors.New("type assertion failed")
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2023-09-04 18:56:19 +00:00
|
|
|
const (
|
|
|
|
strictFilePerm = 0o600
|
|
|
|
daemonBackoffMaxRetries = 3
|
|
|
|
daemonBackoffInitialInterval = 2 * time.Second
|
|
|
|
daemonBackoffMultiplier = 3.5
|
|
|
|
)
|
2023-08-09 09:35:58 +00:00
|
|
|
|
2023-08-13 20:08:53 +00:00
|
|
|
//nolint:revive
|
2023-08-21 09:35:52 +00:00
|
|
|
func (p *Plugin) run(ctx context.Context) error {
|
|
|
|
if err := p.FlagsFromContext(); err != nil {
|
|
|
|
return fmt.Errorf("validation failed: %w", err)
|
2023-08-13 20:08:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2023-02-08 09:13:28 +00:00
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
// Validate handles the settings validation of the plugin.
|
|
|
|
func (p *Plugin) Validate() error {
|
2023-08-13 20:08:53 +00:00
|
|
|
p.Settings.Build.Branch = p.Metadata.Repository.Branch
|
|
|
|
p.Settings.Build.Ref = p.Metadata.Curr.Ref
|
2024-05-06 18:29:51 +00:00
|
|
|
p.Settings.Daemon.Registry = p.Settings.Registry.Address
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2023-08-13 20:08:53 +00:00
|
|
|
if p.Settings.Build.TagsAuto {
|
2021-01-11 20:54:49 +00:00
|
|
|
// return true if tag event or default branch
|
2024-05-17 19:55:16 +00:00
|
|
|
if plugin_tag.IsTaggable(
|
2023-08-13 20:08:53 +00:00
|
|
|
p.Settings.Build.Ref,
|
|
|
|
p.Settings.Build.Branch,
|
2021-01-11 20:54:49 +00:00
|
|
|
) {
|
2024-05-17 19:55:16 +00:00
|
|
|
tag, err := plugin_tag.SemverTagSuffix(
|
2023-08-13 20:08:53 +00:00
|
|
|
p.Settings.Build.Ref,
|
|
|
|
p.Settings.Build.TagsSuffix,
|
2023-12-05 10:46:02 +00:00
|
|
|
true,
|
2021-01-11 20:54:49 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
2023-08-13 20:08:53 +00:00
|
|
|
return fmt.Errorf("cannot generate tags from %s, invalid semantic version: %w", p.Settings.Build.Ref, err)
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
2023-02-08 09:13:28 +00:00
|
|
|
|
2023-08-13 20:08:53 +00:00
|
|
|
p.Settings.Build.Tags = *cli.NewStringSlice(tag...)
|
2021-01-11 20:54:49 +00:00
|
|
|
} else {
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Info().Msgf("skip auto-tagging for %s, not on default branch or tag", p.Settings.Build.Ref)
|
2023-02-08 09:13:28 +00:00
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute provides the implementation of the plugin.
|
2024-05-07 09:57:26 +00:00
|
|
|
//
|
|
|
|
//nolint:gocognit
|
2021-01-11 20:54:49 +00:00
|
|
|
func (p *Plugin) Execute() error {
|
2024-05-06 18:29:51 +00:00
|
|
|
var err error
|
|
|
|
|
2024-05-17 19:55:16 +00:00
|
|
|
homeDir := plugin_util.GetUserHomeDir()
|
|
|
|
batchCmd := make([]*plugin_exec.Cmd, 0)
|
2024-05-05 11:03:50 +00:00
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
// start the Docker daemon server
|
2023-03-24 13:04:29 +00:00
|
|
|
//nolint: nestif
|
2023-08-13 20:08:53 +00:00
|
|
|
if !p.Settings.Daemon.Disabled {
|
2023-03-24 13:04:29 +00:00
|
|
|
// If no custom DNS value set start internal DNS server
|
2023-08-13 20:08:53 +00:00
|
|
|
if len(p.Settings.Daemon.DNS.Value()) == 0 {
|
2024-05-06 18:29:51 +00:00
|
|
|
ip, err := GetContainerIP()
|
2023-03-24 13:04:29 +00:00
|
|
|
if err != nil {
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Warn().Msgf("error detecting IP address: %v", err)
|
2023-03-24 13:04:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ip != "" {
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Debug().Msgf("discovered IP address: %v", ip)
|
2024-05-06 18:29:51 +00:00
|
|
|
|
|
|
|
cmd := p.Settings.Daemon.StartCoreDNS()
|
|
|
|
go func() {
|
|
|
|
_ = cmd.Run()
|
|
|
|
}()
|
2023-03-24 13:04:29 +00:00
|
|
|
|
2023-08-13 20:08:53 +00:00
|
|
|
if err := p.Settings.Daemon.DNS.Set(ip); err != nil {
|
2023-03-24 13:04:29 +00:00
|
|
|
return fmt.Errorf("error setting daemon dns: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
cmd := p.Settings.Daemon.Start()
|
|
|
|
go func() {
|
|
|
|
_ = cmd.Run()
|
|
|
|
}()
|
2021-01-11 20:54:49 +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++ {
|
2024-05-06 18:29:51 +00:00
|
|
|
cmd := docker.Info()
|
2023-02-08 09:13:28 +00:00
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
err := cmd.Run()
|
|
|
|
if err == nil {
|
|
|
|
break
|
|
|
|
}
|
2023-02-08 09:13:28 +00:00
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
time.Sleep(time.Second * 1)
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
if p.Settings.Registry.Config != "" {
|
2024-11-13 22:24:50 +00:00
|
|
|
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 {
|
2024-05-06 18:29:51 +00:00
|
|
|
return fmt.Errorf("error writing docker config: %w", err)
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
if p.Settings.Registry.Password != "" {
|
|
|
|
if err := p.Settings.Registry.Login().Run(); err != nil {
|
2023-08-11 07:13:42 +00:00
|
|
|
return fmt.Errorf("error authenticating: %w", err)
|
2023-08-09 09:35:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
buildkitConf := p.Settings.BuildkitConfig
|
|
|
|
if buildkitConf != "" {
|
2024-05-17 19:55:16 +00:00
|
|
|
if p.Settings.Daemon.BuildkitConfigFile, err = plugin_file.WriteTmpFile("buildkit.toml", buildkitConf); err != nil {
|
2024-05-06 18:29:51 +00:00
|
|
|
return fmt.Errorf("error writing buildkit config: %w", err)
|
2021-07-25 12:28:33 +00:00
|
|
|
}
|
2024-05-06 18:29:51 +00:00
|
|
|
|
|
|
|
defer os.Remove(p.Settings.Daemon.BuildkitConfigFile)
|
2021-07-25 12:28:33 +00:00
|
|
|
}
|
|
|
|
|
2021-01-11 20:54:49 +00:00
|
|
|
switch {
|
2024-05-06 18:29:51 +00:00
|
|
|
case p.Settings.Registry.Password != "":
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Info().Msgf("Detected registry credentials")
|
2024-05-06 18:29:51 +00:00
|
|
|
case p.Settings.Registry.Config != "":
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Info().Msgf("Detected registry credentials file")
|
2021-01-11 20:54:49 +00:00
|
|
|
default:
|
2023-08-13 20:08:53 +00:00
|
|
|
log.Info().Msgf("Registry credentials or Docker config not provided. Guest mode enabled.")
|
2021-01-11 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
p.Settings.Build.AddProxyBuildArgs()
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2023-09-05 08:24:03 +00:00
|
|
|
backoffOps := func() error {
|
2024-05-06 18:29:51 +00:00
|
|
|
return docker.Version().Run()
|
2023-09-04 18:56:19 +00:00
|
|
|
}
|
|
|
|
backoffLog := func(err error, delay time.Duration) {
|
2023-09-05 08:24:03 +00:00
|
|
|
log.Error().Msgf("failed to run docker version command: %v: retry in %s", err, delay.Truncate(time.Second))
|
2023-09-04 18:56:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := backoff.RetryNotify(backoffOps, newBackoff(daemonBackoffMaxRetries), backoffLog); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
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())
|
2021-01-11 20:54:49 +00:00
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
for _, cmd := range batchCmd {
|
2024-05-07 09:57:26 +00:00
|
|
|
if cmd == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-05-06 18:29:51 +00:00
|
|
|
if err := cmd.Run(); err != nil {
|
2021-01-11 20:54:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-08-21 09:35:52 +00:00
|
|
|
|
|
|
|
func (p *Plugin) FlagsFromContext() error {
|
2024-05-17 19:55:16 +00:00
|
|
|
cacheFrom, ok := p.Context.Generic("cache-from").(*plugin_types.StringSliceFlag)
|
2023-08-21 09:35:52 +00:00
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%w: failed to read cache-from input", ErrTypeAssertionFailed)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Settings.Build.CacheFrom = cacheFrom.Get()
|
|
|
|
|
2024-05-17 19:55:16 +00:00
|
|
|
secrets, ok := p.Context.Generic("secrets").(*plugin_types.StringSliceFlag)
|
2023-08-21 09:35:52 +00:00
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("%w: failed to read secrets input", ErrTypeAssertionFailed)
|
|
|
|
}
|
|
|
|
|
|
|
|
p.Settings.Build.Secrets = secrets.Get()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-09-04 18:56:19 +00:00
|
|
|
|
|
|
|
func newBackoff(maxRetries uint64) backoff.BackOff {
|
|
|
|
b := backoff.NewExponentialBackOff()
|
|
|
|
b.InitialInterval = daemonBackoffInitialInterval
|
|
|
|
b.Multiplier = daemonBackoffMultiplier
|
|
|
|
|
|
|
|
return backoff.WithMaxRetries(b, maxRetries)
|
|
|
|
}
|