From 757d2804ea321b2ba7b75234696144f829370b33 Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Mon, 6 May 2024 20:29:51 +0200 Subject: [PATCH] refactor: switch to plugin Cmd and add tests (#175) --- Makefile | 2 +- docker/daemon.go | 112 +++++++++++++++++++ docker/docker.go | 229 ++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- plugin/coredns.go | 42 ------- plugin/daemon.go | 31 ------ plugin/docker.go | 273 ---------------------------------------------- plugin/impl.go | 94 +++++++--------- plugin/plugin.go | 86 ++------------- plugin/util.go | 38 +++++++ 11 files changed, 435 insertions(+), 478 deletions(-) create mode 100644 docker/daemon.go create mode 100644 docker/docker.go delete mode 100644 plugin/coredns.go delete mode 100644 plugin/daemon.go delete mode 100644 plugin/docker.go create mode 100644 plugin/util.go diff --git a/Makefile b/Makefile index dab8e18..e814b72 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ TARGETARCH ?= amd64 ifneq ("$(TARGETVARIANT)","") GOARM ?= $(subst v,,$(TARGETVARIANT)) endif -TAGS ?= netgo +TAGS ?= netgo,osusergo ifndef VERSION ifneq ($(CI_COMMIT_TAG),) diff --git a/docker/daemon.go b/docker/daemon.go new file mode 100644 index 0000000..bc64cde --- /dev/null +++ b/docker/daemon.go @@ -0,0 +1,112 @@ +package docker + +import ( + "os/exec" + + "github.com/thegeeklab/wp-plugin-go/v2/types" + "github.com/urfave/cli/v2" + "golang.org/x/sys/execabs" +) + +const dockerdBin = "/usr/local/bin/dockerd" + +// Daemon defines Docker daemon parameters. +type 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 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 + BuildkitConfigFile string // Docker buildkit config file + MaxConcurrentUploads string // Docker daemon max concurrent uploads +} + +// helper function to create the docker daemon command. +func (d *Daemon) Start() *types.Cmd { + args := []string{ + "--data-root", d.StoragePath, + "--host=unix:///var/run/docker.sock", + } + + if d.StorageDriver != "" { + args = append(args, "-s", d.StorageDriver) + } + + if d.Insecure && d.Registry != "" { + args = append(args, "--insecure-registry", d.Registry) + } + + if d.IPv6 { + args = append(args, "--ipv6") + } + + if d.Mirror != "" { + args = append(args, "--registry-mirror", d.Mirror) + } + + if d.Bip != "" { + args = append(args, "--bip", d.Bip) + } + + for _, dns := range d.DNS.Value() { + args = append(args, "--dns", dns) + } + + for _, dnsSearch := range d.DNSSearch.Value() { + args = append(args, "--dns-search", dnsSearch) + } + + if d.MTU != "" { + args = append(args, "--mtu", d.MTU) + } + + if d.Experimental { + args = append(args, "--experimental") + } + + if d.MaxConcurrentUploads != "" { + args = append(args, "--max-concurrent-uploads", d.MaxConcurrentUploads) + } + + return &types.Cmd{ + Cmd: execabs.Command(dockerdBin, args...), + Private: !d.Debug, + } +} + +func (d *Daemon) CreateBuilder() *types.Cmd { + args := []string{ + "buildx", + "create", + "--use", + } + + if d.BuildkitConfigFile != "" { + args = append(args, "--config", d.BuildkitConfigFile) + } + + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, args...), + } +} + +func (d *Daemon) ListBuilder() *types.Cmd { + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, "buildx", "ls"), + } +} + +func (d *Daemon) StartCoreDNS() *types.Cmd { + return &types.Cmd{ + Cmd: exec.Command("coredns", "-conf", "/etc/coredns/Corefile"), + Private: !d.Debug, + } +} diff --git a/docker/docker.go b/docker/docker.go new file mode 100644 index 0000000..bb064f3 --- /dev/null +++ b/docker/docker.go @@ -0,0 +1,229 @@ +package docker + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/thegeeklab/wp-plugin-go/v2/types" + "github.com/urfave/cli/v2" + "golang.org/x/sys/execabs" +) + +const dockerBin = "/usr/local/bin/docker" + +// Login defines Docker login parameters. +type Registry struct { + Address 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 { + Ref string // Git commit ref + Branch string // Git repository branch + Containerfile string // Docker build Containerfile + Context string // Docker build context + TagsAuto bool // Docker build auto tag + TagsSuffix string // Docker build tags with suffix + Tags cli.StringSlice // Docker build tags + ExtraTags cli.StringSlice // Docker build tags including registry + 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 + Pull bool // Docker build pull + CacheFrom []string // Docker build cache-from + CacheTo string // Docker build cache-to + Compress bool // Docker build compress + Repo string // Docker build repository + NoCache bool // Docker build no-cache + AddHost cli.StringSlice // Docker build add-host + Quiet bool // Docker build quiet + Output string // Docker build output folder + NamedContext cli.StringSlice // Docker build named context + Labels cli.StringSlice // Docker build labels + Provenance string // Docker build provenance attestation + SBOM string // Docker build sbom attestation + Secrets []string // Docker build secrets + Dryrun bool // Docker build dryrun +} + +// helper function to create the docker login command. +func (r *Registry) Login() *types.Cmd { + args := []string{ + "login", + "-u", r.Username, + "-p", r.Password, + } + + if r.Email != "" { + args = append(args, "-e", r.Email) + } + + args = append(args, r.Address) + + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, args...), + } +} + +// helper function to create the docker info command. +func Version() *types.Cmd { + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, "version"), + } +} + +// helper function to create the docker info command. +func Info() *types.Cmd { + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, "info"), + } +} + +// helper function to create the docker build command. +func (b *Build) Run() *types.Cmd { + args := []string{ + "buildx", + "build", + "--rm=true", + "-f", b.Containerfile, + } + + defaultBuildArgs := []string{ + fmt.Sprintf("DOCKER_IMAGE_CREATED=%s", time.Now().Format(time.RFC3339)), + } + + args = append(args, b.Context) + if !b.Dryrun && b.Output == "" && len(b.Tags.Value()) > 0 { + args = append(args, "--push") + } + + if b.Compress { + args = append(args, "--compress") + } + + if b.Pull { + args = append(args, "--pull=true") + } + + if b.NoCache { + args = append(args, "--no-cache") + } + + for _, arg := range b.CacheFrom { + args = append(args, "--cache-from", arg) + } + + if b.CacheTo != "" { + args = append(args, "--cache-to", b.CacheTo) + } + + for _, arg := range b.ArgsEnv.Value() { + b.addProxyValue(arg) + } + + for _, arg := range append(defaultBuildArgs, b.Args.Value()...) { + args = append(args, "--build-arg", arg) + } + + for _, host := range b.AddHost.Value() { + args = append(args, "--add-host", host) + } + + if b.Target != "" { + args = append(args, "--target", b.Target) + } + + if b.Quiet { + args = append(args, "--quiet") + } + + if b.Output != "" { + args = append(args, "--output", b.Output) + } + + for _, arg := range b.NamedContext.Value() { + args = append(args, "--build-context", arg) + } + + if len(b.Platforms.Value()) > 0 { + args = append(args, "--platform", strings.Join(b.Platforms.Value(), ",")) + } + + for _, arg := range b.Tags.Value() { + args = append(args, "-t", fmt.Sprintf("%s:%s", b.Repo, arg)) + } + + for _, arg := range b.ExtraTags.Value() { + args = append(args, "-t", arg) + } + + for _, arg := range b.Labels.Value() { + args = append(args, "--label", arg) + } + + if b.Provenance != "" { + args = append(args, "--provenance", b.Provenance) + } + + if b.SBOM != "" { + args = append(args, "--sbom", b.SBOM) + } + + for _, secret := range b.Secrets { + args = append(args, "--secret", secret) + } + + return &types.Cmd{ + Cmd: execabs.Command(dockerBin, args...), + } +} + +// helper function to add proxy values from the environment. +func (b *Build) AddProxyBuildArgs() { + b.addProxyValue("http_proxy") + b.addProxyValue("https_proxy") + b.addProxyValue("no_proxy") +} + +// helper function to add the upper and lower case version of a proxy value. +func (b *Build) addProxyValue(key string) { + value := b.getProxyValue(key) + + if len(value) > 0 && !b.hasProxyBuildArg(key) { + b.Args = *cli.NewStringSlice(append(b.Args.Value(), fmt.Sprintf("%s=%s", key, value))...) + b.Args = *cli.NewStringSlice(append(b.Args.Value(), 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 (b *Build) 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 (b *Build) hasProxyBuildArg(key string) bool { + keyUpper := strings.ToUpper(key) + + for _, s := range b.Args.Value() { + if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { + return true + } + } + + return false +} diff --git a/go.mod b/go.mod index 3572ac3..566a133 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/rs/zerolog v1.32.0 - github.com/thegeeklab/wp-plugin-go/v2 v2.0.1 + github.com/thegeeklab/wp-plugin-go/v2 v2.3.0 github.com/urfave/cli/v2 v2.27.2 golang.org/x/sys v0.20.0 ) diff --git a/go.sum b/go.sum index 10b2d2c..ef1e65c 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/thegeeklab/wp-plugin-go/v2 v2.0.1 h1:42kqe5U1x5Ysa9I8tDEhh+tyvfFkfXKvlb3UsigBmN4= -github.com/thegeeklab/wp-plugin-go/v2 v2.0.1/go.mod h1:KRfDolkPSpO7Zx54Y0ofTFA7Cvd+7bHTHzYnYAo9WYg= +github.com/thegeeklab/wp-plugin-go/v2 v2.3.0 h1:9LOdITzjxEEbgcH9yU0tDZL0dcDZzNYzbk2+hZ5QsBA= +github.com/thegeeklab/wp-plugin-go/v2 v2.3.0/go.mod h1:I/3M/4OPvr4FFS+s0aaImpX1llA/lS2KC6Bnp+qzsCs= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= diff --git a/plugin/coredns.go b/plugin/coredns.go deleted file mode 100644 index 678f469..0000000 --- a/plugin/coredns.go +++ /dev/null @@ -1,42 +0,0 @@ -package plugin - -import ( - "io" - "net" - "os" - "os/exec" - - "github.com/thegeeklab/wp-plugin-go/v2/trace" -) - -func (p Plugin) startCoredns() { - cmd := exec.Command("coredns", "-conf", "/etc/coredns/Corefile") - if p.Settings.Daemon.Debug { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } else { - cmd.Stdout = io.Discard - cmd.Stderr = io.Discard - } - - go func() { - trace.Cmd(cmd) - _ = cmd.Run() - }() -} - -func getContainerIP() (string, error) { - netInterfaceAddrList, err := net.InterfaceAddrs() - if err != nil { - return "", err - } - - for _, netInterfaceAddr := range netInterfaceAddrList { - netIP, ok := netInterfaceAddr.(*net.IPNet) - if ok && !netIP.IP.IsLoopback() && netIP.IP.To4() != nil { - return netIP.IP.String(), nil - } - } - - return "", nil -} diff --git a/plugin/daemon.go b/plugin/daemon.go deleted file mode 100644 index 1b229be..0000000 --- a/plugin/daemon.go +++ /dev/null @@ -1,31 +0,0 @@ -package plugin - -import ( - "io" - "os" - - "github.com/thegeeklab/wp-plugin-go/v2/trace" -) - -const ( - dockerBin = "/usr/local/bin/docker" - dockerdBin = "/usr/local/bin/dockerd" - dockerHome = "/root/.docker/" - buildkitConfig = "/tmp/buildkit.toml" -) - -func (p Plugin) startDaemon() { - cmd := commandDaemon(p.Settings.Daemon) - if p.Settings.Daemon.Debug { - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - } else { - cmd.Stdout = io.Discard - cmd.Stderr = io.Discard - } - - go func() { - trace.Cmd(cmd.Cmd) - _ = cmd.Run() - }() -} diff --git a/plugin/docker.go b/plugin/docker.go deleted file mode 100644 index a6a1ba7..0000000 --- a/plugin/docker.go +++ /dev/null @@ -1,273 +0,0 @@ -package plugin - -import ( - "fmt" - "os" - "strings" - "time" - - "github.com/urfave/cli/v2" - "golang.org/x/sys/execabs" -) - -// helper function to create the docker login command. -func commandLogin(login Login) *Cmd { - if login.Email != "" { - return commandLoginEmail(login) - } - - args := []string{ - "login", - "-u", login.Username, - "-p", login.Password, - login.Registry, - } - - return &Cmd{ - Cmd: execabs.Command(dockerBin, args...), - } -} - -func commandLoginEmail(login Login) *Cmd { - args := []string{ - "login", - "-u", login.Username, - "-p", login.Password, - "-e", login.Email, - login.Registry, - } - - return &Cmd{ - Cmd: execabs.Command(dockerBin, args...), - } -} - -// helper function to create the docker info command. -func commandVersion() *Cmd { - return &Cmd{ - Cmd: execabs.Command(dockerBin, "version"), - } -} - -// helper function to create the docker info command. -func commandInfo() *Cmd { - return &Cmd{ - Cmd: execabs.Command(dockerBin, "info"), - } -} - -func commandBuilder(daemon Daemon) *Cmd { - args := []string{ - "buildx", - "create", - "--use", - } - - if daemon.BuildkitConfig != "" { - args = append(args, "--config", buildkitConfig) - } - - return &Cmd{ - Cmd: execabs.Command(dockerBin, args...), - } -} - -func commandBuildx() *Cmd { - return &Cmd{ - Cmd: execabs.Command(dockerBin, "buildx", "ls"), - } -} - -// helper function to create the docker build command. -func commandBuild(build Build, dryrun bool) *Cmd { - args := []string{ - "buildx", - "build", - "--rm=true", - "-f", build.Containerfile, - } - - defaultBuildArgs := []string{ - fmt.Sprintf("DOCKER_IMAGE_CREATED=%s", time.Now().Format(time.RFC3339)), - } - - args = append(args, build.Context) - if !dryrun && build.Output == "" && len(build.Tags.Value()) > 0 { - args = append(args, "--push") - } - - if build.Compress { - args = append(args, "--compress") - } - - if build.Pull { - args = append(args, "--pull=true") - } - - if build.NoCache { - args = append(args, "--no-cache") - } - - for _, arg := range build.CacheFrom { - args = append(args, "--cache-from", arg) - } - - if build.CacheTo != "" { - args = append(args, "--cache-to", build.CacheTo) - } - - for _, arg := range build.ArgsEnv.Value() { - addProxyValue(&build, arg) - } - - for _, arg := range append(defaultBuildArgs, build.Args.Value()...) { - args = append(args, "--build-arg", arg) - } - - for _, host := range build.AddHost.Value() { - args = append(args, "--add-host", host) - } - - if build.Target != "" { - args = append(args, "--target", build.Target) - } - - if build.Quiet { - args = append(args, "--quiet") - } - - if build.Output != "" { - args = append(args, "--output", build.Output) - } - - for _, arg := range build.NamedContext.Value() { - args = append(args, "--build-context", arg) - } - - if len(build.Platforms.Value()) > 0 { - args = append(args, "--platform", strings.Join(build.Platforms.Value(), ",")) - } - - for _, arg := range build.Tags.Value() { - args = append(args, "-t", fmt.Sprintf("%s:%s", build.Repo, arg)) - } - - for _, arg := range build.ExtraTags.Value() { - args = append(args, "-t", arg) - } - - for _, arg := range build.Labels.Value() { - args = append(args, "--label", arg) - } - - if build.Provenance != "" { - args = append(args, "--provenance", build.Provenance) - } - - if build.SBOM != "" { - args = append(args, "--sbom", build.SBOM) - } - - for _, secret := range build.Secrets { - args = append(args, "--secret", secret) - } - - return &Cmd{ - Cmd: execabs.Command(dockerBin, args...), - } -} - -// 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 = *cli.NewStringSlice(append(build.Args.Value(), fmt.Sprintf("%s=%s", key, value))...) - build.Args = *cli.NewStringSlice(append(build.Args.Value(), 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.Value() { - if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { - return true - } - } - - return false -} - -// helper function to create the docker daemon command. -func commandDaemon(daemon Daemon) *Cmd { - args := []string{ - "--data-root", daemon.StoragePath, - "--host=unix:///var/run/docker.sock", - } - - if daemon.StorageDriver != "" { - args = append(args, "-s", daemon.StorageDriver) - } - - if daemon.Insecure && daemon.Registry != "" { - args = append(args, "--insecure-registry", daemon.Registry) - } - - if daemon.IPv6 { - args = append(args, "--ipv6") - } - - if daemon.Mirror != "" { - args = append(args, "--registry-mirror", daemon.Mirror) - } - - if daemon.Bip != "" { - args = append(args, "--bip", daemon.Bip) - } - - for _, dns := range daemon.DNS.Value() { - args = append(args, "--dns", dns) - } - - for _, dnsSearch := range daemon.DNSSearch.Value() { - args = append(args, "--dns-search", dnsSearch) - } - - if daemon.MTU != "" { - args = append(args, "--mtu", daemon.MTU) - } - - if daemon.Experimental { - args = append(args, "--experimental") - } - - if daemon.MaxConcurrentUploads != "" { - args = append(args, "--max-concurrent-uploads", daemon.MaxConcurrentUploads) - } - - return &Cmd{ - Cmd: execabs.Command(dockerdBin, args...), - } -} diff --git a/plugin/impl.go b/plugin/impl.go index 9c347fa..24c04e0 100644 --- a/plugin/impl.go +++ b/plugin/impl.go @@ -5,14 +5,15 @@ import ( "errors" "fmt" "os" - "path/filepath" "time" "github.com/cenkalti/backoff/v4" "github.com/rs/zerolog/log" + "github.com/thegeeklab/wp-docker-buildx/docker" + "github.com/thegeeklab/wp-plugin-go/v2/file" "github.com/thegeeklab/wp-plugin-go/v2/tag" - "github.com/thegeeklab/wp-plugin-go/v2/trace" "github.com/thegeeklab/wp-plugin-go/v2/types" + "github.com/thegeeklab/wp-plugin-go/v2/util" "github.com/urfave/cli/v2" ) @@ -46,7 +47,7 @@ func (p *Plugin) run(ctx context.Context) error { 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.Login.Registry + p.Settings.Daemon.Registry = p.Settings.Registry.Address if p.Settings.Build.TagsAuto { // return true if tag event or default branch @@ -75,24 +76,29 @@ func (p *Plugin) Validate() error { } // Execute provides the implementation of the plugin. -// -//nolint:gocognit func (p *Plugin) Execute() error { - batchCmd := make([]*Cmd, 0) + var err error + + homeDir := util.GetUserHomeDir() + batchCmd := make([]*types.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() + 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) - p.startCoredns() + + 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) @@ -100,13 +106,16 @@ func (p *Plugin) Execute() error { } } - p.startDaemon() + 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 := commandInfo() + cmd := docker.Info() err := cmd.Run() if err == nil { @@ -116,57 +125,40 @@ func (p *Plugin) Execute() error { time.Sleep(time.Second * 1) } - // Create Auth Config File - if p.Settings.Login.Config != "" { - if err := os.MkdirAll(dockerHome, strictFilePerm); err != nil { - return fmt.Errorf("failed to create docker home: %w", err) - } - - path := filepath.Join(dockerHome, "config.json") - - err := os.WriteFile(path, []byte(p.Settings.Login.Config), strictFilePerm) - if err != nil { - return fmt.Errorf("error writing config.json: %w", err) + if p.Settings.Registry.Config != "" { + if err := WriteDockerConf(homeDir, p.Settings.Registry.Config); err != nil { + return fmt.Errorf("error writing docker config: %w", err) } } - // login to the Docker registry - if p.Settings.Login.Password != "" { - cmd := commandLogin(p.Settings.Login) - - err := cmd.Run() - if err != nil { + if p.Settings.Registry.Password != "" { + if err := p.Settings.Registry.Login().Run(); err != nil { return fmt.Errorf("error authenticating: %w", err) } } - if p.Settings.Daemon.BuildkitConfig != "" { - err := os.WriteFile(buildkitConfig, []byte(p.Settings.Daemon.BuildkitConfig), strictFilePerm) - if err != nil { - return fmt.Errorf("error writing buildkit.toml: %w", err) + buildkitConf := p.Settings.BuildkitConfig + if buildkitConf != "" { + if p.Settings.Daemon.BuildkitConfigFile, err = 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.Login.Password != "": + case p.Settings.Registry.Password != "": log.Info().Msgf("Detected registry credentials") - case p.Settings.Login.Config != "": + 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.") } - // add proxy build args - addProxyBuildArgs(&p.Settings.Build) + p.Settings.Build.AddProxyBuildArgs() backoffOps := func() error { - versionCmd := commandVersion() // docker version - - versionCmd.Stdout = os.Stdout - versionCmd.Stderr = os.Stderr - trace.Cmd(versionCmd.Cmd) - - return versionCmd.Run() + 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)) @@ -176,19 +168,13 @@ func (p *Plugin) Execute() error { return err } - batchCmd = append(batchCmd, commandInfo()) // docker info - batchCmd = append(batchCmd, commandBuilder(p.Settings.Daemon)) - batchCmd = append(batchCmd, commandBuildx()) - batchCmd = append(batchCmd, commandBuild(p.Settings.Build, p.Settings.Dryrun)) // docker build - - // execute all commands in batch mode. - for _, bc := range batchCmd { - bc.Stdout = os.Stdout - bc.Stderr = os.Stderr - trace.Cmd(bc.Cmd) + 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()) - err := bc.Run() - if err != nil { + for _, cmd := range batchCmd { + if err := cmd.Run(); err != nil { return err } } diff --git a/plugin/plugin.go b/plugin/plugin.go index 0d6ce7d..9d6ed54 100644 --- a/plugin/plugin.go +++ b/plugin/plugin.go @@ -3,10 +3,10 @@ package plugin import ( "fmt" + "github.com/thegeeklab/wp-docker-buildx/docker" wp "github.com/thegeeklab/wp-plugin-go/v2/plugin" "github.com/thegeeklab/wp-plugin-go/v2/types" "github.com/urfave/cli/v2" - "golang.org/x/sys/execabs" ) //go:generate go run ../internal/docs/main.go -output=../docs/data/data-raw.yaml @@ -19,73 +19,11 @@ type Plugin struct { // Settings for the Plugin. type Settings struct { - Daemon Daemon - Login Login - Build Build - Dryrun bool -} - -// Daemon defines Docker daemon parameters. -type 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 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 - BuildkitConfig string // Docker buildkit config - MaxConcurrentUploads string -} - -// 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 { - Ref string // Git commit ref - Branch string // Git repository branch - Containerfile string // Docker build Containerfile - Context string // Docker build context - TagsAuto bool // Docker build auto tag - TagsSuffix string // Docker build tags with suffix - Tags cli.StringSlice // Docker build tags - ExtraTags cli.StringSlice // Docker build tags including registry - 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 - Pull bool // Docker build pull - CacheFrom []string // Docker build cache-from - CacheTo string // Docker build cache-to - Compress bool // Docker build compress - Repo string // Docker build repository - NoCache bool // Docker build no-cache - AddHost cli.StringSlice // Docker build add-host - Quiet bool // Docker build quiet - Output string // Docker build output folder - NamedContext cli.StringSlice // Docker build named context - Labels cli.StringSlice // Docker build labels - Provenance string // Docker build provenance attestation - SBOM string // Docker build sbom attestation - Secrets []string // Docker build secrets -} + BuildkitConfig string -type Cmd struct { - *execabs.Cmd - Private bool + Daemon docker.Daemon + Registry docker.Registry + Build docker.Build } func New(e wp.ExecuteFunc, build ...string) *Plugin { @@ -127,7 +65,7 @@ func Flags(settings *Settings, category string) []cli.Flag { Name: "dry-run", EnvVars: []string{"PLUGIN_DRY_RUN"}, Usage: "disable docker push", - Destination: &settings.Dryrun, + Destination: &settings.Build.Dryrun, Category: category, }, &cli.StringFlag{ @@ -225,7 +163,7 @@ func Flags(settings *Settings, category string) []cli.Flag { Name: "daemon.buildkit-config", EnvVars: []string{"PLUGIN_BUILDKIT_CONFIG"}, Usage: "content of the docker buildkit toml config", - Destination: &settings.Daemon.BuildkitConfig, + Destination: &settings.BuildkitConfig, Category: category, }, &cli.StringFlag{ @@ -367,14 +305,14 @@ func Flags(settings *Settings, category string) []cli.Flag { EnvVars: []string{"PLUGIN_REGISTRY", "DOCKER_REGISTRY"}, Usage: "docker registry to authenticate with", Value: "https://index.docker.io/v1/", - Destination: &settings.Login.Registry, + Destination: &settings.Registry.Address, Category: category, }, &cli.StringFlag{ Name: "docker.username", EnvVars: []string{"PLUGIN_USERNAME", "DOCKER_USERNAME"}, Usage: "username for registry authentication", - Destination: &settings.Login.Username, + Destination: &settings.Registry.Username, DefaultText: "$DOCKER_USERNAME", Category: category, }, @@ -382,7 +320,7 @@ func Flags(settings *Settings, category string) []cli.Flag { Name: "docker.password", EnvVars: []string{"PLUGIN_PASSWORD", "DOCKER_PASSWORD"}, Usage: "password for registry authentication", - Destination: &settings.Login.Password, + Destination: &settings.Registry.Password, DefaultText: "$DOCKER_PASSWORD", Category: category, }, @@ -390,7 +328,7 @@ func Flags(settings *Settings, category string) []cli.Flag { Name: "docker.email", EnvVars: []string{"PLUGIN_EMAIL", "DOCKER_EMAIL"}, Usage: "email address for registry authentication", - Destination: &settings.Login.Email, + Destination: &settings.Registry.Email, DefaultText: "$DOCKER_EMAIL", Category: category, }, @@ -398,7 +336,7 @@ func Flags(settings *Settings, category string) []cli.Flag { Name: "docker.config", EnvVars: []string{"PLUGIN_CONFIG", "DOCKER_PLUGIN_CONFIG"}, Usage: "content of the docker daemon json config", - Destination: &settings.Login.Config, + Destination: &settings.Registry.Config, DefaultText: "$DOCKER_PLUGIN_CONFIG", Category: category, }, diff --git a/plugin/util.go b/plugin/util.go new file mode 100644 index 0000000..91f6296 --- /dev/null +++ b/plugin/util.go @@ -0,0 +1,38 @@ +package plugin + +import ( + "net" + "os" + "path/filepath" +) + +func GetContainerIP() (string, error) { + netInterfaceAddrList, err := net.InterfaceAddrs() + if err != nil { + return "", err + } + + for _, netInterfaceAddr := range netInterfaceAddrList { + netIP, ok := netInterfaceAddr.(*net.IPNet) + if ok && !netIP.IP.IsLoopback() && netIP.IP.To4() != nil { + return netIP.IP.String(), nil + } + } + + return "", nil +} + +func WriteDockerConf(path, conf string) error { + confPath := filepath.Join(path, ".docker", "config.json") + + if err := os.MkdirAll(confPath, strictFilePerm); err != nil { + return err + } + + err := os.WriteFile(path, []byte(conf), strictFilePerm) + if err != nil { + return err + } + + return nil +}