mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-10 03:47:03 +02:00
d5a413f9f4
its too hard to debug docker build issues... sometimes is just a typo in the binary name, and you might end debugging it for way too long... this prints the full path to the build context (so, locally at least, you can cd into it) and also all the files available there when the error seems to be one of the "file not found" kind. Hopefully this helps fixing things easier :) closes #3912 Signed-off-by: Carlos A Becker <caarlos0@users.noreply.github.com>
309 lines
7.9 KiB
Go
309 lines
7.9 KiB
Go
package docker
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/caarlos0/log"
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
|
"github.com/goreleaser/goreleaser/internal/gio"
|
|
"github.com/goreleaser/goreleaser/internal/ids"
|
|
"github.com/goreleaser/goreleaser/internal/pipe"
|
|
"github.com/goreleaser/goreleaser/internal/semerrgroup"
|
|
"github.com/goreleaser/goreleaser/internal/tmpl"
|
|
"github.com/goreleaser/goreleaser/pkg/config"
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
|
)
|
|
|
|
const (
|
|
dockerConfigExtra = "DockerConfig"
|
|
|
|
useBuildx = "buildx"
|
|
useDocker = "docker"
|
|
)
|
|
|
|
// Pipe for docker.
|
|
type Pipe struct{}
|
|
|
|
func (Pipe) String() string { return "docker images" }
|
|
func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.Dockers) == 0 || ctx.SkipDocker }
|
|
|
|
func (Pipe) Dependencies(ctx *context.Context) []string {
|
|
var cmds []string
|
|
for _, s := range ctx.Config.Dockers {
|
|
switch s.Use {
|
|
case useDocker, useBuildx:
|
|
cmds = append(cmds, "docker")
|
|
// TODO: how to check if buildx is installed
|
|
}
|
|
}
|
|
return cmds
|
|
}
|
|
|
|
// Default sets the pipe defaults.
|
|
func (Pipe) Default(ctx *context.Context) error {
|
|
ids := ids.New("dockers")
|
|
for i := range ctx.Config.Dockers {
|
|
docker := &ctx.Config.Dockers[i]
|
|
|
|
if docker.ID != "" {
|
|
ids.Inc(docker.ID)
|
|
}
|
|
if docker.Goos == "" {
|
|
docker.Goos = "linux"
|
|
}
|
|
if docker.Goarch == "" {
|
|
docker.Goarch = "amd64"
|
|
}
|
|
if docker.Goarm == "" {
|
|
docker.Goarm = "6"
|
|
}
|
|
if docker.Goamd64 == "" {
|
|
docker.Goamd64 = "v1"
|
|
}
|
|
if docker.Dockerfile == "" {
|
|
docker.Dockerfile = "Dockerfile"
|
|
}
|
|
if docker.Use == "" {
|
|
docker.Use = useDocker
|
|
}
|
|
if err := validateImager(docker.Use); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return ids.Validate()
|
|
}
|
|
|
|
func validateImager(use string) error {
|
|
valid := make([]string, 0, len(imagers))
|
|
for k := range imagers {
|
|
valid = append(valid, k)
|
|
}
|
|
for _, s := range valid {
|
|
if s == use {
|
|
return nil
|
|
}
|
|
}
|
|
sort.Strings(valid)
|
|
return fmt.Errorf("docker: invalid use: %s, valid options are %v", use, valid)
|
|
}
|
|
|
|
// Publish the docker images.
|
|
func (Pipe) Publish(ctx *context.Context) error {
|
|
skips := pipe.SkipMemento{}
|
|
images := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableDockerImage)).List()
|
|
for _, image := range images {
|
|
if err := dockerPush(ctx, image); err != nil {
|
|
if pipe.IsSkip(err) {
|
|
skips.Remember(err)
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return skips.Evaluate()
|
|
}
|
|
|
|
// Run the pipe.
|
|
func (Pipe) Run(ctx *context.Context) error {
|
|
g := semerrgroup.NewSkipAware(semerrgroup.New(ctx.Parallelism))
|
|
for i, docker := range ctx.Config.Dockers {
|
|
i := i
|
|
docker := docker
|
|
g.Go(func() error {
|
|
log := log.WithField("index", i)
|
|
log.Debug("looking for artifacts matching")
|
|
filters := []artifact.Filter{
|
|
artifact.ByGoos(docker.Goos),
|
|
artifact.ByGoarch(docker.Goarch),
|
|
artifact.Or(
|
|
artifact.ByType(artifact.Binary),
|
|
artifact.ByType(artifact.LinuxPackage),
|
|
),
|
|
}
|
|
// TODO: properly test this
|
|
switch docker.Goarch {
|
|
case "amd64":
|
|
filters = append(filters, artifact.ByGoamd64(docker.Goamd64))
|
|
case "arm":
|
|
filters = append(filters, artifact.ByGoarm(docker.Goarm))
|
|
}
|
|
if len(docker.IDs) > 0 {
|
|
filters = append(filters, artifact.ByIDs(docker.IDs...))
|
|
}
|
|
artifacts := ctx.Artifacts.Filter(artifact.And(filters...))
|
|
log.WithField("artifacts", artifacts.Paths()).Debug("found artifacts")
|
|
return process(ctx, docker, artifacts.List())
|
|
})
|
|
}
|
|
if err := g.Wait(); err != nil {
|
|
if pipe.IsSkip(err) {
|
|
return err
|
|
}
|
|
return fmt.Errorf("docker build failed: %w\nLearn more at https://goreleaser.com/errors/docker-build\n", err) // nolint:revive
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func process(ctx *context.Context, docker config.Docker, artifacts []*artifact.Artifact) error {
|
|
if len(artifacts) == 0 {
|
|
log.Warn("not binaries or packages found for the given platform - COPY/ADD may not work")
|
|
}
|
|
tmp, err := os.MkdirTemp("", "goreleaserdocker")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create temporary dir: %w", err)
|
|
}
|
|
|
|
images, err := processImageTemplates(ctx, docker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(images) == 0 {
|
|
return pipe.Skip("no image templates found")
|
|
}
|
|
|
|
log := log.WithField("image", images[0])
|
|
log.Debug("tempdir: " + tmp)
|
|
|
|
dockerfile, err := tmpl.New(ctx).Apply(docker.Dockerfile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := gio.Copy(dockerfile, filepath.Join(tmp, "Dockerfile")); err != nil {
|
|
return fmt.Errorf("failed to copy dockerfile: %w", err)
|
|
}
|
|
|
|
for _, file := range docker.Files {
|
|
if err := os.MkdirAll(filepath.Join(tmp, filepath.Dir(file)), 0o755); err != nil {
|
|
return fmt.Errorf("failed to copy extra file '%s': %w", file, err)
|
|
}
|
|
if err := gio.Copy(file, filepath.Join(tmp, file)); err != nil {
|
|
return fmt.Errorf("failed to copy extra file '%s': %w", file, err)
|
|
}
|
|
}
|
|
for _, art := range artifacts {
|
|
if err := gio.Copy(art.Path, filepath.Join(tmp, filepath.Base(art.Path))); err != nil {
|
|
return fmt.Errorf("failed to copy artifact: %w", err)
|
|
}
|
|
}
|
|
|
|
buildFlags, err := processBuildFlagTemplates(ctx, docker)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Info("building docker image")
|
|
if err := imagers[docker.Use].Build(ctx, tmp, images, buildFlags); err != nil {
|
|
if strings.Contains(err.Error(), "file not found") || strings.Contains(err.Error(), "not found: not found") {
|
|
var files []string
|
|
_ = filepath.Walk(tmp, func(path string, info fs.FileInfo, err error) error {
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
files = append(files, info.Name())
|
|
return nil
|
|
})
|
|
return fmt.Errorf(`seems like you tried to copy a file that is not available in the build context.
|
|
|
|
Here's more information about the build context:
|
|
|
|
dir: %q
|
|
files in that dir:
|
|
%s
|
|
|
|
Previous error:
|
|
%w`, tmp, strings.Join(files, "\n "), err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
for _, img := range images {
|
|
ctx.Artifacts.Add(&artifact.Artifact{
|
|
Type: artifact.PublishableDockerImage,
|
|
Name: img,
|
|
Path: img,
|
|
Goarch: docker.Goarch,
|
|
Goos: docker.Goos,
|
|
Goarm: docker.Goarm,
|
|
Extra: map[string]interface{}{
|
|
dockerConfigExtra: docker,
|
|
},
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func processImageTemplates(ctx *context.Context, docker config.Docker) ([]string, error) {
|
|
// nolint:prealloc
|
|
var images []string
|
|
for _, imageTemplate := range docker.ImageTemplates {
|
|
image, err := tmpl.New(ctx).Apply(imageTemplate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to execute image template '%s': %w", imageTemplate, err)
|
|
}
|
|
if image == "" {
|
|
continue
|
|
}
|
|
|
|
images = append(images, image)
|
|
}
|
|
|
|
return images, nil
|
|
}
|
|
|
|
func processBuildFlagTemplates(ctx *context.Context, docker config.Docker) ([]string, error) {
|
|
// nolint:prealloc
|
|
var buildFlags []string
|
|
for _, buildFlagTemplate := range docker.BuildFlagTemplates {
|
|
buildFlag, err := tmpl.New(ctx).Apply(buildFlagTemplate)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to process build flag template '%s': %w", buildFlagTemplate, err)
|
|
}
|
|
buildFlags = append(buildFlags, buildFlag)
|
|
}
|
|
return buildFlags, nil
|
|
}
|
|
|
|
func dockerPush(ctx *context.Context, image *artifact.Artifact) error {
|
|
log.WithField("image", image.Name).Info("pushing")
|
|
|
|
docker, err := artifact.Extra[config.Docker](*image, dockerConfigExtra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if strings.TrimSpace(docker.SkipPush) == "true" {
|
|
return pipe.Skip("docker.skip_push is set: " + image.Name)
|
|
}
|
|
if strings.TrimSpace(docker.SkipPush) == "auto" && ctx.Semver.Prerelease != "" {
|
|
return pipe.Skip("prerelease detected with 'auto' push, skipping docker publish: " + image.Name)
|
|
}
|
|
|
|
digest, err := imagers[docker.Use].Push(ctx, image.Name, docker.PushFlags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
art := &artifact.Artifact{
|
|
Type: artifact.DockerImage,
|
|
Name: image.Name,
|
|
Path: image.Path,
|
|
Goarch: image.Goarch,
|
|
Goos: image.Goos,
|
|
Goarm: image.Goarm,
|
|
Extra: map[string]interface{}{},
|
|
}
|
|
if docker.ID != "" {
|
|
art.Extra[artifact.ExtraID] = docker.ID
|
|
}
|
|
art.Extra[artifact.ExtraDigest] = digest
|
|
|
|
ctx.Artifacts.Add(art)
|
|
return nil
|
|
}
|